tapline-host 1.3.1 → 1.5.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/dist/actionlog.d.ts +28 -1
- package/dist/actionlog.js +2 -2
- package/dist/actionlog.js.map +1 -1
- package/dist/audit.d.ts +2 -0
- package/dist/audit.js +1 -1
- package/dist/audit.js.map +1 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/format.d.ts +6 -0
- package/dist/format.js +69 -20
- package/dist/format.js.map +1 -1
- package/dist/images.d.ts +27 -0
- package/dist/images.js +50 -0
- package/dist/images.js.map +1 -1
- package/dist/init.js +83 -1
- package/dist/init.js.map +1 -1
- package/dist/journey.d.ts +259 -0
- package/dist/journey.js +336 -0
- package/dist/journey.js.map +1 -0
- package/dist/journeyexport.d.ts +58 -0
- package/dist/journeyexport.js +318 -0
- package/dist/journeyexport.js.map +1 -0
- package/dist/journeygif.d.ts +62 -0
- package/dist/journeygif.js +192 -0
- package/dist/journeygif.js.map +1 -0
- package/dist/journeyreport.d.ts +202 -0
- package/dist/journeyreport.js +664 -0
- package/dist/journeyreport.js.map +1 -0
- package/dist/mcp.d.ts +1 -0
- package/dist/mcp.js +72 -32
- package/dist/mcp.js.map +1 -1
- package/dist/media.d.ts +86 -0
- package/dist/media.js +244 -0
- package/dist/media.js.map +1 -0
- package/dist/protocol.d.ts +6 -1
- package/dist/protocol.js +9 -1
- package/dist/protocol.js.map +1 -1
- package/dist/report.d.ts +55 -0
- package/dist/report.js +298 -0
- package/dist/report.js.map +1 -0
- package/dist/run.js +1 -1
- package/dist/run.js.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.js +8 -0
- package/dist/server.js.map +1 -1
- package/dist/tool-text-data.d.ts +5 -1
- package/dist/tool-text-data.js +5 -1
- package/dist/tool-text-data.js.map +1 -1
- package/dist/tools.d.ts +262 -0
- package/dist/tools.js +1337 -35
- package/dist/tools.js.map +1 -1
- package/dist/uxcheck.d.ts +102 -0
- package/dist/uxcheck.js +128 -0
- package/dist/uxcheck.js.map +1 -0
- package/package.json +2 -1
- package/skills/tapline/SKILL.md +40 -1
- package/skills/tapline/references/personas.md +45 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
/** The real runner: the user's own `gh` and `git`. Only the host's `mcp` command builds one; tests never do. */
|
|
6
|
+
export const realRunner = (command, args, options) => new Promise((resolve) => {
|
|
7
|
+
execFile(command, args, { ...(options?.cwd !== undefined ? { cwd: options.cwd } : {}), timeout: 120_000, maxBuffer: 16 * 1024 * 1024, windowsHide: true, env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GH_PROMPT_DISABLED: "1" } }, (error, stdout, stderr) => {
|
|
8
|
+
if (error === null)
|
|
9
|
+
return resolve({ code: 0, stdout, stderr });
|
|
10
|
+
const code = error.code;
|
|
11
|
+
if (code === "ENOENT")
|
|
12
|
+
return resolve({ code: null, stdout, stderr: error.message, missing: true });
|
|
13
|
+
resolve({ code: typeof code === "number" ? code : 1, stdout, stderr: stderr !== "" ? stderr : error.message });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
/** owner/name as GitHub allows them: no leading dash or dot, no `..` (1.5 review, finding 7). */
|
|
17
|
+
const REPO = /^[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9_][A-Za-z0-9._-]*$/;
|
|
18
|
+
const QA_BRANCH = "qa-reports";
|
|
19
|
+
/** Tokens never reach a message. */
|
|
20
|
+
function plain(text) {
|
|
21
|
+
const line = text.split(/\r?\n/).map((l) => l.trim()).find((l) => l !== "") ?? "";
|
|
22
|
+
return line.replace(/gh[pousr]_[A-Za-z0-9]{20,}/g, "***").replace(/(https?:\/\/)[^/@\s]+@/g, "$1").slice(0, 240);
|
|
23
|
+
}
|
|
24
|
+
function explain(result, what, repo) {
|
|
25
|
+
if (result.missing === true) {
|
|
26
|
+
return "the GitHub CLI (gh) isn't installed or isn't on the PATH; install it from https://cli.github.com and sign in with `gh auth login`";
|
|
27
|
+
}
|
|
28
|
+
const said = `${result.stderr}\n${result.stdout}`;
|
|
29
|
+
if (/not logged in|gh auth login|authentication required|bad credentials|requires authentication/i.test(said)) {
|
|
30
|
+
return "gh isn't signed in; run `gh auth login`, then export again";
|
|
31
|
+
}
|
|
32
|
+
if (what.startsWith("git push") && /permission|403|denied|protected branch|not allowed|forbidden/i.test(said)) {
|
|
33
|
+
return `${what} was refused: you may have no write access to ${repo}, or the branch is protected`;
|
|
34
|
+
}
|
|
35
|
+
if (/could not resolve|404|not found|no such/i.test(said)) {
|
|
36
|
+
return `GitHub says ${repo} (or that pull request) wasn't found, or you have no access to it`;
|
|
37
|
+
}
|
|
38
|
+
if (/permission|403|forbidden/i.test(said))
|
|
39
|
+
return `you have no access to ${repo} (${plain(said)})`;
|
|
40
|
+
const reason = plain(said);
|
|
41
|
+
return `${what} failed${reason !== "" ? `: ${reason}` : ""}`;
|
|
42
|
+
}
|
|
43
|
+
/** `https://github.com/owner/name/raw/branch/path`: opens for people signed in to GitHub, private repositories too. */
|
|
44
|
+
function rawUrl(repo, branch, path) {
|
|
45
|
+
const encode = (value) => value.split("/").map(encodeURIComponent).join("/");
|
|
46
|
+
return `https://github.com/${repo}/raw/${encode(branch)}/${encode(path)}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* App text made inert in a GitHub comment: no @mention that pings, no picture, link or HTML it can add (finding 7).
|
|
50
|
+
* Markdown's own escapes, a zero-width space after `@`, and links broken so they don't render.
|
|
51
|
+
*/
|
|
52
|
+
export function inert(text) {
|
|
53
|
+
return text
|
|
54
|
+
.replace(/[\\`*_[\]<>|!]/g, (c) => `\\${c}`)
|
|
55
|
+
.replace(/@/g, "@\u200b")
|
|
56
|
+
.replace(/(https?):\/\//gi, "$1\u200b://")
|
|
57
|
+
// GitHub also turns a bare `www.` word into a link.
|
|
58
|
+
.replace(/\bwww\./gi, (w) => `${w.slice(0, 3)}\u200b.`);
|
|
59
|
+
}
|
|
60
|
+
function bodyOf(input, pictureUrl, reportUrl, all) {
|
|
61
|
+
const lines = [inert(input.summary), "", input.findingsLine, ""];
|
|
62
|
+
if (input.headline !== undefined)
|
|
63
|
+
lines.push(`})`, "");
|
|
64
|
+
const shown = all ? input.findings : input.findings.slice(0, 8);
|
|
65
|
+
shown.forEach((f, i) => {
|
|
66
|
+
lines.push(`${i + 1}. **${f.severity}** ${f.kind}: ${inert(f.title)}`);
|
|
67
|
+
if (all && f.shot !== undefined)
|
|
68
|
+
lines.push("", `})`, "");
|
|
69
|
+
});
|
|
70
|
+
if (!all && input.findings.length > shown.length)
|
|
71
|
+
lines.push(`(+${input.findings.length - shown.length} more in the report.)`);
|
|
72
|
+
lines.push("", `Full report: [report.md](${reportUrl}) (also report.html in the same folder).`, "", "Written by Tapline.");
|
|
73
|
+
return lines.join("\n");
|
|
74
|
+
}
|
|
75
|
+
function validTarget(target) {
|
|
76
|
+
if (!REPO.test(target.repo) || target.repo.includes(".."))
|
|
77
|
+
return `export repo ${JSON.stringify(target.repo)} isn't owner/name`;
|
|
78
|
+
if (target.to === "pr" && (target.pr === undefined || !Number.isInteger(target.pr) || target.pr < 1))
|
|
79
|
+
return "export to a pull request needs its number";
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function safeBranch(branch) {
|
|
83
|
+
return branch !== "" && !branch.startsWith("-") && !/[\s~^:?*[\\]|\.\.|@\{/.test(branch);
|
|
84
|
+
}
|
|
85
|
+
function inside(root, path) {
|
|
86
|
+
return path === root || path.startsWith(root + sep);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Makes the folders from `base` down to `dir` one at a time, refusing any that is a link (a symlink or a junction):
|
|
90
|
+
* a repository can hold a `reports` link that points out of the clone (Codex review of 1.5.0).
|
|
91
|
+
*/
|
|
92
|
+
function makeFoldersInside(base, dir) {
|
|
93
|
+
const steps = relative(base, dir).split(sep).filter((step) => step !== "");
|
|
94
|
+
let at = base;
|
|
95
|
+
for (const step of steps) {
|
|
96
|
+
at = join(at, step);
|
|
97
|
+
if (existsSync(at) || isLink(at)) {
|
|
98
|
+
if (isLink(at) || !lstatSync(at).isDirectory())
|
|
99
|
+
throw new Error(`the repository has a link or a file where the report goes (${step}); nothing was exported`);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
mkdirSync(at);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function isLink(path) {
|
|
107
|
+
try {
|
|
108
|
+
return lstatSync(path).isSymbolicLink();
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The files, and only them, into `into` (made if need be), keeping their folders. Both ends are checked: a source is
|
|
116
|
+
* a plain file inside the report folder, and every folder and file written is inside the clone, with no link on the way.
|
|
117
|
+
*/
|
|
118
|
+
function copyReport(folder, clone, into, files) {
|
|
119
|
+
const root = realpathSync(resolve(folder));
|
|
120
|
+
const cloneRoot = realpathSync(resolve(clone));
|
|
121
|
+
const target = resolve(into);
|
|
122
|
+
if (!inside(resolve(clone), target))
|
|
123
|
+
throw new Error("the report would go outside the clone; nothing was exported");
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
// Never outside the report folder, whatever a list holds (1.5 review, finding 7).
|
|
126
|
+
const from = resolve(root, file);
|
|
127
|
+
if (isAbsolute(file) || file.split(/[\\/]/).includes("..") || !inside(root, from)) {
|
|
128
|
+
throw new Error(`the report lists a file outside its folder (${JSON.stringify(file)}); nothing was exported`);
|
|
129
|
+
}
|
|
130
|
+
if (isLink(from) || !inside(root, realpathSync(from))) {
|
|
131
|
+
throw new Error(`the report lists a link (${JSON.stringify(file)}); nothing was exported`);
|
|
132
|
+
}
|
|
133
|
+
const to = join(target, file);
|
|
134
|
+
makeFoldersInside(resolve(clone), dirname(to));
|
|
135
|
+
if (!inside(cloneRoot, realpathSync(dirname(to))))
|
|
136
|
+
throw new Error("the report would go outside the clone; nothing was exported");
|
|
137
|
+
if (isLink(to))
|
|
138
|
+
throw new Error(`the repository has a link where a report file goes (${JSON.stringify(file)}); nothing was exported`);
|
|
139
|
+
copyFileSync(from, to);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** A public repository is refused unless the agent said `public: true`; what can't be read is refused too. */
|
|
143
|
+
async function visibilityRefusal(run, repo, allowed) {
|
|
144
|
+
const seen = await run("gh", ["repo", "view", repo, "--json", "visibility"]);
|
|
145
|
+
if (seen.code !== 0)
|
|
146
|
+
return explain(seen, "gh repo view", repo);
|
|
147
|
+
let visibility = "";
|
|
148
|
+
try {
|
|
149
|
+
const parsed = JSON.parse(seen.stdout);
|
|
150
|
+
if (typeof parsed === "object" && parsed !== null && "visibility" in parsed && typeof parsed.visibility === "string")
|
|
151
|
+
visibility = parsed.visibility.toUpperCase();
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
// refused below
|
|
155
|
+
}
|
|
156
|
+
if (visibility === "")
|
|
157
|
+
return `Tapline couldn't tell whether ${repo} is public, so it wrote nothing`;
|
|
158
|
+
if (visibility === "PUBLIC" && !allowed)
|
|
159
|
+
return publicWords(repo);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
function publicWords(repo) {
|
|
163
|
+
return `${repo} is a public repository, so the report (screenshots of the app included) would be visible to everyone; export again with public: true if your user wants that`;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Whether the export's repository is public and the agent didn't say `public: true`, asked before the journey ends so a
|
|
167
|
+
* refused public export can be tried again. Everything else (an invalid target, gh missing or not signed in, an answer
|
|
168
|
+
* that can't be read) is null here: the export itself says so, after the journey has ended and its report is written.
|
|
169
|
+
*/
|
|
170
|
+
export async function exportRefusal(target, run) {
|
|
171
|
+
if (target.confirm !== true || target.public === true || validTarget(target) !== null)
|
|
172
|
+
return null;
|
|
173
|
+
try {
|
|
174
|
+
const seen = await run("gh", ["repo", "view", target.repo, "--json", "visibility"]);
|
|
175
|
+
if (seen.code !== 0)
|
|
176
|
+
return null;
|
|
177
|
+
const parsed = JSON.parse(seen.stdout);
|
|
178
|
+
const visibility = typeof parsed === "object" && parsed !== null && "visibility" in parsed && typeof parsed.visibility === "string" ? parsed.visibility.toUpperCase() : "";
|
|
179
|
+
return visibility === "PUBLIC" ? publicWords(target.repo) : null;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Puts the report on GitHub. `{to: "pr"}`: into the pull request's own branch under `.tapline/reports/<name>/`, with a
|
|
187
|
+
* comment. `{to: "issue"}`: to the `qa-reports` branch (made as an orphan when missing), with an issue linking it.
|
|
188
|
+
*/
|
|
189
|
+
export async function exportReport(input, run, makeTemp = () => mkdtempSync(join(tmpdir(), "tapline-export-"))) {
|
|
190
|
+
if (input.target.confirm !== true)
|
|
191
|
+
return { ok: false, message: "export needs confirm: true (your user asked for it)" };
|
|
192
|
+
const bad = validTarget(input.target);
|
|
193
|
+
if (bad !== null)
|
|
194
|
+
return { ok: false, message: bad };
|
|
195
|
+
const repo = input.target.repo;
|
|
196
|
+
const tmp = makeTemp();
|
|
197
|
+
const clone = join(tmp, "repo");
|
|
198
|
+
try {
|
|
199
|
+
const auth = await run("gh", ["auth", "status"]);
|
|
200
|
+
if (auth.code !== 0)
|
|
201
|
+
return { ok: false, message: explain(auth, "gh auth status", repo) };
|
|
202
|
+
return input.target.to === "pr" ? await toPullRequest(input, run, repo, input.target.pr ?? 0, tmp, clone) : await toIssue(input, run, repo, tmp, clone);
|
|
203
|
+
}
|
|
204
|
+
catch (error) {
|
|
205
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
// A temporary folder that won't go away must not turn a done export into a failure (finding 12).
|
|
209
|
+
try {
|
|
210
|
+
rmSync(tmp, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// left for the system to clean up
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function toPullRequest(input, run, repo, pr, tmp, clone) {
|
|
218
|
+
const view = await run("gh", ["pr", "view", String(pr), "--repo", repo, "--json", "headRefName,headRepository,headRepositoryOwner,isCrossRepository,url"]);
|
|
219
|
+
if (view.code !== 0)
|
|
220
|
+
return { ok: false, message: explain(view, "gh pr view", repo) };
|
|
221
|
+
let head = "";
|
|
222
|
+
let url = `https://github.com/${repo}/pull/${pr}`;
|
|
223
|
+
let headRepo = repo;
|
|
224
|
+
try {
|
|
225
|
+
const parsed = JSON.parse(view.stdout);
|
|
226
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
227
|
+
const o = parsed;
|
|
228
|
+
if (typeof o["headRefName"] === "string")
|
|
229
|
+
head = o["headRefName"];
|
|
230
|
+
if (typeof o["url"] === "string")
|
|
231
|
+
url = o["url"];
|
|
232
|
+
const owner = typeof o["headRepositoryOwner"] === "object" && o["headRepositoryOwner"] !== null ? o["headRepositoryOwner"]["login"] : undefined;
|
|
233
|
+
const name = typeof o["headRepository"] === "object" && o["headRepository"] !== null ? o["headRepository"]["name"] : undefined;
|
|
234
|
+
if (typeof owner === "string" && typeof name === "string")
|
|
235
|
+
headRepo = `${owner}/${name}`;
|
|
236
|
+
else if (o["isCrossRepository"] === true)
|
|
237
|
+
headRepo = "";
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
return { ok: false, message: "gh pr view answered something Tapline couldn't read" };
|
|
242
|
+
}
|
|
243
|
+
if (!safeBranch(head))
|
|
244
|
+
return { ok: false, message: "the pull request's branch name isn't one Tapline will push to" };
|
|
245
|
+
if (headRepo === "")
|
|
246
|
+
return { ok: false, message: "the pull request's head repository couldn't be told, so nothing was written" };
|
|
247
|
+
const fork = headRepo.toLowerCase() !== repo.toLowerCase();
|
|
248
|
+
if (fork && input.target.fork !== true) {
|
|
249
|
+
return { ok: false, message: `the pull request's branch lives in ${headRepo}, not ${repo}, so the report would be written there; export again with fork: true if your user wants that, or export to an issue` };
|
|
250
|
+
}
|
|
251
|
+
const refusedComment = await visibilityRefusal(run, repo, input.target.public === true);
|
|
252
|
+
if (refusedComment !== null)
|
|
253
|
+
return { ok: false, message: refusedComment };
|
|
254
|
+
if (fork) {
|
|
255
|
+
const refusedFork = await visibilityRefusal(run, headRepo, input.target.public === true);
|
|
256
|
+
if (refusedFork !== null)
|
|
257
|
+
return { ok: false, message: refusedFork };
|
|
258
|
+
}
|
|
259
|
+
const cloned = await run("gh", ["repo", "clone", headRepo, clone, "--", "--branch", head, "--single-branch", "--depth", "1"]);
|
|
260
|
+
if (cloned.code !== 0)
|
|
261
|
+
return { ok: false, message: explain(cloned, "gh repo clone", headRepo) };
|
|
262
|
+
const where = `.tapline/reports/${input.name}`;
|
|
263
|
+
copyReport(input.folder, clone, join(clone, where), input.files);
|
|
264
|
+
const git = async (args, what) => {
|
|
265
|
+
const done = await run("git", args, { cwd: clone });
|
|
266
|
+
return done.code === 0 ? null : explain(done, what, headRepo);
|
|
267
|
+
};
|
|
268
|
+
const failure = (await git(["add", "-f", "--", where], "git add")) ??
|
|
269
|
+
(await git(["commit", "-m", `QA journey report: ${input.goal}`.slice(0, 120)], "git commit")) ??
|
|
270
|
+
(await git(["push", "origin", `HEAD:refs/heads/${head}`], "git push"));
|
|
271
|
+
if (failure !== null)
|
|
272
|
+
return { ok: false, message: failure };
|
|
273
|
+
const comment = join(tmp, "comment.md");
|
|
274
|
+
writeFileSync(comment, bodyOf(input, (p) => rawUrl(headRepo, head, `${where}/${p}`), rawUrl(headRepo, head, `${where}/report.md`), false), "utf8");
|
|
275
|
+
const posted = await run("gh", ["pr", "comment", String(pr), "--repo", repo, "--body-file", comment]);
|
|
276
|
+
if (posted.code !== 0)
|
|
277
|
+
return { ok: false, message: `${explain(posted, "gh pr comment", repo)} (the report was already pushed to ${where}/ on ${head} of ${headRepo})` };
|
|
278
|
+
return { ok: true, lines: [`Exported: committed ${input.files.length} files of the report to ${where}/ on branch ${head} of ${headRepo}, and commented on ${url}.`] };
|
|
279
|
+
}
|
|
280
|
+
async function toIssue(input, run, repo, tmp, clone) {
|
|
281
|
+
const refused = await visibilityRefusal(run, repo, input.target.public === true);
|
|
282
|
+
if (refused !== null)
|
|
283
|
+
return { ok: false, message: refused };
|
|
284
|
+
const branch = await run("gh", ["api", `repos/${repo}/branches/${QA_BRANCH}`, "--silent"]);
|
|
285
|
+
const exists = branch.code === 0;
|
|
286
|
+
if (!exists && !/404|not found/i.test(`${branch.stderr}${branch.stdout}`))
|
|
287
|
+
return { ok: false, message: explain(branch, "gh api", repo) };
|
|
288
|
+
const cloned = exists
|
|
289
|
+
? await run("gh", ["repo", "clone", repo, clone, "--", "--branch", QA_BRANCH, "--single-branch", "--depth", "1"])
|
|
290
|
+
: await run("gh", ["repo", "clone", repo, clone, "--", "--depth", "1", "--no-checkout"]);
|
|
291
|
+
if (cloned.code !== 0)
|
|
292
|
+
return { ok: false, message: explain(cloned, "gh repo clone", repo) };
|
|
293
|
+
const git = async (args, what) => {
|
|
294
|
+
const done = await run("git", args, { cwd: clone });
|
|
295
|
+
return done.code === 0 ? null : explain(done, what, repo);
|
|
296
|
+
};
|
|
297
|
+
if (!exists) {
|
|
298
|
+
const orphan = (await git(["checkout", "--orphan", QA_BRANCH], "git checkout --orphan")) ?? (await git(["rm", "-rf", "-q", "--ignore-unmatch", "."], "git rm"));
|
|
299
|
+
if (orphan !== null)
|
|
300
|
+
return { ok: false, message: orphan };
|
|
301
|
+
}
|
|
302
|
+
const where = `reports/${input.name}`;
|
|
303
|
+
copyReport(input.folder, clone, join(clone, where), input.files);
|
|
304
|
+
const failure = (await git(["add", "-f", "--", where], "git add")) ??
|
|
305
|
+
(await git(["commit", "-m", `QA journey report: ${input.goal}`.slice(0, 120)], "git commit")) ??
|
|
306
|
+
(await git(["push", "origin", `HEAD:refs/heads/${QA_BRANCH}`], "git push"));
|
|
307
|
+
if (failure !== null)
|
|
308
|
+
return { ok: false, message: failure };
|
|
309
|
+
const body = join(tmp, "issue.md");
|
|
310
|
+
writeFileSync(body, bodyOf(input, (p) => rawUrl(repo, QA_BRANCH, `${where}/${p}`), rawUrl(repo, QA_BRANCH, `${where}/report.md`), true), "utf8");
|
|
311
|
+
const title = `QA journey: ${input.goal}`.slice(0, 120);
|
|
312
|
+
const created = await run("gh", ["issue", "create", "--repo", repo, "--title", title, "--body-file", body]);
|
|
313
|
+
if (created.code !== 0)
|
|
314
|
+
return { ok: false, message: `${explain(created, "gh issue create", repo)} (the report was already pushed to ${where}/ on ${QA_BRANCH})` };
|
|
315
|
+
const url = created.stdout.trim().split(/\s+/).find((w) => w.startsWith("https://")) ?? "";
|
|
316
|
+
return { ok: true, lines: [`Exported: pushed ${input.files.length} files of the report to ${where}/ on branch ${QA_BRANCH} of ${repo}${exists ? "" : " (made it)"}, and opened an issue${url !== "" ? `: ${url}` : "."}`] };
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=journeyexport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journeyexport.js","sourceRoot":"","sources":["../src/journeyexport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC3H,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAsB9E,gHAAgH;AAChH,MAAM,CAAC,MAAM,UAAU,GAAW,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAC3D,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,QAAQ,CACN,OAAO,EACP,IAAI,EACJ,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,GAAG,EAAE,kBAAkB,EAAE,GAAG,EAAE,EAAE,EAC7M,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;QACxB,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAChE,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;QACnD,IAAI,IAAI,KAAK,QAAQ;YAAE,OAAO,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QACpG,OAAO,CAAC,EAAE,IAAI,EAAE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACjH,CAAC,CACF,CAAC;AACJ,CAAC,CAAC,CAAC;AAmBL,iGAAiG;AACjG,MAAM,IAAI,GAAG,yDAAyD,CAAC;AACvE,MAAM,SAAS,GAAG,YAAY,CAAC;AAE/B,oCAAoC;AACpC,SAAS,KAAK,CAAC,IAAY;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;IAClF,OAAO,IAAI,CAAC,OAAO,CAAC,6BAA6B,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,yBAAyB,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACnH,CAAC;AAED,SAAS,OAAO,CAAC,MAAiB,EAAE,IAAY,EAAE,IAAY;IAC5D,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC5B,OAAO,mIAAmI,CAAC;IAC7I,CAAC;IACD,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC;IAClD,IAAI,8FAA8F,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9G,OAAO,4DAA4D,CAAC;IACtE,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,+DAA+D,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9G,OAAO,GAAG,IAAI,iDAAiD,IAAI,8BAA8B,CAAC;IACpG,CAAC;IACD,IAAI,0CAA0C,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1D,OAAO,eAAe,IAAI,mEAAmE,CAAC;IAChG,CAAC;IACD,IAAI,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,yBAAyB,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC;IACpG,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,GAAG,IAAI,UAAU,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;AAC/D,CAAC;AAED,uHAAuH;AACvH,SAAS,MAAM,CAAC,IAAY,EAAE,MAAc,EAAE,IAAY;IACxD,MAAM,MAAM,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC7F,OAAO,sBAAsB,IAAI,QAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,IAAY;IAChC,OAAO,IAAI;SACR,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;SAC3C,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC;SACxB,OAAO,CAAC,iBAAiB,EAAE,aAAa,CAAC;QAC1C,oDAAoD;SACnD,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,MAAM,CAAC,KAAkB,EAAE,UAAwC,EAAE,SAAiB,EAAE,GAAY;IAC3G,MAAM,KAAK,GAAa,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,EAAE,EAAE,KAAK,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IAC3E,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,kBAAkB,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAClG,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAChE,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACvE,IAAI,GAAG,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,GAAG,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACpG,CAAC,CAAC,CAAC;IACH,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,uBAAuB,CAAC,CAAC;IAC/H,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,4BAA4B,SAAS,0CAA0C,EAAE,EAAE,EAAE,qBAAqB,CAAC,CAAC;IAC3H,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,WAAW,CAAC,MAAqB;IACxC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,eAAe,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC;IAChI,IAAI,MAAM,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QAAE,OAAO,2CAA2C,CAAC;IACzJ,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,UAAU,CAAC,MAAc;IAChC,OAAO,MAAM,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,MAAM,CAAC,IAAY,EAAE,IAAY;IACxC,OAAO,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,IAAY,EAAE,GAAW;IAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;IAC3E,IAAI,EAAE,GAAG,IAAI,CAAC;IACd,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,EAAE,GAAG,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QACpB,IAAI,UAAU,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;YACjC,IAAI,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,IAAI,yBAAyB,CAAC,CAAC;QAC/J,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,MAAM,CAAC,IAAY;IAC1B,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,MAAc,EAAE,KAAa,EAAE,IAAY,EAAE,KAAwB;IACvF,MAAM,IAAI,GAAG,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACpH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,kFAAkF;QAClF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACjC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC;YAClF,MAAM,IAAI,KAAK,CAAC,+CAA+C,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAChH,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACtD,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC7F,CAAC;QACD,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,iBAAiB,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC/C,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QAClI,IAAI,MAAM,CAAC,EAAE,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uDAAuD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACtI,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,8GAA8G;AAC9G,KAAK,UAAU,iBAAiB,CAAC,GAAW,EAAE,IAAY,EAAE,OAAgB;IAC1E,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;IAC7E,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,CAAC;IAChE,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,YAAY,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;YAAE,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;IACrK,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;IACD,IAAI,UAAU,KAAK,EAAE;QAAE,OAAO,iCAAiC,IAAI,iCAAiC,CAAC;IACrG,IAAI,UAAU,KAAK,QAAQ,IAAI,CAAC,OAAO;QAAE,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,WAAW,CAAC,IAAY;IAC/B,OAAO,GAAG,IAAI,+JAA+J,CAAC;AAChL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAqB,EAAE,GAAW;IACpE,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnG,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;QACpF,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACjC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,MAAM,UAAU,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,YAAY,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3K,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAkB,EAAE,GAAW,EAAE,WAAyB,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,CAAC,CAAC;IAC/I,IAAI,KAAK,CAAC,MAAM,CAAC,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC;IACxH,MAAM,GAAG,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;IACrD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;IAC/B,MAAM,GAAG,GAAG,QAAQ,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;QACjD,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,gBAAgB,EAAE,IAAI,CAAC,EAAE,CAAC;QAC1F,OAAO,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,aAAa,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1J,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IACxF,CAAC;YAAS,CAAC;QACT,iGAAiG;QACjG,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,CAAC;QAAC,MAAM,CAAC;YACP,kCAAkC;QACpC,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,KAAkB,EAAE,GAAW,EAAE,IAAY,EAAE,EAAU,EAAE,GAAW,EAAE,KAAa;IAChH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,sEAAsE,CAAC,CAAC,CAAC;IAC3J,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC;IACtF,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,GAAG,GAAG,sBAAsB,IAAI,SAAS,EAAE,EAAE,CAAC;IAClD,IAAI,QAAQ,GAAG,IAAI,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAClD,MAAM,CAAC,GAAG,MAAiC,CAAC;YAC5C,IAAI,OAAO,CAAC,CAAC,aAAa,CAAC,KAAK,QAAQ;gBAAE,IAAI,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC;YAClE,IAAI,OAAO,CAAC,CAAC,KAAK,CAAC,KAAK,QAAQ;gBAAE,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC;YACjD,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,qBAAqB,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,qBAAqB,CAA6B,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7K,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,gBAAgB,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,gBAAgB,CAA6B,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5J,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,IAAI,KAAK,QAAQ;gBAAE,QAAQ,GAAG,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC;iBACpF,IAAI,CAAC,CAAC,mBAAmB,CAAC,KAAK,IAAI;gBAAE,QAAQ,GAAG,EAAE,CAAC;QAC1D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,qDAAqD,EAAE,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,+DAA+D,EAAE,CAAC;IACtH,IAAI,QAAQ,KAAK,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,6EAA6E,EAAE,CAAC;IAClI,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3D,IAAI,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;QACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,sCAAsC,QAAQ,SAAS,IAAI,qHAAqH,EAAE,CAAC;IAClN,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IACxF,IAAI,cAAc,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAC3E,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,WAAW,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;QACzF,IAAI,WAAW,KAAK,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;IACvE,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9H,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,CAAC;IACjG,MAAM,KAAK,GAAG,oBAAoB,KAAK,CAAC,IAAI,EAAE,CAAC;IAC/C,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,GAAG,GAAG,KAAK,EAAE,IAAc,EAAE,IAAY,EAA0B,EAAE;QACzE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAChE,CAAC,CAAC;IACF,MAAM,OAAO,GACX,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,sBAAsB,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;QAC7F,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,mBAAmB,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IACzE,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACxC,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,KAAK,YAAY,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,CAAC;IACnJ,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC;IACtG,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,sCAAsC,KAAK,QAAQ,IAAI,OAAO,QAAQ,GAAG,EAAE,CAAC;IACzK,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,uBAAuB,KAAK,CAAC,KAAK,CAAC,MAAM,2BAA2B,KAAK,eAAe,IAAI,OAAO,QAAQ,sBAAsB,GAAG,GAAG,CAAC,EAAE,CAAC;AACxK,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,KAAkB,EAAE,GAAW,EAAE,IAAY,EAAE,GAAW,EAAE,KAAa;IAC9F,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IACjF,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,aAAa,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3F,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;IAC1I,MAAM,MAAM,GAAG,MAAM;QACnB,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACjH,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC;IAC3F,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,CAAC,EAAE,CAAC;IAC7F,MAAM,GAAG,GAAG,KAAK,EAAE,IAAc,EAAE,IAAY,EAA0B,EAAE;QACzE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5D,CAAC,CAAC;IACF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,EAAE,SAAS,CAAC,EAAE,uBAAuB,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,kBAAkB,EAAE,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC;QAChK,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAC7D,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,KAAK,CAAC,IAAI,EAAE,CAAC;IACtC,UAAU,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACjE,MAAM,OAAO,GACX,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,EAAE,sBAAsB,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC;QAC7F,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,mBAAmB,SAAS,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;IAC9E,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;IAC7D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACnC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,KAAK,IAAI,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,SAAS,EAAE,GAAG,KAAK,YAAY,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACjJ,MAAM,KAAK,GAAG,eAAe,KAAK,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACxD,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5G,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,iBAAiB,EAAE,IAAI,CAAC,sCAAsC,KAAK,QAAQ,SAAS,GAAG,EAAE,CAAC;IACnK,MAAM,GAAG,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3F,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,oBAAoB,KAAK,CAAC,KAAK,CAAC,MAAM,2BAA2B,KAAK,eAAe,SAAS,OAAO,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,wBAAwB,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC;AAC9N,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type Pixels } from "./images.js";
|
|
2
|
+
export declare const GIF_WIDTH = 360;
|
|
3
|
+
export declare const GIF_MAX_BYTES: number;
|
|
4
|
+
/** One kept frame: the screen after a step, when it was taken, and where the step touched (shares of the screen). */
|
|
5
|
+
export interface GifFrame {
|
|
6
|
+
pixels: Pixels;
|
|
7
|
+
at: number;
|
|
8
|
+
tap?: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
/** Set by `fitted` when the picture was letterboxed: its place in the canvas, as shares of it. */
|
|
13
|
+
fit?: {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
w: number;
|
|
17
|
+
h: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export interface GifResult {
|
|
21
|
+
bytes: Uint8Array;
|
|
22
|
+
/** Pictures in the file (frames and tap rings). */
|
|
23
|
+
frames: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
/** Pictures dropped to stay under the cap. */
|
|
27
|
+
dropped: number;
|
|
28
|
+
/** The cap needed the pictures at half the size. */
|
|
29
|
+
shrunk: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface Slide {
|
|
32
|
+
pixels: Pixels;
|
|
33
|
+
delay: number;
|
|
34
|
+
/** A frame of a still stretch: the first to go when the file is too big. */
|
|
35
|
+
still: boolean;
|
|
36
|
+
/** The screen before a tap, with the ring drawn on it. */
|
|
37
|
+
ring: boolean;
|
|
38
|
+
}
|
|
39
|
+
/** The frames made the same size as the first one. */
|
|
40
|
+
export declare function fitted(frames: GifFrame[], width: number): GifFrame[];
|
|
41
|
+
export declare function slidesOf(frames: GifFrame[]): Slide[];
|
|
42
|
+
/** How the frames are laid out in time (for tests): each picture's delay in ms, whether it is a still stretch, whether it is a tap ring. */
|
|
43
|
+
export declare function slidePlan(frames: GifFrame[]): Array<{
|
|
44
|
+
delay: number;
|
|
45
|
+
still: boolean;
|
|
46
|
+
ring: boolean;
|
|
47
|
+
}>;
|
|
48
|
+
/** The GIF of a journey. `maxBytes` and `width` are for tests; the defaults are the spec's (3 MB, 360 px). */
|
|
49
|
+
export declare function encodeJourneyGif(frames: GifFrame[], options?: {
|
|
50
|
+
maxBytes?: number;
|
|
51
|
+
width?: number;
|
|
52
|
+
}): GifResult;
|
|
53
|
+
export interface GifInfo {
|
|
54
|
+
version: string;
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
frames: number;
|
|
58
|
+
/** 0 = forever; undefined when the file says nothing. */
|
|
59
|
+
loops: number | undefined;
|
|
60
|
+
}
|
|
61
|
+
/** Reads a GIF's header and counts its pictures by walking its blocks; throws when it isn't one. */
|
|
62
|
+
export declare function gifInfo(bytes: Uint8Array): GifInfo;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import gifencDefault, * as gifencNamespace from "gifenc";
|
|
2
|
+
import { markRing, scaleToWidth } from "./images.js";
|
|
3
|
+
function isGifenc(value) {
|
|
4
|
+
return typeof value === "object" && value !== null && "GIFEncoder" in value && typeof value.GIFEncoder === "function" && "quantize" in value && "applyPalette" in value;
|
|
5
|
+
}
|
|
6
|
+
/** gifenc is CommonJS with a `default` of its own: Node hands over its exports as the default, bundlers and test runners as the module. */
|
|
7
|
+
function loadGifenc() {
|
|
8
|
+
const inner = typeof gifencDefault === "object" && gifencDefault !== null && "default" in gifencDefault ? gifencDefault.default : undefined;
|
|
9
|
+
for (const candidate of [gifencDefault, inner, gifencNamespace])
|
|
10
|
+
if (isGifenc(candidate))
|
|
11
|
+
return candidate;
|
|
12
|
+
throw new Error("The GIF encoder (gifenc) could not be loaded.");
|
|
13
|
+
}
|
|
14
|
+
const gifenc = loadGifenc();
|
|
15
|
+
export const GIF_WIDTH = 360;
|
|
16
|
+
export const GIF_MAX_BYTES = 3 * 1024 * 1024;
|
|
17
|
+
/** A still stretch shows its frame this long at most (two frames a second); closer frames keep their real gap. */
|
|
18
|
+
const STILL_MS = 500;
|
|
19
|
+
const MIN_DELAY_MS = 40;
|
|
20
|
+
const RING_MS = 350;
|
|
21
|
+
const LAST_MS = 1500;
|
|
22
|
+
/** Time handed to the frame before when a frame is dropped, at most. */
|
|
23
|
+
const MERGED_MS = 1200;
|
|
24
|
+
const COLOURS = 128;
|
|
25
|
+
/** The frames made the same size as the first one. */
|
|
26
|
+
export function fitted(frames, width) {
|
|
27
|
+
const first = frames[0];
|
|
28
|
+
if (first === undefined)
|
|
29
|
+
return [];
|
|
30
|
+
const scaledFirst = scaleToWidth(first.pixels, width);
|
|
31
|
+
const height = scaledFirst.height;
|
|
32
|
+
return frames.map((frame, i) => {
|
|
33
|
+
const scaled = i === 0 ? scaledFirst : scaleToWidth(frame.pixels, width);
|
|
34
|
+
if (scaled.height === height)
|
|
35
|
+
return { ...frame, pixels: scaled };
|
|
36
|
+
// Another shape (the phone turned): the whole picture is fitted inside the canvas, centred, never cropped.
|
|
37
|
+
const k = Math.min(width / frame.pixels.width, height / frame.pixels.height);
|
|
38
|
+
const inner = scaleToWidth(frame.pixels, Math.max(1, Math.round(frame.pixels.width * k)));
|
|
39
|
+
const data = new Uint8Array(width * height * 4).fill(255);
|
|
40
|
+
const left = Math.max(0, Math.floor((width - inner.width) / 2));
|
|
41
|
+
const top = Math.max(0, Math.floor((height - inner.height) / 2));
|
|
42
|
+
const rows = Math.min(inner.height, height - top);
|
|
43
|
+
const columns = Math.min(inner.width, width - left);
|
|
44
|
+
for (let y = 0; y < rows; y++) {
|
|
45
|
+
data.set(inner.data.subarray(y * inner.width * 4, (y * inner.width + columns) * 4), ((top + y) * width + left) * 4);
|
|
46
|
+
}
|
|
47
|
+
// Where the picture sits in the canvas (shares of it), so a tap's share of the screen can be moved to match.
|
|
48
|
+
return { ...frame, pixels: { width, height, data }, fit: { x: left / width, y: top / height, w: inner.width / width, h: inner.height / height } };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/** A tap (a share of the screen) as a share of the canvas the screen was fitted in. */
|
|
52
|
+
function tapOnCanvas(tap, fit) {
|
|
53
|
+
return fit === undefined ? tap : { x: fit.x + tap.x * fit.w, y: fit.y + tap.y * fit.h };
|
|
54
|
+
}
|
|
55
|
+
export function slidesOf(frames) {
|
|
56
|
+
const slides = [];
|
|
57
|
+
frames.forEach((frame, i) => {
|
|
58
|
+
const previous = frames[i - 1];
|
|
59
|
+
if (frame.tap !== undefined) {
|
|
60
|
+
// The tap was made on the screen before it: the ring goes there, a moment before the result.
|
|
61
|
+
// The ring sits on the picture it is drawn on, so it follows that picture's scale and offset in the canvas.
|
|
62
|
+
const shown = previous ?? frame;
|
|
63
|
+
slides.push({ pixels: markRing(shown.pixels, tapOnCanvas(frame.tap, shown.fit)), delay: RING_MS, still: false, ring: true });
|
|
64
|
+
}
|
|
65
|
+
const next = frames[i + 1];
|
|
66
|
+
const gap = next === undefined ? LAST_MS : Math.max(0, next.at - frame.at);
|
|
67
|
+
slides.push({
|
|
68
|
+
pixels: frame.pixels,
|
|
69
|
+
delay: next === undefined ? LAST_MS : Math.min(STILL_MS, Math.max(MIN_DELAY_MS, gap)),
|
|
70
|
+
still: next !== undefined && gap > STILL_MS,
|
|
71
|
+
ring: false,
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
return slides;
|
|
75
|
+
}
|
|
76
|
+
/** Every second one of `indexes` (or the only one), dropped; the time goes to the picture before. */
|
|
77
|
+
function dropHalf(slides, indexes) {
|
|
78
|
+
if (indexes.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
const gone = new Set(indexes.length === 1 ? indexes : indexes.filter((_, n) => n % 2 === 1));
|
|
81
|
+
const kept = [];
|
|
82
|
+
slides.forEach((slide, i) => {
|
|
83
|
+
if (!gone.has(i)) {
|
|
84
|
+
kept.push(slide);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const before = kept[kept.length - 1];
|
|
88
|
+
if (before !== undefined)
|
|
89
|
+
kept[kept.length - 1] = { ...before, delay: Math.min(MERGED_MS, before.delay + slide.delay) };
|
|
90
|
+
});
|
|
91
|
+
return kept;
|
|
92
|
+
}
|
|
93
|
+
/** Fewer pictures: the still ones first, then every second one but the first and the last; null when there is nothing left to drop. */
|
|
94
|
+
function shrink(slides) {
|
|
95
|
+
const inner = slides.map((_, i) => i).filter((i) => i > 0 && i < slides.length - 1);
|
|
96
|
+
const still = inner.filter((i) => slides[i]?.still === true);
|
|
97
|
+
return dropHalf(slides, still) ?? dropHalf(slides, inner);
|
|
98
|
+
}
|
|
99
|
+
/** How the frames are laid out in time (for tests): each picture's delay in ms, whether it is a still stretch, whether it is a tap ring. */
|
|
100
|
+
export function slidePlan(frames) {
|
|
101
|
+
return slidesOf([...frames].sort((a, b) => a.at - b.at)).map(({ delay, still, ring }) => ({ delay, still, ring }));
|
|
102
|
+
}
|
|
103
|
+
function encode(slides, coded) {
|
|
104
|
+
const first = slides[0];
|
|
105
|
+
if (first === undefined)
|
|
106
|
+
throw new Error("There are no frames for a GIF.");
|
|
107
|
+
const encoder = gifenc.GIFEncoder();
|
|
108
|
+
const { width, height } = first.pixels;
|
|
109
|
+
for (const slide of slides) {
|
|
110
|
+
let entry = coded.get(slide.pixels);
|
|
111
|
+
if (entry === undefined) {
|
|
112
|
+
const palette = gifenc.quantize(slide.pixels.data, COLOURS, { format: "rgb444" });
|
|
113
|
+
entry = { palette, index: gifenc.applyPalette(slide.pixels.data, palette, "rgb444") };
|
|
114
|
+
coded.set(slide.pixels, entry);
|
|
115
|
+
}
|
|
116
|
+
encoder.writeFrame(entry.index, width, height, { palette: entry.palette, delay: slide.delay, repeat: 0 });
|
|
117
|
+
}
|
|
118
|
+
encoder.finish();
|
|
119
|
+
return encoder.bytes();
|
|
120
|
+
}
|
|
121
|
+
/** The GIF of a journey. `maxBytes` and `width` are for tests; the defaults are the spec's (3 MB, 360 px). */
|
|
122
|
+
export function encodeJourneyGif(frames, options = {}) {
|
|
123
|
+
if (frames.length === 0)
|
|
124
|
+
throw new Error("There are no frames for a GIF.");
|
|
125
|
+
const maxBytes = options.maxBytes ?? GIF_MAX_BYTES;
|
|
126
|
+
let width = options.width ?? GIF_WIDTH;
|
|
127
|
+
let shrunk = false;
|
|
128
|
+
let ordered = [...frames].sort((a, b) => a.at - b.at);
|
|
129
|
+
for (;;) {
|
|
130
|
+
let slides = slidesOf(fitted(ordered, width));
|
|
131
|
+
const total = slides.length;
|
|
132
|
+
const coded = new Map();
|
|
133
|
+
let bytes = encode(slides, coded);
|
|
134
|
+
while (bytes.length > maxBytes) {
|
|
135
|
+
const fewer = shrink(slides);
|
|
136
|
+
if (fewer === null)
|
|
137
|
+
break;
|
|
138
|
+
slides = fewer;
|
|
139
|
+
bytes = encode(slides, coded);
|
|
140
|
+
}
|
|
141
|
+
const first = slides[0];
|
|
142
|
+
if (bytes.length <= maxBytes || width <= 90 || first === undefined) {
|
|
143
|
+
return { bytes, frames: slides.length, width: first?.pixels.width ?? width, height: first?.pixels.height ?? 0, dropped: total - slides.length, shrunk };
|
|
144
|
+
}
|
|
145
|
+
// Even two pictures are over the cap: the same pictures at half the size.
|
|
146
|
+
width = Math.max(90, Math.round(width / 2));
|
|
147
|
+
shrunk = true;
|
|
148
|
+
ordered = ordered.map((frame) => ({ ...frame, pixels: scaleToWidth(frame.pixels, width) }));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Reads a GIF's header and counts its pictures by walking its blocks; throws when it isn't one. */
|
|
152
|
+
export function gifInfo(bytes) {
|
|
153
|
+
const text = (from, to) => String.fromCharCode(...bytes.subarray(from, to));
|
|
154
|
+
const head = text(0, 6);
|
|
155
|
+
if (!/^GIF8[79]a$/.test(head))
|
|
156
|
+
throw new Error("not a GIF");
|
|
157
|
+
const u16 = (at) => (bytes[at] ?? 0) | ((bytes[at + 1] ?? 0) << 8);
|
|
158
|
+
const width = u16(6);
|
|
159
|
+
const height = u16(8);
|
|
160
|
+
const packed = bytes[10] ?? 0;
|
|
161
|
+
let at = 13 + ((packed & 0x80) !== 0 ? 3 * 2 ** ((packed & 7) + 1) : 0);
|
|
162
|
+
let frames = 0;
|
|
163
|
+
let loops;
|
|
164
|
+
const skipBlocks = () => {
|
|
165
|
+
while (at < bytes.length && (bytes[at] ?? 0) !== 0)
|
|
166
|
+
at += (bytes[at] ?? 0) + 1;
|
|
167
|
+
at += 1;
|
|
168
|
+
};
|
|
169
|
+
while (at < bytes.length) {
|
|
170
|
+
const marker = bytes[at];
|
|
171
|
+
if (marker === 0x3b)
|
|
172
|
+
break;
|
|
173
|
+
if (marker === 0x21) {
|
|
174
|
+
const label = bytes[at + 1];
|
|
175
|
+
if (label === 0xff && text(at + 3, at + 14) === "NETSCAPE2.0")
|
|
176
|
+
loops = u16(at + 16);
|
|
177
|
+
at += 2;
|
|
178
|
+
skipBlocks();
|
|
179
|
+
}
|
|
180
|
+
else if (marker === 0x2c) {
|
|
181
|
+
const flags = bytes[at + 9] ?? 0;
|
|
182
|
+
at += 10 + ((flags & 0x80) !== 0 ? 3 * 2 ** ((flags & 7) + 1) : 0);
|
|
183
|
+
at += 1; // the LZW minimum code size
|
|
184
|
+
skipBlocks();
|
|
185
|
+
frames++;
|
|
186
|
+
}
|
|
187
|
+
else
|
|
188
|
+
throw new Error("not a GIF");
|
|
189
|
+
}
|
|
190
|
+
return { version: head, width, height, frames, loops };
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=journeygif.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journeygif.js","sourceRoot":"","sources":["../src/journeygif.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,EAAE,KAAK,eAAe,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AASlE,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU,IAAI,UAAU,IAAI,KAAK,IAAI,cAAc,IAAI,KAAK,CAAC;AAC1K,CAAC;AAED,2IAA2I;AAC3I,SAAS,UAAU;IACjB,MAAM,KAAK,GAAY,OAAO,aAAa,KAAK,QAAQ,IAAI,aAAa,KAAK,IAAI,IAAI,SAAS,IAAI,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACrJ,KAAK,MAAM,SAAS,IAAI,CAAC,aAAa,EAAE,KAAK,EAAE,eAAe,CAAc;QAAE,IAAI,QAAQ,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAC;IACxH,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;AAE5B,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAC7B,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAC7C,kHAAkH;AAClH,MAAM,QAAQ,GAAG,GAAG,CAAC;AACrB,MAAM,YAAY,GAAG,EAAE,CAAC;AACxB,MAAM,OAAO,GAAG,GAAG,CAAC;AACpB,MAAM,OAAO,GAAG,IAAI,CAAC;AACrB,wEAAwE;AACxE,MAAM,SAAS,GAAG,IAAI,CAAC;AACvB,MAAM,OAAO,GAAG,GAAG,CAAC;AAqCpB,sDAAsD;AACtD,MAAM,UAAU,MAAM,CAAC,MAAkB,EAAE,KAAa;IACtD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;IAClC,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACzE,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM;YAAE,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAClE,2GAA2G;QAC3G,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1F,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,KAAK,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC;QACpD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;YAC9B,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;QACtH,CAAC;QACD,6GAA6G;QAC7G,OAAO,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,EAAE,IAAI,GAAG,KAAK,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,EAAE,EAAE,CAAC;IACpJ,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uFAAuF;AACvF,SAAS,WAAW,CAAC,GAA6B,EAAE,GAAoB;IACtE,OAAO,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,MAAkB;IACzC,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,GAAG,KAAK,SAAS,EAAE,CAAC;YAC5B,6FAA6F;YAC7F,4GAA4G;YAC5G,MAAM,KAAK,GAAG,QAAQ,IAAI,KAAK,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/H,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,IAAI,CAAC;YACV,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;YACrF,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,GAAG,GAAG,QAAQ;YAC3C,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,qGAAqG;AACrG,SAAS,QAAQ,CAAC,MAAe,EAAE,OAAiB;IAClD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7F,MAAM,IAAI,GAAY,EAAE,CAAC;IACzB,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACjB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO;QACT,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;IAC1H,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uIAAuI;AACvI,SAAS,MAAM,CAAC,MAAe;IAC7B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACpF,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC,CAAC;IAC7D,OAAO,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AAC5D,CAAC;AAED,4IAA4I;AAC5I,MAAM,UAAU,SAAS,CAAC,MAAkB;IAC1C,OAAO,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACrH,CAAC;AAED,SAAS,MAAM,CAAC,MAAe,EAAE,KAAyB;IACxD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,EAAE,CAAC;IACpC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;YAClF,KAAK,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,CAAC;YACtF,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5G,CAAC;IACD,OAAO,CAAC,MAAM,EAAE,CAAC;IACjB,OAAO,OAAO,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,gBAAgB,CAAC,MAAkB,EAAE,UAAiD,EAAE;IACtG,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAC3E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,aAAa,CAAC;IACnD,IAAI,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,SAAS,CAAC;IACvC,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACtD,SAAS,CAAC;QACR,IAAI,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC;QAC5B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAiB,CAAC;QACvC,IAAI,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAClC,OAAO,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YAC7B,IAAI,KAAK,KAAK,IAAI;gBAAE,MAAM;YAC1B,MAAM,GAAG,KAAK,CAAC;YACf,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAChC,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ,IAAI,KAAK,IAAI,EAAE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACnE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;QAC1J,CAAC;QACD,0EAA0E;QAC1E,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,GAAG,IAAI,CAAC;QACd,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC;AAWD,oGAAoG;AACpG,MAAM,UAAU,OAAO,CAAC,KAAiB;IACvC,MAAM,IAAI,GAAG,CAAC,IAAY,EAAE,EAAU,EAAU,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IACpG,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,GAAG,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACnF,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACrB,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAyB,CAAC;IAC9B,MAAM,UAAU,GAAG,GAAS,EAAE;QAC5B,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC;YAAE,EAAE,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/E,EAAE,IAAI,CAAC,CAAC;IACV,CAAC,CAAC;IACF,OAAO,EAAE,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QACzB,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM;QAC3B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YAC5B,IAAI,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,KAAK,aAAa;gBAAE,KAAK,GAAG,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;YACpF,EAAE,IAAI,CAAC,CAAC;YACR,UAAU,EAAE,CAAC;QACf,CAAC;aAAM,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;YAC3B,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACjC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACnE,EAAE,IAAI,CAAC,CAAC,CAAC,4BAA4B;YACrC,UAAU,EAAE,CAAC;YACb,MAAM,EAAE,CAAC;QACX,CAAC;;YAAM,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AACzD,CAAC"}
|