deploylog 0.2.2 → 0.4.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/LICENSE +21 -0
- package/README.md +117 -36
- package/dist/api.d.ts +59 -1
- package/dist/api.js +43 -2
- package/dist/editor.d.ts +23 -0
- package/dist/editor.js +46 -0
- package/dist/entry-commands.d.ts +111 -0
- package/dist/entry-commands.js +157 -0
- package/dist/index.js +486 -110
- package/dist/init.d.ts +38 -0
- package/dist/init.js +86 -0
- package/dist/open.d.ts +33 -0
- package/dist/open.js +51 -0
- package/dist/project-config.d.ts +15 -5
- package/dist/project-config.js +43 -7
- package/dist/push.d.ts +71 -0
- package/dist/push.js +149 -0
- package/dist/resolve.d.ts +18 -0
- package/dist/resolve.js +16 -0
- package/package.json +4 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Monolithiq LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/marko-builds/deploylog-cli/main/.github/assets/logo.png" width="120" alt="DeployLog" />
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">deploylog</h1>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
npm i -g deploylog
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
Node 18+ required.
|
|
26
|
+
Node 18+ required. Installs two equivalent commands: `deploylog` and the short alias `dpl`.
|
|
27
27
|
|
|
28
28
|
## Authenticate
|
|
29
29
|
|
|
@@ -38,54 +38,94 @@ Credentials are stored with [`conf`](https://github.com/sindresorhus/conf) in yo
|
|
|
38
38
|
## Quick start
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
#
|
|
42
|
-
deploylog
|
|
41
|
+
# Wire this repo to a project (writes .deploylog.yml)
|
|
42
|
+
deploylog init
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
deploylog push
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
--publish
|
|
44
|
+
# Draft an entry from your commits, rewritten by AI
|
|
45
|
+
deploylog push --from-git --ai-summarize
|
|
46
|
+
|
|
47
|
+
# Review it, then ship it
|
|
48
|
+
deploylog list --drafts
|
|
49
|
+
deploylog view dark-mode
|
|
50
|
+
deploylog publish dark-mode
|
|
52
51
|
```
|
|
53
52
|
|
|
53
|
+
The full lifecycle lives in the terminal: create, list, view, edit, publish, unpublish, delete. No dashboard detour.
|
|
54
|
+
|
|
54
55
|
## Project config
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
`deploylog init` writes a `.deploylog.yml` at your repo root so you don't have to pass `--project` every time:
|
|
57
58
|
|
|
58
59
|
```yaml
|
|
59
60
|
project: my-app
|
|
61
|
+
default_type: feature # optional
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Referencing entries
|
|
65
|
+
|
|
66
|
+
Entry commands accept a **slug or an id** (`deploylog publish dark-mode`, `deploylog publish 3f2b8a1c-...`). Slugs are matched against the 50 most recent entries; older entries need the id (`deploylog list` shows both). Note that editing a draft's title can change its slug, so scripts should prefer ids.
|
|
67
|
+
|
|
68
|
+
## Machine-readable output
|
|
69
|
+
|
|
70
|
+
Every data command takes `--json`: raw JSON on stdout, errors as `{"error":{"code","message"}}` on stderr, and no interactive prompts, ever. Built for CI and AI agents.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
deploylog list --drafts --json | jq -r '.[0].id'
|
|
60
74
|
```
|
|
61
75
|
|
|
62
76
|
## Commands
|
|
63
77
|
|
|
64
|
-
### `deploylog login`
|
|
78
|
+
### `deploylog login` / `deploylog logout`
|
|
65
79
|
|
|
66
|
-
Authenticate with an API key.
|
|
80
|
+
Authenticate with an API key (create one at [deploylog.dev/dashboard/api-keys](https://deploylog.dev/dashboard/api-keys)).
|
|
67
81
|
|
|
68
82
|
```
|
|
69
83
|
--key <key> API key (starts with dk_)
|
|
70
84
|
--api-url <url> API base URL (default: https://deploylog.dev)
|
|
71
85
|
```
|
|
72
86
|
|
|
73
|
-
### `deploylog
|
|
87
|
+
### `deploylog init`
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
Scaffold `.deploylog.yml` in the current directory. Picks the project interactively, or takes `--project`.
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
```
|
|
92
|
+
-p, --project <slug> Project slug
|
|
93
|
+
-T, --type <type> Default entry type for pushes from this repo
|
|
94
|
+
--force Overwrite an existing .deploylog.yml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `deploylog projects` (alias: `proj`)
|
|
78
98
|
|
|
79
99
|
List projects in your organization.
|
|
80
100
|
|
|
81
|
-
### `deploylog
|
|
101
|
+
### `deploylog projects create <name>`
|
|
102
|
+
|
|
103
|
+
Create a project. The slug is generated from the name.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
--url <url> Project website URL
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### `deploylog whoami`
|
|
110
|
+
|
|
111
|
+
Show the authenticated org, plan, API key (name, prefix, permissions), and AI usage this month.
|
|
112
|
+
|
|
113
|
+
### `deploylog list` (alias: `ls`)
|
|
82
114
|
|
|
83
|
-
List recent entries for a project.
|
|
115
|
+
List recent entries for a project. Prints each entry's slug and id.
|
|
84
116
|
|
|
85
117
|
```
|
|
86
118
|
-p, --project <slug> Project slug (or set in .deploylog.yml)
|
|
119
|
+
--drafts Only drafts
|
|
120
|
+
--published Only published entries
|
|
121
|
+
-T, --type <type> Filter by entry type
|
|
122
|
+
-n, --limit <n> Max entries (1-50)
|
|
87
123
|
```
|
|
88
124
|
|
|
125
|
+
### `deploylog view <entry>` (alias: `show`)
|
|
126
|
+
|
|
127
|
+
Show a full entry, including its Markdown body.
|
|
128
|
+
|
|
89
129
|
### `deploylog push`
|
|
90
130
|
|
|
91
131
|
Create a new changelog entry.
|
|
@@ -94,44 +134,85 @@ Create a new changelog entry.
|
|
|
94
134
|
-t, --title <title> Entry title
|
|
95
135
|
-b, --body <markdown> Entry body (Markdown)
|
|
96
136
|
-p, --project <slug> Project slug (or set in .deploylog.yml)
|
|
97
|
-
--type <type>
|
|
137
|
+
-T, --type <type> feature | fix | improvement | breaking | announcement
|
|
98
138
|
--version <version> Semver (e.g. 1.2.3)
|
|
99
|
-
--publish
|
|
100
|
-
--draft
|
|
101
|
-
--from-git
|
|
102
|
-
--ai-summarize
|
|
139
|
+
-P, --publish Publish immediately
|
|
140
|
+
-D, --draft Save as draft (default)
|
|
141
|
+
-g, --from-git Derive title/body from commits since the last tag (alias: --git)
|
|
142
|
+
-a, --ai-summarize Rewrite the entry with Claude Haiku (alias: --ai)
|
|
103
143
|
-y, --yes Skip interactive confirmation for AI-generated content
|
|
104
144
|
```
|
|
105
145
|
|
|
146
|
+
### `deploylog edit <entry>`
|
|
147
|
+
|
|
148
|
+
Update an entry. With no field flags on an interactive terminal, your `$EDITOR` opens prefilled with the current body. If the server rejects an edited body, it is saved to a recovery file, never lost.
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
-t, --title <title> New title (may change a draft's slug; the CLI tells you)
|
|
152
|
+
-T, --type <type> Entry type
|
|
153
|
+
--version <version> Semver version (pass "" to clear)
|
|
154
|
+
-b, --body <markdown> New body
|
|
155
|
+
--body-file <path> Read the new body from a file (- for stdin)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `deploylog publish <entry>` (alias: `pub`) / `deploylog unpublish <entry>` (alias: `unpub`)
|
|
159
|
+
|
|
160
|
+
Publish a draft, or revert a published entry to draft. Publishing is idempotent: re-running is a no-op, and the email digest (Pro) is sent at most once per entry, ever. Unpublishing resets the publish date; republishing gets a new one.
|
|
161
|
+
|
|
162
|
+
### `deploylog delete <entry>` (alias: `rm`)
|
|
163
|
+
|
|
164
|
+
Delete an entry permanently. Prompts for confirmation on a terminal; requires `--yes` in CI or `--json` mode.
|
|
165
|
+
|
|
166
|
+
### `deploylog import github <repo>`
|
|
167
|
+
|
|
168
|
+
Backfill your existing GitHub releases as draft entries. Takes `owner/repo` or a github.com URL. Skips versions you already imported.
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
-p, --project <slug> Project slug (or set in .deploylog.yml)
|
|
172
|
+
--token <token> GitHub PAT for private repos / rate limits (or DEPLOYLOG_GITHUB_TOKEN); never stored
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### `deploylog open [entry]` (alias: `o`)
|
|
176
|
+
|
|
177
|
+
Open the project's public changelog (or one entry's page) in your browser. Prints the URL when headless.
|
|
178
|
+
|
|
106
179
|
## Recipes
|
|
107
180
|
|
|
108
|
-
**
|
|
181
|
+
**Onboard a repo in one minute:**
|
|
109
182
|
|
|
110
183
|
```bash
|
|
111
|
-
deploylog
|
|
184
|
+
deploylog login --key dk_xxx
|
|
185
|
+
deploylog projects create "My App" --url https://myapp.dev
|
|
186
|
+
deploylog init
|
|
187
|
+
deploylog import github me/my-app # backfill old releases as drafts
|
|
188
|
+
deploylog list --drafts # review
|
|
189
|
+
deploylog publish v1-4-0
|
|
112
190
|
```
|
|
113
191
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
**AI-polished release notes:**
|
|
192
|
+
**Draft from recent commits, ship after review:**
|
|
117
193
|
|
|
118
194
|
```bash
|
|
119
|
-
deploylog push --from-git --ai-summarize
|
|
195
|
+
deploylog push --from-git --ai-summarize
|
|
196
|
+
deploylog view <slug> # read what the AI wrote
|
|
197
|
+
deploylog edit <slug> # tweak in $EDITOR
|
|
198
|
+
deploylog publish <slug>
|
|
120
199
|
```
|
|
121
200
|
|
|
122
|
-
|
|
201
|
+
**CI: publish on release, no prompts:**
|
|
123
202
|
|
|
124
|
-
|
|
203
|
+
```bash
|
|
204
|
+
deploylog push --from-git --ai-summarize --yes --publish --json
|
|
205
|
+
```
|
|
125
206
|
|
|
126
|
-
For
|
|
207
|
+
For GitHub Actions specifically, prefer the official Action: [`deploylogdev/action`](https://github.com/marketplace/actions/publish-to-deploylog).
|
|
127
208
|
|
|
128
|
-
-
|
|
209
|
+
**Agent-driven usage:** every command's `--json` output is stable and prompt-free; destructive operations refuse without an explicit `--yes`.
|
|
129
210
|
|
|
130
211
|
## Related
|
|
131
212
|
|
|
132
213
|
- **Dashboard** — [deploylog.dev](https://deploylog.dev)
|
|
133
214
|
- **Widget** — embeddable changelog widget at `cdn.deploylog.dev`
|
|
134
|
-
- **GitHub Action** — [`deploylogdev/action@v1`](https://github.com/marketplace/actions/deploylog)
|
|
215
|
+
- **GitHub Action** — [`deploylogdev/action@v1`](https://github.com/marketplace/actions/publish-to-deploylog)
|
|
135
216
|
|
|
136
217
|
## License
|
|
137
218
|
|
package/dist/api.d.ts
CHANGED
|
@@ -21,7 +21,55 @@ export interface Entry {
|
|
|
21
21
|
created_at: string;
|
|
22
22
|
}
|
|
23
23
|
export declare function listProjects(): Promise<Project[]>;
|
|
24
|
-
export declare function
|
|
24
|
+
export declare function createProject(name: string, websiteUrl?: string): Promise<Project>;
|
|
25
|
+
export interface GithubImportResult {
|
|
26
|
+
imported: number;
|
|
27
|
+
skipped: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function importGithub(projectSlug: string, repo: string, token?: string): Promise<GithubImportResult>;
|
|
30
|
+
export interface WhoAmI {
|
|
31
|
+
organization: {
|
|
32
|
+
name: string;
|
|
33
|
+
slug: string;
|
|
34
|
+
plan: string | null;
|
|
35
|
+
};
|
|
36
|
+
api_key: {
|
|
37
|
+
name: string;
|
|
38
|
+
prefix: string;
|
|
39
|
+
permissions: string[];
|
|
40
|
+
last_used_at: string | null;
|
|
41
|
+
created_at: string;
|
|
42
|
+
};
|
|
43
|
+
ai_usage: {
|
|
44
|
+
used: number;
|
|
45
|
+
limit: number | null;
|
|
46
|
+
month_key: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export declare function whoami(): Promise<WhoAmI>;
|
|
50
|
+
export interface ListEntriesFilters {
|
|
51
|
+
status?: 'draft' | 'published';
|
|
52
|
+
type?: string;
|
|
53
|
+
limit?: number;
|
|
54
|
+
}
|
|
55
|
+
export declare function listEntries(projectSlug: string, filters?: ListEntriesFilters): Promise<Entry[]>;
|
|
56
|
+
/** Full entry including the markdown body — the single-entry GET. */
|
|
57
|
+
export interface EntryDetail extends Entry {
|
|
58
|
+
body_markdown: string;
|
|
59
|
+
updated_at: string | null;
|
|
60
|
+
}
|
|
61
|
+
export declare function getEntry(entryId: string): Promise<EntryDetail>;
|
|
62
|
+
export interface UpdateEntryInput {
|
|
63
|
+
title?: string;
|
|
64
|
+
body_markdown?: string;
|
|
65
|
+
entry_type?: string | null;
|
|
66
|
+
version?: string;
|
|
67
|
+
slug?: string;
|
|
68
|
+
}
|
|
69
|
+
export declare function updateEntry(entryId: string, input: UpdateEntryInput): Promise<EntryDetail>;
|
|
70
|
+
export declare function deleteEntry(entryId: string): Promise<{
|
|
71
|
+
id: string;
|
|
72
|
+
}>;
|
|
25
73
|
export interface CreateEntryInput {
|
|
26
74
|
title: string;
|
|
27
75
|
body_markdown: string;
|
|
@@ -30,6 +78,16 @@ export interface CreateEntryInput {
|
|
|
30
78
|
publish?: boolean;
|
|
31
79
|
}
|
|
32
80
|
export declare function createEntry(projectSlug: string, input: CreateEntryInput): Promise<Entry>;
|
|
81
|
+
/** Entry row returned by the publish route, plus whether the state actually changed. */
|
|
82
|
+
export interface PublishStateEntry {
|
|
83
|
+
id: string;
|
|
84
|
+
title: string;
|
|
85
|
+
slug: string;
|
|
86
|
+
published: boolean;
|
|
87
|
+
published_at: string | null;
|
|
88
|
+
changed: boolean;
|
|
89
|
+
}
|
|
90
|
+
export declare function setEntryPublished(entryId: string, published: boolean): Promise<PublishStateEntry>;
|
|
33
91
|
export interface SummarizeInput {
|
|
34
92
|
project_slug?: string;
|
|
35
93
|
commits?: string[];
|
package/dist/api.js
CHANGED
|
@@ -46,8 +46,43 @@ async function request(path, options = {}) {
|
|
|
46
46
|
export async function listProjects() {
|
|
47
47
|
return request('/projects');
|
|
48
48
|
}
|
|
49
|
-
export async function
|
|
50
|
-
return request(
|
|
49
|
+
export async function createProject(name, websiteUrl) {
|
|
50
|
+
return request('/projects', {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
body: JSON.stringify({ name, website_url: websiteUrl ?? '' }),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export async function importGithub(projectSlug, repo, token) {
|
|
56
|
+
return request(`/projects/${projectSlug}/import/github`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
body: JSON.stringify(token ? { repo, token } : { repo }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
export async function whoami() {
|
|
62
|
+
return request('/whoami');
|
|
63
|
+
}
|
|
64
|
+
export async function listEntries(projectSlug, filters = {}) {
|
|
65
|
+
const params = new URLSearchParams();
|
|
66
|
+
if (filters.status)
|
|
67
|
+
params.set('status', filters.status);
|
|
68
|
+
if (filters.type)
|
|
69
|
+
params.set('type', filters.type);
|
|
70
|
+
if (filters.limit !== undefined)
|
|
71
|
+
params.set('limit', String(filters.limit));
|
|
72
|
+
const qs = params.toString();
|
|
73
|
+
return request(`/projects/${projectSlug}/entries${qs ? `?${qs}` : ''}`);
|
|
74
|
+
}
|
|
75
|
+
export async function getEntry(entryId) {
|
|
76
|
+
return request(`/entries/${entryId}`);
|
|
77
|
+
}
|
|
78
|
+
export async function updateEntry(entryId, input) {
|
|
79
|
+
return request(`/entries/${entryId}`, {
|
|
80
|
+
method: 'PATCH',
|
|
81
|
+
body: JSON.stringify(input),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
export async function deleteEntry(entryId) {
|
|
85
|
+
return request(`/entries/${entryId}`, { method: 'DELETE' });
|
|
51
86
|
}
|
|
52
87
|
export async function createEntry(projectSlug, input) {
|
|
53
88
|
return request(`/projects/${projectSlug}/entries`, {
|
|
@@ -55,6 +90,12 @@ export async function createEntry(projectSlug, input) {
|
|
|
55
90
|
body: JSON.stringify(input),
|
|
56
91
|
});
|
|
57
92
|
}
|
|
93
|
+
export async function setEntryPublished(entryId, published) {
|
|
94
|
+
return request(`/entries/${entryId}/publish`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
body: JSON.stringify({ published }),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
58
99
|
export async function summarize(input) {
|
|
59
100
|
return request('/ai-summarize', {
|
|
60
101
|
method: 'POST',
|
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type EditorResult = {
|
|
2
|
+
kind: 'edited';
|
|
3
|
+
body: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'unchanged';
|
|
6
|
+
} | {
|
|
7
|
+
kind: 'aborted';
|
|
8
|
+
message: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'no-editor';
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Round-trip a markdown body through $EDITOR (fallback: $VISUAL, then vi).
|
|
15
|
+
* Trailing newlines are normalized before the no-op check — editors append
|
|
16
|
+
* one on save, and that alone must not count as a change.
|
|
17
|
+
*/
|
|
18
|
+
export declare function editInEditor(initial: string): EditorResult;
|
|
19
|
+
/**
|
|
20
|
+
* Persist a body the server rejected so the user's writing is never lost —
|
|
21
|
+
* printed in the error message as a recoverable path.
|
|
22
|
+
*/
|
|
23
|
+
export declare function saveRecoveryFile(body: string): string;
|
package/dist/editor.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* Round-trip a markdown body through $EDITOR (fallback: $VISUAL, then vi).
|
|
7
|
+
* Trailing newlines are normalized before the no-op check — editors append
|
|
8
|
+
* one on save, and that alone must not count as a change.
|
|
9
|
+
*/
|
|
10
|
+
export function editInEditor(initial) {
|
|
11
|
+
const editor = process.env.EDITOR ?? process.env.VISUAL ?? 'vi';
|
|
12
|
+
const dir = mkdtempSync(join(tmpdir(), 'deploylog-'));
|
|
13
|
+
const file = join(dir, 'entry.md');
|
|
14
|
+
writeFileSync(file, initial, 'utf8');
|
|
15
|
+
try {
|
|
16
|
+
const res = spawnSync(editor, [file], { stdio: 'inherit' });
|
|
17
|
+
if (res.error) {
|
|
18
|
+
return {
|
|
19
|
+
kind: 'no-editor',
|
|
20
|
+
message: `Could not launch editor '${editor}'. Set $EDITOR, or pass --body / --body-file instead.`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
if (res.status !== 0) {
|
|
24
|
+
return { kind: 'aborted', message: `Editor exited with status ${res.status}; entry not changed.` };
|
|
25
|
+
}
|
|
26
|
+
const edited = readFileSync(file, 'utf8');
|
|
27
|
+
if (edited.replace(/\n+$/, '') === initial.replace(/\n+$/, '')) {
|
|
28
|
+
return { kind: 'unchanged' };
|
|
29
|
+
}
|
|
30
|
+
return { kind: 'edited', body: edited };
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Persist a body the server rejected so the user's writing is never lost —
|
|
38
|
+
* printed in the error message as a recoverable path.
|
|
39
|
+
*/
|
|
40
|
+
export function saveRecoveryFile(body) {
|
|
41
|
+
const dir = join(tmpdir(), 'deploylog-recovery');
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
const file = join(dir, `entry-${process.pid}-${Date.now()}.md`);
|
|
44
|
+
writeFileSync(file, body, 'utf8');
|
|
45
|
+
return file;
|
|
46
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { type Entry, type EntryDetail, type PublishStateEntry, type UpdateEntryInput } from './api.js';
|
|
2
|
+
import { type ProjectConfig } from './project-config.js';
|
|
3
|
+
import { type EditorResult } from './editor.js';
|
|
4
|
+
/**
|
|
5
|
+
* Shared deps for the entry-lifecycle commands (publish/unpublish, and the
|
|
6
|
+
* view/edit/delete siblings that follow) — same DI shape as PushDeps.
|
|
7
|
+
*/
|
|
8
|
+
export interface EntryCommandDeps {
|
|
9
|
+
api: {
|
|
10
|
+
listEntries(slug: string): Promise<Entry[]>;
|
|
11
|
+
getEntry(id: string): Promise<EntryDetail>;
|
|
12
|
+
updateEntry(id: string, input: UpdateEntryInput): Promise<EntryDetail>;
|
|
13
|
+
deleteEntry(id: string): Promise<{
|
|
14
|
+
id: string;
|
|
15
|
+
}>;
|
|
16
|
+
setEntryPublished(id: string, published: boolean): Promise<PublishStateEntry>;
|
|
17
|
+
};
|
|
18
|
+
readProjectConfig(): ProjectConfig | null;
|
|
19
|
+
editor(initial: string): EditorResult;
|
|
20
|
+
saveRecovery(body: string): string;
|
|
21
|
+
readFile(path: string): string;
|
|
22
|
+
confirm(question: string): Promise<boolean>;
|
|
23
|
+
isTTY: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface SetPublishedOptions {
|
|
26
|
+
ref: string;
|
|
27
|
+
project?: string;
|
|
28
|
+
publish: boolean;
|
|
29
|
+
}
|
|
30
|
+
export type SetPublishedResult = {
|
|
31
|
+
kind: 'updated';
|
|
32
|
+
entry: PublishStateEntry;
|
|
33
|
+
publish: boolean;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'not-found';
|
|
36
|
+
message: string;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'missing-fields';
|
|
39
|
+
message: string;
|
|
40
|
+
};
|
|
41
|
+
export declare function runSetPublished(opts: SetPublishedOptions, deps?: EntryCommandDeps): Promise<SetPublishedResult>;
|
|
42
|
+
export interface ViewOptions {
|
|
43
|
+
ref: string;
|
|
44
|
+
project?: string;
|
|
45
|
+
}
|
|
46
|
+
export type ViewResult = {
|
|
47
|
+
kind: 'found';
|
|
48
|
+
entry: EntryDetail;
|
|
49
|
+
} | {
|
|
50
|
+
kind: 'not-found';
|
|
51
|
+
message: string;
|
|
52
|
+
} | {
|
|
53
|
+
kind: 'missing-fields';
|
|
54
|
+
message: string;
|
|
55
|
+
};
|
|
56
|
+
export declare function runView(opts: ViewOptions, deps?: EntryCommandDeps): Promise<ViewResult>;
|
|
57
|
+
export interface EditOptions {
|
|
58
|
+
ref: string;
|
|
59
|
+
project?: string;
|
|
60
|
+
title?: string;
|
|
61
|
+
type?: string;
|
|
62
|
+
version?: string;
|
|
63
|
+
body?: string;
|
|
64
|
+
bodyFile?: string;
|
|
65
|
+
}
|
|
66
|
+
export type EditResult = {
|
|
67
|
+
kind: 'updated';
|
|
68
|
+
entry: EntryDetail;
|
|
69
|
+
previousSlug: string;
|
|
70
|
+
} | {
|
|
71
|
+
kind: 'not-found';
|
|
72
|
+
message: string;
|
|
73
|
+
} | {
|
|
74
|
+
kind: 'missing-fields';
|
|
75
|
+
message: string;
|
|
76
|
+
} | {
|
|
77
|
+
kind: 'cancelled';
|
|
78
|
+
message: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: 'unchanged';
|
|
81
|
+
message: string;
|
|
82
|
+
} | {
|
|
83
|
+
kind: 'body-rejected';
|
|
84
|
+
message: string;
|
|
85
|
+
recoveryPath: string;
|
|
86
|
+
};
|
|
87
|
+
export declare function runEdit(opts: EditOptions, deps?: EntryCommandDeps): Promise<EditResult>;
|
|
88
|
+
export interface DeleteOptions {
|
|
89
|
+
ref: string;
|
|
90
|
+
project?: string;
|
|
91
|
+
yes?: boolean;
|
|
92
|
+
}
|
|
93
|
+
export type DeleteResult = {
|
|
94
|
+
kind: 'deleted';
|
|
95
|
+
id: string;
|
|
96
|
+
title: string;
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'cancelled';
|
|
99
|
+
message: string;
|
|
100
|
+
} | {
|
|
101
|
+
kind: 'confirm-required';
|
|
102
|
+
message: string;
|
|
103
|
+
} | {
|
|
104
|
+
kind: 'not-found';
|
|
105
|
+
message: string;
|
|
106
|
+
} | {
|
|
107
|
+
kind: 'missing-fields';
|
|
108
|
+
message: string;
|
|
109
|
+
};
|
|
110
|
+
export declare function runDelete(opts: DeleteOptions, deps?: EntryCommandDeps): Promise<DeleteResult>;
|
|
111
|
+
export declare const defaultEntryCommandDeps: EntryCommandDeps;
|