@spectratools/graphic-designer-cli 0.3.1 → 0.3.2
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/cli.js +65 -136
- package/dist/index.d.ts +156 -2
- package/dist/index.js +65 -136
- package/dist/publish/index.d.ts +38 -9
- package/dist/publish/index.js +62 -130
- package/dist/qa.d.ts +25 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/{spec.schema-BxXBTOn-.d.ts → spec.schema-DhAI-tE8.d.ts} +164 -20
- package/dist/spec.schema.d.ts +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -8,38 +8,8 @@ import { Cli, z as z3 } from "incur";
|
|
|
8
8
|
// src/publish/gist.ts
|
|
9
9
|
import { readFile } from "fs/promises";
|
|
10
10
|
import { basename } from "path";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var DEFAULT_RETRY_POLICY = {
|
|
14
|
-
maxRetries: 3,
|
|
15
|
-
baseMs: 500,
|
|
16
|
-
maxMs: 4e3
|
|
17
|
-
};
|
|
18
|
-
function sleep(ms) {
|
|
19
|
-
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
20
|
-
}
|
|
21
|
-
async function withRetry(operation, policy = DEFAULT_RETRY_POLICY) {
|
|
22
|
-
let attempt = 0;
|
|
23
|
-
let lastError;
|
|
24
|
-
while (attempt <= policy.maxRetries) {
|
|
25
|
-
try {
|
|
26
|
-
const value = await operation();
|
|
27
|
-
return { value, attempts: attempt + 1 };
|
|
28
|
-
} catch (error) {
|
|
29
|
-
lastError = error;
|
|
30
|
-
if (attempt >= policy.maxRetries) {
|
|
31
|
-
break;
|
|
32
|
-
}
|
|
33
|
-
const backoff = Math.min(policy.baseMs * 2 ** attempt, policy.maxMs);
|
|
34
|
-
const jitter = Math.floor(Math.random() * 125);
|
|
35
|
-
await sleep(backoff + jitter);
|
|
36
|
-
attempt += 1;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
throw lastError;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// src/publish/gist.ts
|
|
11
|
+
import { withRetry } from "@spectratools/cli-shared/middleware";
|
|
12
|
+
import { createHttpClient } from "@spectratools/cli-shared/utils";
|
|
43
13
|
function requireGitHubToken(token) {
|
|
44
14
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
45
15
|
if (!resolved) {
|
|
@@ -47,28 +17,21 @@ function requireGitHubToken(token) {
|
|
|
47
17
|
}
|
|
48
18
|
return resolved;
|
|
49
19
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const text = await response.text();
|
|
63
|
-
throw new Error(
|
|
64
|
-
`GitHub Gist API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
return await response.json();
|
|
68
|
-
}, retryPolicy);
|
|
69
|
-
}
|
|
20
|
+
var DEFAULT_RETRY = {
|
|
21
|
+
maxRetries: 3,
|
|
22
|
+
baseMs: 500,
|
|
23
|
+
maxMs: 4e3
|
|
24
|
+
};
|
|
25
|
+
var github = createHttpClient({
|
|
26
|
+
baseUrl: "https://api.github.com",
|
|
27
|
+
defaultHeaders: {
|
|
28
|
+
Accept: "application/vnd.github+json",
|
|
29
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
70
32
|
async function publishToGist(options) {
|
|
71
33
|
const token = requireGitHubToken(options.token);
|
|
34
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY;
|
|
72
35
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
73
36
|
readFile(options.imagePath),
|
|
74
37
|
readFile(options.metadataPath)
|
|
@@ -106,27 +69,27 @@ async function publishToGist(options) {
|
|
|
106
69
|
};
|
|
107
70
|
const endpoint = options.gistId ? `/gists/${options.gistId}` : "/gists";
|
|
108
71
|
const method = options.gistId ? "PATCH" : "POST";
|
|
109
|
-
const published = await
|
|
110
|
-
endpoint,
|
|
111
|
-
{
|
|
72
|
+
const published = await withRetry(
|
|
73
|
+
() => github.request(endpoint, {
|
|
112
74
|
method,
|
|
113
|
-
body:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
75
|
+
body: payload,
|
|
76
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
77
|
+
}),
|
|
78
|
+
retry
|
|
117
79
|
);
|
|
118
80
|
return {
|
|
119
81
|
target: "gist",
|
|
120
|
-
gistId: published.
|
|
121
|
-
htmlUrl: published.
|
|
122
|
-
|
|
123
|
-
files: Object.keys(published.value.files ?? payload.files)
|
|
82
|
+
gistId: published.id,
|
|
83
|
+
htmlUrl: published.html_url,
|
|
84
|
+
files: Object.keys(published.files ?? payload.files)
|
|
124
85
|
};
|
|
125
86
|
}
|
|
126
87
|
|
|
127
88
|
// src/publish/github.ts
|
|
128
89
|
import { readFile as readFile2 } from "fs/promises";
|
|
129
90
|
import { basename as basename2, posix } from "path";
|
|
91
|
+
import { withRetry as withRetry2 } from "@spectratools/cli-shared/middleware";
|
|
92
|
+
import { HttpError, createHttpClient as createHttpClient2 } from "@spectratools/cli-shared/utils";
|
|
130
93
|
function requireGitHubToken2(token) {
|
|
131
94
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
132
95
|
if (!resolved) {
|
|
@@ -134,56 +97,18 @@ function requireGitHubToken2(token) {
|
|
|
134
97
|
}
|
|
135
98
|
return resolved;
|
|
136
99
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
});
|
|
148
|
-
if (!response.ok) {
|
|
149
|
-
const text = await response.text();
|
|
150
|
-
throw new Error(
|
|
151
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
return await response.json();
|
|
155
|
-
}, retryPolicy);
|
|
156
|
-
}
|
|
157
|
-
async function githubJsonMaybe(path, token, retryPolicy) {
|
|
158
|
-
const { value, attempts } = await withRetry(async () => {
|
|
159
|
-
const response = await fetch(`https://api.github.com${path}`, {
|
|
160
|
-
headers: {
|
|
161
|
-
Accept: "application/vnd.github+json",
|
|
162
|
-
Authorization: `Bearer ${token}`,
|
|
163
|
-
"User-Agent": "spectratools-graphic-designer"
|
|
164
|
-
}
|
|
165
|
-
});
|
|
166
|
-
if (response.status === 404) {
|
|
167
|
-
return { found: false };
|
|
168
|
-
}
|
|
169
|
-
if (!response.ok) {
|
|
170
|
-
const text = await response.text();
|
|
171
|
-
throw new Error(
|
|
172
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
const json = await response.json();
|
|
176
|
-
return { found: true, value: json };
|
|
177
|
-
}, retryPolicy);
|
|
178
|
-
if (!value.found) {
|
|
179
|
-
return { found: false, attempts };
|
|
100
|
+
var DEFAULT_RETRY2 = {
|
|
101
|
+
maxRetries: 3,
|
|
102
|
+
baseMs: 500,
|
|
103
|
+
maxMs: 4e3
|
|
104
|
+
};
|
|
105
|
+
var github2 = createHttpClient2({
|
|
106
|
+
baseUrl: "https://api.github.com",
|
|
107
|
+
defaultHeaders: {
|
|
108
|
+
Accept: "application/vnd.github+json",
|
|
109
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
180
110
|
}
|
|
181
|
-
|
|
182
|
-
found: true,
|
|
183
|
-
value: value.value,
|
|
184
|
-
attempts
|
|
185
|
-
};
|
|
186
|
-
}
|
|
111
|
+
});
|
|
187
112
|
function parseRepo(repo) {
|
|
188
113
|
const [owner, name] = repo.split("/");
|
|
189
114
|
if (!owner || !name) {
|
|
@@ -199,9 +124,25 @@ function normalizePath(pathPrefix, filename) {
|
|
|
199
124
|
const trimmed = (pathPrefix ?? "artifacts").replace(/^\/+|\/+$/gu, "");
|
|
200
125
|
return posix.join(trimmed, filename);
|
|
201
126
|
}
|
|
127
|
+
async function githubJsonMaybe(path, token, retry) {
|
|
128
|
+
return withRetry2(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const value = await github2.request(path, {
|
|
131
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
132
|
+
});
|
|
133
|
+
return { found: true, value };
|
|
134
|
+
} catch (err) {
|
|
135
|
+
if (err instanceof HttpError && err.status === 404) {
|
|
136
|
+
return { found: false };
|
|
137
|
+
}
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}, retry);
|
|
141
|
+
}
|
|
202
142
|
async function publishToGitHub(options) {
|
|
203
143
|
const token = requireGitHubToken2(options.token);
|
|
204
144
|
const branch = options.branch ?? "main";
|
|
145
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY2;
|
|
205
146
|
const commitMessage = options.commitMessage ?? "chore(graphic-designer): publish deterministic artifacts";
|
|
206
147
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
207
148
|
readFile2(options.imagePath),
|
|
@@ -219,16 +160,10 @@ async function publishToGitHub(options) {
|
|
|
219
160
|
content: metadataBuffer.toString("base64")
|
|
220
161
|
}
|
|
221
162
|
];
|
|
222
|
-
let totalAttempts = 0;
|
|
223
163
|
const files = [];
|
|
224
164
|
for (const upload of uploads) {
|
|
225
165
|
const existingPath = `${toApiContentPath(options.repo, upload.destination)}?ref=${encodeURIComponent(branch)}`;
|
|
226
|
-
const existing = await githubJsonMaybe(
|
|
227
|
-
existingPath,
|
|
228
|
-
token,
|
|
229
|
-
options.retryPolicy
|
|
230
|
-
);
|
|
231
|
-
totalAttempts += existing.attempts;
|
|
166
|
+
const existing = await githubJsonMaybe(existingPath, token, retry);
|
|
232
167
|
const body = {
|
|
233
168
|
message: `${commitMessage} (${basename2(upload.sourcePath)})`,
|
|
234
169
|
content: upload.content,
|
|
@@ -236,27 +171,24 @@ async function publishToGitHub(options) {
|
|
|
236
171
|
sha: existing.value?.sha
|
|
237
172
|
};
|
|
238
173
|
const putPath = toApiContentPath(options.repo, upload.destination);
|
|
239
|
-
const published = await
|
|
240
|
-
putPath,
|
|
241
|
-
{
|
|
174
|
+
const published = await withRetry2(
|
|
175
|
+
() => github2.request(putPath, {
|
|
242
176
|
method: "PUT",
|
|
243
|
-
body
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
177
|
+
body,
|
|
178
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
179
|
+
}),
|
|
180
|
+
retry
|
|
247
181
|
);
|
|
248
|
-
totalAttempts += published.attempts;
|
|
249
182
|
files.push({
|
|
250
183
|
path: upload.destination,
|
|
251
|
-
...published.
|
|
252
|
-
...published.
|
|
184
|
+
...published.content?.sha ? { sha: published.content.sha } : {},
|
|
185
|
+
...published.content?.html_url ? { htmlUrl: published.content.html_url } : {}
|
|
253
186
|
});
|
|
254
187
|
}
|
|
255
188
|
return {
|
|
256
189
|
target: "github",
|
|
257
190
|
repo: options.repo,
|
|
258
191
|
branch,
|
|
259
|
-
attempts: totalAttempts,
|
|
260
192
|
files
|
|
261
193
|
};
|
|
262
194
|
}
|
|
@@ -4827,7 +4759,6 @@ cli.command("publish", {
|
|
|
4827
4759
|
issueCount: z3.number()
|
|
4828
4760
|
}),
|
|
4829
4761
|
publish: z3.object({
|
|
4830
|
-
attempts: z3.number(),
|
|
4831
4762
|
summary: z3.string(),
|
|
4832
4763
|
url: z3.string().optional()
|
|
4833
4764
|
})
|
|
@@ -4880,7 +4811,6 @@ cli.command("publish", {
|
|
|
4880
4811
|
issueCount: qa.issues.length
|
|
4881
4812
|
},
|
|
4882
4813
|
publish: {
|
|
4883
|
-
attempts: gist.attempts,
|
|
4884
4814
|
summary: `Published ${gist.files.length} files to gist ${gist.gistId}.`,
|
|
4885
4815
|
url: gist.htmlUrl
|
|
4886
4816
|
}
|
|
@@ -4893,7 +4823,7 @@ cli.command("publish", {
|
|
|
4893
4823
|
retryable: false
|
|
4894
4824
|
});
|
|
4895
4825
|
}
|
|
4896
|
-
const
|
|
4826
|
+
const github3 = await publishToGitHub({
|
|
4897
4827
|
imagePath,
|
|
4898
4828
|
metadataPath,
|
|
4899
4829
|
repo: c.options.repo,
|
|
@@ -4901,7 +4831,7 @@ cli.command("publish", {
|
|
|
4901
4831
|
...c.options.pathPrefix ? { pathPrefix: c.options.pathPrefix } : {},
|
|
4902
4832
|
...c.options.description ? { commitMessage: c.options.description } : {}
|
|
4903
4833
|
});
|
|
4904
|
-
const url =
|
|
4834
|
+
const url = github3.files.find((file) => file.htmlUrl)?.htmlUrl;
|
|
4905
4835
|
return c.ok({
|
|
4906
4836
|
target: "github",
|
|
4907
4837
|
qa: {
|
|
@@ -4909,8 +4839,7 @@ cli.command("publish", {
|
|
|
4909
4839
|
issueCount: qa.issues.length
|
|
4910
4840
|
},
|
|
4911
4841
|
publish: {
|
|
4912
|
-
|
|
4913
|
-
summary: `Published ${github.files.length} files to ${github.repo}@${github.branch}.`,
|
|
4842
|
+
summary: `Published ${github3.files.length} files to ${github3.repo}@${github3.branch}.`,
|
|
4914
4843
|
url
|
|
4915
4844
|
}
|
|
4916
4845
|
});
|
package/dist/publish/index.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
maxRetries: number;
|
|
3
|
-
baseMs: number;
|
|
4
|
-
maxMs: number;
|
|
5
|
-
}
|
|
1
|
+
import { RetryOptions } from '@spectratools/cli-shared/middleware';
|
|
6
2
|
|
|
7
3
|
type GitHubPublishOptions = {
|
|
8
4
|
imagePath: string;
|
|
@@ -12,19 +8,37 @@ type GitHubPublishOptions = {
|
|
|
12
8
|
branch?: string;
|
|
13
9
|
token?: string;
|
|
14
10
|
commitMessage?: string;
|
|
15
|
-
retryPolicy?:
|
|
11
|
+
retryPolicy?: RetryOptions;
|
|
16
12
|
};
|
|
17
13
|
type GitHubPublishResult = {
|
|
18
14
|
target: 'github';
|
|
19
15
|
repo: string;
|
|
20
16
|
branch: string;
|
|
21
|
-
attempts: number;
|
|
22
17
|
files: Array<{
|
|
23
18
|
path: string;
|
|
24
19
|
htmlUrl?: string;
|
|
25
20
|
sha?: string;
|
|
26
21
|
}>;
|
|
27
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* Publish rendered design artifacts to a GitHub repository.
|
|
25
|
+
*
|
|
26
|
+
* Uploads the PNG image and sidecar `.meta.json` metadata to the specified
|
|
27
|
+
* repository via the GitHub Contents API. Files are placed under
|
|
28
|
+
* {@link GitHubPublishOptions.pathPrefix} (defaults to `"artifacts"`). If a
|
|
29
|
+
* file already exists at the destination it is updated in-place (its SHA is
|
|
30
|
+
* fetched first for the update).
|
|
31
|
+
*
|
|
32
|
+
* Requires a GitHub token — either passed via
|
|
33
|
+
* {@link GitHubPublishOptions.token} or resolved from the `GITHUB_TOKEN`
|
|
34
|
+
* environment variable.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Publish configuration including file paths, target repo,
|
|
37
|
+
* branch, commit message, and optional retry policy.
|
|
38
|
+
* @returns A {@link GitHubPublishResult} with the repo, branch, and per-file
|
|
39
|
+
* metadata (path, SHA, HTML URL).
|
|
40
|
+
* @throws When no GitHub token is available or the API request fails.
|
|
41
|
+
*/
|
|
28
42
|
declare function publishToGitHub(options: GitHubPublishOptions): Promise<GitHubPublishResult>;
|
|
29
43
|
|
|
30
44
|
type GistPublishOptions = {
|
|
@@ -35,15 +49,30 @@ type GistPublishOptions = {
|
|
|
35
49
|
filenamePrefix?: string;
|
|
36
50
|
public?: boolean;
|
|
37
51
|
token?: string;
|
|
38
|
-
retryPolicy?:
|
|
52
|
+
retryPolicy?: RetryOptions;
|
|
39
53
|
};
|
|
40
54
|
type GistPublishResult = {
|
|
41
55
|
target: 'gist';
|
|
42
56
|
gistId: string;
|
|
43
57
|
htmlUrl: string;
|
|
44
|
-
attempts: number;
|
|
45
58
|
files: string[];
|
|
46
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Publish rendered design artifacts to a GitHub Gist.
|
|
62
|
+
*
|
|
63
|
+
* Creates (or updates, when {@link GistPublishOptions.gistId} is set) a gist
|
|
64
|
+
* containing the PNG image as a base64 text file, the sidecar `.meta.json`
|
|
65
|
+
* metadata, and a Markdown README with an embedded preview.
|
|
66
|
+
*
|
|
67
|
+
* Requires a GitHub token — either passed via {@link GistPublishOptions.token}
|
|
68
|
+
* or resolved from the `GITHUB_TOKEN` environment variable.
|
|
69
|
+
*
|
|
70
|
+
* @param options - Publish configuration including file paths, gist settings,
|
|
71
|
+
* and optional retry policy.
|
|
72
|
+
* @returns A {@link GistPublishResult} with the gist ID, URL, attempt count,
|
|
73
|
+
* and list of created file names.
|
|
74
|
+
* @throws When no GitHub token is available or the API request fails.
|
|
75
|
+
*/
|
|
47
76
|
declare function publishToGist(options: GistPublishOptions): Promise<GistPublishResult>;
|
|
48
77
|
|
|
49
78
|
export { type GistPublishOptions, type GistPublishResult, type GitHubPublishOptions, type GitHubPublishResult, publishToGist, publishToGitHub };
|
package/dist/publish/index.js
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
1
|
// src/publish/github.ts
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import { basename, posix } from "path";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
var DEFAULT_RETRY_POLICY = {
|
|
7
|
-
maxRetries: 3,
|
|
8
|
-
baseMs: 500,
|
|
9
|
-
maxMs: 4e3
|
|
10
|
-
};
|
|
11
|
-
function sleep(ms) {
|
|
12
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
-
}
|
|
14
|
-
async function withRetry(operation, policy = DEFAULT_RETRY_POLICY) {
|
|
15
|
-
let attempt = 0;
|
|
16
|
-
let lastError;
|
|
17
|
-
while (attempt <= policy.maxRetries) {
|
|
18
|
-
try {
|
|
19
|
-
const value = await operation();
|
|
20
|
-
return { value, attempts: attempt + 1 };
|
|
21
|
-
} catch (error) {
|
|
22
|
-
lastError = error;
|
|
23
|
-
if (attempt >= policy.maxRetries) {
|
|
24
|
-
break;
|
|
25
|
-
}
|
|
26
|
-
const backoff = Math.min(policy.baseMs * 2 ** attempt, policy.maxMs);
|
|
27
|
-
const jitter = Math.floor(Math.random() * 125);
|
|
28
|
-
await sleep(backoff + jitter);
|
|
29
|
-
attempt += 1;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
throw lastError;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// src/publish/github.ts
|
|
4
|
+
import { withRetry } from "@spectratools/cli-shared/middleware";
|
|
5
|
+
import { HttpError, createHttpClient } from "@spectratools/cli-shared/utils";
|
|
36
6
|
function requireGitHubToken(token) {
|
|
37
7
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
38
8
|
if (!resolved) {
|
|
@@ -40,56 +10,18 @@ function requireGitHubToken(token) {
|
|
|
40
10
|
}
|
|
41
11
|
return resolved;
|
|
42
12
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
});
|
|
54
|
-
if (!response.ok) {
|
|
55
|
-
const text = await response.text();
|
|
56
|
-
throw new Error(
|
|
57
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
return await response.json();
|
|
61
|
-
}, retryPolicy);
|
|
62
|
-
}
|
|
63
|
-
async function githubJsonMaybe(path, token, retryPolicy) {
|
|
64
|
-
const { value, attempts } = await withRetry(async () => {
|
|
65
|
-
const response = await fetch(`https://api.github.com${path}`, {
|
|
66
|
-
headers: {
|
|
67
|
-
Accept: "application/vnd.github+json",
|
|
68
|
-
Authorization: `Bearer ${token}`,
|
|
69
|
-
"User-Agent": "spectratools-graphic-designer"
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
if (response.status === 404) {
|
|
73
|
-
return { found: false };
|
|
74
|
-
}
|
|
75
|
-
if (!response.ok) {
|
|
76
|
-
const text = await response.text();
|
|
77
|
-
throw new Error(
|
|
78
|
-
`GitHub API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
const json = await response.json();
|
|
82
|
-
return { found: true, value: json };
|
|
83
|
-
}, retryPolicy);
|
|
84
|
-
if (!value.found) {
|
|
85
|
-
return { found: false, attempts };
|
|
13
|
+
var DEFAULT_RETRY = {
|
|
14
|
+
maxRetries: 3,
|
|
15
|
+
baseMs: 500,
|
|
16
|
+
maxMs: 4e3
|
|
17
|
+
};
|
|
18
|
+
var github = createHttpClient({
|
|
19
|
+
baseUrl: "https://api.github.com",
|
|
20
|
+
defaultHeaders: {
|
|
21
|
+
Accept: "application/vnd.github+json",
|
|
22
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
86
23
|
}
|
|
87
|
-
|
|
88
|
-
found: true,
|
|
89
|
-
value: value.value,
|
|
90
|
-
attempts
|
|
91
|
-
};
|
|
92
|
-
}
|
|
24
|
+
});
|
|
93
25
|
function parseRepo(repo) {
|
|
94
26
|
const [owner, name] = repo.split("/");
|
|
95
27
|
if (!owner || !name) {
|
|
@@ -105,9 +37,25 @@ function normalizePath(pathPrefix, filename) {
|
|
|
105
37
|
const trimmed = (pathPrefix ?? "artifacts").replace(/^\/+|\/+$/gu, "");
|
|
106
38
|
return posix.join(trimmed, filename);
|
|
107
39
|
}
|
|
40
|
+
async function githubJsonMaybe(path, token, retry) {
|
|
41
|
+
return withRetry(async () => {
|
|
42
|
+
try {
|
|
43
|
+
const value = await github.request(path, {
|
|
44
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
45
|
+
});
|
|
46
|
+
return { found: true, value };
|
|
47
|
+
} catch (err) {
|
|
48
|
+
if (err instanceof HttpError && err.status === 404) {
|
|
49
|
+
return { found: false };
|
|
50
|
+
}
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
}, retry);
|
|
54
|
+
}
|
|
108
55
|
async function publishToGitHub(options) {
|
|
109
56
|
const token = requireGitHubToken(options.token);
|
|
110
57
|
const branch = options.branch ?? "main";
|
|
58
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY;
|
|
111
59
|
const commitMessage = options.commitMessage ?? "chore(graphic-designer): publish deterministic artifacts";
|
|
112
60
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
113
61
|
readFile(options.imagePath),
|
|
@@ -125,16 +73,10 @@ async function publishToGitHub(options) {
|
|
|
125
73
|
content: metadataBuffer.toString("base64")
|
|
126
74
|
}
|
|
127
75
|
];
|
|
128
|
-
let totalAttempts = 0;
|
|
129
76
|
const files = [];
|
|
130
77
|
for (const upload of uploads) {
|
|
131
78
|
const existingPath = `${toApiContentPath(options.repo, upload.destination)}?ref=${encodeURIComponent(branch)}`;
|
|
132
|
-
const existing = await githubJsonMaybe(
|
|
133
|
-
existingPath,
|
|
134
|
-
token,
|
|
135
|
-
options.retryPolicy
|
|
136
|
-
);
|
|
137
|
-
totalAttempts += existing.attempts;
|
|
79
|
+
const existing = await githubJsonMaybe(existingPath, token, retry);
|
|
138
80
|
const body = {
|
|
139
81
|
message: `${commitMessage} (${basename(upload.sourcePath)})`,
|
|
140
82
|
content: upload.content,
|
|
@@ -142,27 +84,24 @@ async function publishToGitHub(options) {
|
|
|
142
84
|
sha: existing.value?.sha
|
|
143
85
|
};
|
|
144
86
|
const putPath = toApiContentPath(options.repo, upload.destination);
|
|
145
|
-
const published = await
|
|
146
|
-
putPath,
|
|
147
|
-
{
|
|
87
|
+
const published = await withRetry(
|
|
88
|
+
() => github.request(putPath, {
|
|
148
89
|
method: "PUT",
|
|
149
|
-
body
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
90
|
+
body,
|
|
91
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
92
|
+
}),
|
|
93
|
+
retry
|
|
153
94
|
);
|
|
154
|
-
totalAttempts += published.attempts;
|
|
155
95
|
files.push({
|
|
156
96
|
path: upload.destination,
|
|
157
|
-
...published.
|
|
158
|
-
...published.
|
|
97
|
+
...published.content?.sha ? { sha: published.content.sha } : {},
|
|
98
|
+
...published.content?.html_url ? { htmlUrl: published.content.html_url } : {}
|
|
159
99
|
});
|
|
160
100
|
}
|
|
161
101
|
return {
|
|
162
102
|
target: "github",
|
|
163
103
|
repo: options.repo,
|
|
164
104
|
branch,
|
|
165
|
-
attempts: totalAttempts,
|
|
166
105
|
files
|
|
167
106
|
};
|
|
168
107
|
}
|
|
@@ -170,6 +109,8 @@ async function publishToGitHub(options) {
|
|
|
170
109
|
// src/publish/gist.ts
|
|
171
110
|
import { readFile as readFile2 } from "fs/promises";
|
|
172
111
|
import { basename as basename2 } from "path";
|
|
112
|
+
import { withRetry as withRetry2 } from "@spectratools/cli-shared/middleware";
|
|
113
|
+
import { createHttpClient as createHttpClient2 } from "@spectratools/cli-shared/utils";
|
|
173
114
|
function requireGitHubToken2(token) {
|
|
174
115
|
const resolved = token ?? process.env.GITHUB_TOKEN;
|
|
175
116
|
if (!resolved) {
|
|
@@ -177,28 +118,21 @@ function requireGitHubToken2(token) {
|
|
|
177
118
|
}
|
|
178
119
|
return resolved;
|
|
179
120
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
const text = await response.text();
|
|
193
|
-
throw new Error(
|
|
194
|
-
`GitHub Gist API ${path} failed (${response.status}): ${text || response.statusText}`
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
return await response.json();
|
|
198
|
-
}, retryPolicy);
|
|
199
|
-
}
|
|
121
|
+
var DEFAULT_RETRY2 = {
|
|
122
|
+
maxRetries: 3,
|
|
123
|
+
baseMs: 500,
|
|
124
|
+
maxMs: 4e3
|
|
125
|
+
};
|
|
126
|
+
var github2 = createHttpClient2({
|
|
127
|
+
baseUrl: "https://api.github.com",
|
|
128
|
+
defaultHeaders: {
|
|
129
|
+
Accept: "application/vnd.github+json",
|
|
130
|
+
"User-Agent": "spectratools-graphic-designer"
|
|
131
|
+
}
|
|
132
|
+
});
|
|
200
133
|
async function publishToGist(options) {
|
|
201
134
|
const token = requireGitHubToken2(options.token);
|
|
135
|
+
const retry = options.retryPolicy ?? DEFAULT_RETRY2;
|
|
202
136
|
const [imageBuffer, metadataBuffer] = await Promise.all([
|
|
203
137
|
readFile2(options.imagePath),
|
|
204
138
|
readFile2(options.metadataPath)
|
|
@@ -236,21 +170,19 @@ async function publishToGist(options) {
|
|
|
236
170
|
};
|
|
237
171
|
const endpoint = options.gistId ? `/gists/${options.gistId}` : "/gists";
|
|
238
172
|
const method = options.gistId ? "PATCH" : "POST";
|
|
239
|
-
const published = await
|
|
240
|
-
endpoint,
|
|
241
|
-
{
|
|
173
|
+
const published = await withRetry2(
|
|
174
|
+
() => github2.request(endpoint, {
|
|
242
175
|
method,
|
|
243
|
-
body:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
176
|
+
body: payload,
|
|
177
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
178
|
+
}),
|
|
179
|
+
retry
|
|
247
180
|
);
|
|
248
181
|
return {
|
|
249
182
|
target: "gist",
|
|
250
|
-
gistId: published.
|
|
251
|
-
htmlUrl: published.
|
|
252
|
-
|
|
253
|
-
files: Object.keys(published.value.files ?? payload.files)
|
|
183
|
+
gistId: published.id,
|
|
184
|
+
htmlUrl: published.html_url,
|
|
185
|
+
files: Object.keys(published.files ?? payload.files)
|
|
254
186
|
};
|
|
255
187
|
}
|
|
256
188
|
export {
|