@tanishqkancharla/diffmap 0.1.4 → 0.2.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/README.md +69 -61
- package/index.html +18 -0
- package/package.json +7 -5
- package/src/annotations.ts +36 -29
- package/src/changes.ts +229 -0
- package/src/cli.test.ts +50 -0
- package/src/cli.ts +41 -5
- package/src/errors.ts +10 -0
- package/src/gist/GistApp.tsx +39 -20
- package/src/gist/GistStatus.tsx +2 -51
- package/src/gist/fetchGist.ts +23 -0
- package/src/git.test.ts +402 -0
- package/src/git.ts +498 -0
- package/src/github/GitHubApp.tsx +39 -22
- package/src/github/fetchRepo.test.ts +59 -1
- package/src/github/fetchRepo.ts +99 -43
- package/src/github/patch.test.ts +18 -9
- package/src/github/patch.ts +30 -19
- package/src/host/HostHome.tsx +86 -0
- package/src/host/files.ts +10 -0
- package/src/host/home.md +156 -0
- package/src/host/home.test.ts +83 -0
- package/src/index.ts +1 -0
- package/src/packageVersion.ts +7 -0
- package/src/parseFence.ts +6 -6
- package/src/parseViewer.test.ts +87 -0
- package/src/parseViewer.ts +103 -77
- package/src/serve.test.ts +306 -0
- package/src/serve.ts +48 -5
- package/src/share.ts +18 -0
- package/src/update.test.ts +19 -0
- package/src/update.ts +285 -0
- package/src/viewer/CallStackDiff.tsx +15 -3
- package/src/viewer/ChangesCard.tsx +267 -0
- package/src/viewer/CloseServerButton.tsx +16 -0
- package/src/viewer/ComarkView.tsx +83 -15
- package/src/viewer/Fence.tsx +9 -1
- package/src/viewer/FileExcerpt.tsx +25 -3
- package/src/viewer/HtmlPlaceholder.tsx +6 -1
- package/src/viewer/MermaidBlock.tsx +99 -11
- package/src/viewer/RangePicker.tsx +526 -0
- package/src/viewer/SourceDiffPanel.tsx +277 -121
- package/src/viewer/TableOfContents.tsx +313 -39
- package/src/viewer/ViewerApp.tsx +120 -68
- package/src/viewer/changesSource.ts +91 -0
- package/src/viewer/sourceHeader.test.ts +2 -87
- package/src/viewer/sourceHeader.ts +4 -63
- package/src/viewer/sourceNavigation.ts +33 -42
- package/src/viewer/viewerMode.ts +1 -1
- package/vite.config.ts +2 -1
- package/vite.gist.config.ts +57 -0
- package/src/viewer/Check.tsx +0 -24
- package/src/viewer/DoneButton.tsx +0 -32
package/src/cli.ts
CHANGED
|
@@ -2,16 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
import { Cli, z } from "incur";
|
|
4
4
|
import { resolveFromInvokeCwd } from "./invokeCwd.js";
|
|
5
|
+
import { packageVersion } from "./packageVersion.js";
|
|
5
6
|
import { listRunningDiffmaps } from "./registry.js";
|
|
6
7
|
import { startServer } from "./serve.js";
|
|
7
8
|
import { shareMarkdownFile } from "./share.js";
|
|
9
|
+
import {
|
|
10
|
+
detectInstallMethod,
|
|
11
|
+
fetchLatestVersion,
|
|
12
|
+
packageName,
|
|
13
|
+
resolveUpdateArgv,
|
|
14
|
+
runCommand,
|
|
15
|
+
runUpdate,
|
|
16
|
+
} from "./update.js";
|
|
8
17
|
|
|
9
18
|
const serveArgs = z.object({
|
|
10
19
|
file: z.string().describe("Path to the markdown file"),
|
|
11
20
|
});
|
|
12
21
|
|
|
13
22
|
const serveOptions = z.object({
|
|
14
|
-
port: z.coerce.number().optional().describe("Port (default
|
|
23
|
+
port: z.coerce.number().optional().describe("Port (default: a free port)"),
|
|
15
24
|
root: z.string().optional().describe("Workspace root for file excerpts"),
|
|
16
25
|
});
|
|
17
26
|
|
|
@@ -33,7 +42,7 @@ async function* runServe(c: ServeContext) {
|
|
|
33
42
|
c.options.root === undefined
|
|
34
43
|
? resolveFromInvokeCwd(".")
|
|
35
44
|
: resolveFromInvokeCwd(c.options.root),
|
|
36
|
-
port: c.options.port
|
|
45
|
+
port: c.options.port,
|
|
37
46
|
});
|
|
38
47
|
if (started instanceof Error) {
|
|
39
48
|
return c.error({
|
|
@@ -48,11 +57,11 @@ async function* runServe(c: ServeContext) {
|
|
|
48
57
|
|
|
49
58
|
const cli = Cli.create("diffmap", {
|
|
50
59
|
description: "Serve a spec locally, or share it as a gist",
|
|
51
|
-
version:
|
|
60
|
+
version: packageVersion,
|
|
52
61
|
args: serveArgs,
|
|
53
62
|
options: serveOptions,
|
|
54
63
|
output: serveOutput,
|
|
55
|
-
hint: "`serve <spec.md>` runs the local viewer. `share <spec.md>` publishes a secret gist.",
|
|
64
|
+
hint: "`serve <spec.md>` runs the local viewer. `share <spec.md>` publishes a secret gist. `update` (or `--update`) installs the latest diffmap.",
|
|
56
65
|
examples: [
|
|
57
66
|
{
|
|
58
67
|
args: { file: "spec.md" },
|
|
@@ -96,6 +105,7 @@ const cli = Cli.create("diffmap", {
|
|
|
96
105
|
async run(c) {
|
|
97
106
|
const shared = await shareMarkdownFile({
|
|
98
107
|
filePath: resolveFromInvokeCwd(c.args.file),
|
|
108
|
+
workspaceRoot: resolveFromInvokeCwd("."),
|
|
99
109
|
isPublic: c.options.public === true,
|
|
100
110
|
});
|
|
101
111
|
if (shared instanceof Error) {
|
|
@@ -129,6 +139,32 @@ const cli = Cli.create("diffmap", {
|
|
|
129
139
|
}
|
|
130
140
|
return c.ok({ instances });
|
|
131
141
|
},
|
|
142
|
+
})
|
|
143
|
+
.command("update", {
|
|
144
|
+
description: `Update diffmap to the latest ${packageName} on npm (same as --update)`,
|
|
145
|
+
output: z.object({
|
|
146
|
+
status: z.enum(["up-to-date", "updated", "manual"]),
|
|
147
|
+
message: z.string(),
|
|
148
|
+
current: z.string(),
|
|
149
|
+
previous: z.string().optional(),
|
|
150
|
+
latest: z.string().optional(),
|
|
151
|
+
command: z.string().optional(),
|
|
152
|
+
}),
|
|
153
|
+
async run(c) {
|
|
154
|
+
const result = await runUpdate({
|
|
155
|
+
currentVersion: packageVersion,
|
|
156
|
+
fetchLatestVersion,
|
|
157
|
+
detectInstallMethod,
|
|
158
|
+
runCommand,
|
|
159
|
+
});
|
|
160
|
+
if (result instanceof Error) {
|
|
161
|
+
return c.error({
|
|
162
|
+
code: "UPDATE_FAILED",
|
|
163
|
+
message: result.message,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
return c.ok(result);
|
|
167
|
+
},
|
|
132
168
|
});
|
|
133
169
|
|
|
134
|
-
await cli.serve();
|
|
170
|
+
await cli.serve(resolveUpdateArgv(process.argv.slice(2)));
|
package/src/errors.ts
CHANGED
|
@@ -30,6 +30,11 @@ export class DiffmapAnnotationError extends errore.createTaggedError({
|
|
|
30
30
|
message: "diffmap source annotation: $reason",
|
|
31
31
|
}) {}
|
|
32
32
|
|
|
33
|
+
export class DiffmapGitError extends errore.createTaggedError({
|
|
34
|
+
name: "DiffmapGitError",
|
|
35
|
+
message: "$reason",
|
|
36
|
+
}) {}
|
|
37
|
+
|
|
33
38
|
export class DiffmapDefinitionError extends errore.createTaggedError({
|
|
34
39
|
name: "DiffmapDefinitionError",
|
|
35
40
|
message: "$reason",
|
|
@@ -39,3 +44,8 @@ export class DiffmapShareError extends errore.createTaggedError({
|
|
|
39
44
|
name: "DiffmapShareError",
|
|
40
45
|
message: "diffmap share failed: $reason",
|
|
41
46
|
}) {}
|
|
47
|
+
|
|
48
|
+
export class DiffmapUpdateError extends errore.createTaggedError({
|
|
49
|
+
name: "DiffmapUpdateError",
|
|
50
|
+
message: "diffmap update failed: $reason",
|
|
51
|
+
}) {}
|
package/src/gist/GistApp.tsx
CHANGED
|
@@ -4,10 +4,16 @@ import { style, useStyles } from "purse-styles";
|
|
|
4
4
|
import { init } from "md4x/standalone";
|
|
5
5
|
import { parseViewerDocument } from "../parseViewer.ts";
|
|
6
6
|
import type { ViewerDocument } from "../parseViewer.ts";
|
|
7
|
+
import { WORKING } from "../changes.ts";
|
|
8
|
+
import {
|
|
9
|
+
ChangesLoaderContext,
|
|
10
|
+
type ChangesLoader,
|
|
11
|
+
} from "../viewer/changesSource.ts";
|
|
7
12
|
import { ViewerApp } from "../viewer/ViewerApp.tsx";
|
|
8
13
|
import { loadGistMarkdown, type GistDocument } from "./fetchGist.ts";
|
|
9
14
|
import { GistPinContext } from "./pin.ts";
|
|
10
|
-
import {
|
|
15
|
+
import { HostHome } from "../host/HostHome.tsx";
|
|
16
|
+
import { GistLoading, GistStatus } from "./GistStatus.tsx";
|
|
11
17
|
import {
|
|
12
18
|
gistPath,
|
|
13
19
|
parseGistPath,
|
|
@@ -40,6 +46,7 @@ type GistView =
|
|
|
40
46
|
gist: GistDocument;
|
|
41
47
|
file: string;
|
|
42
48
|
document: ViewerDocument;
|
|
49
|
+
loadChanges: ChangesLoader;
|
|
43
50
|
};
|
|
44
51
|
|
|
45
52
|
export function GistApp() {
|
|
@@ -83,7 +90,7 @@ export function GistApp() {
|
|
|
83
90
|
return () => abort.abort();
|
|
84
91
|
}, [route]);
|
|
85
92
|
|
|
86
|
-
if (route.kind === "home") return <
|
|
93
|
+
if (route.kind === "home") return <HostHome />;
|
|
87
94
|
if (route.kind === "invalid") {
|
|
88
95
|
return (
|
|
89
96
|
<GistStatus title="Not a gist id">
|
|
@@ -159,24 +166,26 @@ export function GistApp() {
|
|
|
159
166
|
htmlUrl: view.gist.htmlUrl,
|
|
160
167
|
}}
|
|
161
168
|
>
|
|
162
|
-
<
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
<ChangesLoaderContext.Provider value={view.loadChanges}>
|
|
170
|
+
<ViewerApp
|
|
171
|
+
key={`${view.gist.id}:${view.file}`}
|
|
172
|
+
document={view.document}
|
|
173
|
+
mode="gist"
|
|
174
|
+
headerActions={
|
|
175
|
+
<GistHeader
|
|
176
|
+
gistId={view.gist.id}
|
|
177
|
+
htmlUrl={view.gist.htmlUrl}
|
|
178
|
+
files={view.gist.files}
|
|
179
|
+
current={view.file}
|
|
180
|
+
onSelect={(file) => {
|
|
181
|
+
const next = gistPath(view.gist.id, file);
|
|
182
|
+
window.history.pushState({}, "", next);
|
|
183
|
+
setRoute(parseGistPath(next));
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
}
|
|
187
|
+
/>
|
|
188
|
+
</ChangesLoaderContext.Provider>
|
|
180
189
|
</GistPinContext.Provider>
|
|
181
190
|
);
|
|
182
191
|
}
|
|
@@ -217,11 +226,21 @@ async function loadView(
|
|
|
217
226
|
message: parsed.message,
|
|
218
227
|
};
|
|
219
228
|
}
|
|
229
|
+
const snapshot = loaded.gist.changes;
|
|
220
230
|
return {
|
|
221
231
|
kind: "ready",
|
|
222
232
|
gist: loaded.gist,
|
|
223
233
|
file: loaded.gist.chosen.name,
|
|
224
234
|
document: parsed,
|
|
235
|
+
loadChanges: (rev) =>
|
|
236
|
+
Promise.resolve(
|
|
237
|
+
snapshot[rev] ??
|
|
238
|
+
new Error(
|
|
239
|
+
rev === WORKING
|
|
240
|
+
? "Uncommitted changes are only available in a local viewer."
|
|
241
|
+
: `This gist has no snapshot of ${rev}. Share the spec again with diffmap share.`,
|
|
242
|
+
),
|
|
243
|
+
),
|
|
225
244
|
};
|
|
226
245
|
}
|
|
227
246
|
|
package/src/gist/GistStatus.tsx
CHANGED
|
@@ -61,52 +61,6 @@ export function GistLoading(props: { title?: string }) {
|
|
|
61
61
|
);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export function GistLanding() {
|
|
65
|
-
const code = useStyles(styles.code);
|
|
66
|
-
return (
|
|
67
|
-
<GistStatus title="diffmap">
|
|
68
|
-
<P>
|
|
69
|
-
A hosted viewer for diffmap walkthroughs. This page fetches a GitHub
|
|
70
|
-
gist or a public repo file in your browser. There is no server holding
|
|
71
|
-
the markdown.
|
|
72
|
-
</P>
|
|
73
|
-
<P>
|
|
74
|
-
Canonical gist URL:{" "}
|
|
75
|
-
<code className={code}>https://diffmap.dev/g/<gistId></code>.
|
|
76
|
-
Optional file:{" "}
|
|
77
|
-
<code className={code}>/g/<gistId>/<file.md></code>. GitHub
|
|
78
|
-
spec:{" "}
|
|
79
|
-
<code className={code}>
|
|
80
|
-
https://diffmap.dev/<owner>/<repo>/pull/<n>/<path.md>
|
|
81
|
-
</code>{" "}
|
|
82
|
-
or{" "}
|
|
83
|
-
<code className={code}>
|
|
84
|
-
/<owner>/<repo>/commit/<sha>/<path.md>
|
|
85
|
-
</code>
|
|
86
|
-
. Heading hashes stay in the fragment, same as the local viewer.
|
|
87
|
-
</P>
|
|
88
|
-
<P>
|
|
89
|
-
Share a secret gist (requires <code className={code}>gh</code>
|
|
90
|
-
):
|
|
91
|
-
</P>
|
|
92
|
-
<P>
|
|
93
|
-
<code className={code}>
|
|
94
|
-
npx @tanishqkancharla/diffmap share path/to.md
|
|
95
|
-
</code>
|
|
96
|
-
</P>
|
|
97
|
-
<P>
|
|
98
|
-
Secret gists are unlisted, not private. Anyone with the id can read
|
|
99
|
-
them. Unauthenticated GitHub API is 60 requests per hour per IP.
|
|
100
|
-
</P>
|
|
101
|
-
<P>
|
|
102
|
-
<Link href="https://github.com/tanishqkancharla/diffmap">
|
|
103
|
-
tanishqkancharla/diffmap
|
|
104
|
-
</Link>
|
|
105
|
-
</P>
|
|
106
|
-
</GistStatus>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
64
|
const styles = {
|
|
111
65
|
shell: style(flex({ direction: "column" }), {
|
|
112
66
|
width: "100%",
|
|
@@ -117,7 +71,7 @@ const styles = {
|
|
|
117
71
|
backgroundColor: colors.gray[4],
|
|
118
72
|
}),
|
|
119
73
|
header: style(
|
|
120
|
-
flex({ direction: "row",
|
|
74
|
+
flex({ direction: "row", alignItems: "center", justifyContent: "between" }),
|
|
121
75
|
spacing.padding({ x: 6, y: 3 }),
|
|
122
76
|
flexItem({ size: "hug" }),
|
|
123
77
|
border(["bottom"], "border"),
|
|
@@ -142,10 +96,7 @@ const styles = {
|
|
|
142
96
|
title: style(text({ size: "xl", fontWeight: 600, color: "highContrast" }), {
|
|
143
97
|
marginBottom: spacing.value(4),
|
|
144
98
|
}),
|
|
145
|
-
loading: style(flex({ direction: "row",
|
|
99
|
+
loading: style(flex({ direction: "row", alignItems: "center" }), {
|
|
146
100
|
minHeight: spacing.value(12),
|
|
147
101
|
}),
|
|
148
|
-
code: style(text({ size: "sm", color: "highContrast" }), {
|
|
149
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
150
|
-
}),
|
|
151
102
|
};
|
package/src/gist/fetchGist.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GIST_CHANGES_FILE, type Changes, type Rev } from "../changes.ts";
|
|
1
2
|
import { githubGistUrl, markdownFileNames, pickMarkdownFile } from "./route.ts";
|
|
2
3
|
|
|
3
4
|
export type GistMarkdownFile = {
|
|
@@ -10,6 +11,8 @@ export type GistDocument = {
|
|
|
10
11
|
htmlUrl: string;
|
|
11
12
|
files: GistMarkdownFile[];
|
|
12
13
|
chosen: GistMarkdownFile;
|
|
14
|
+
/** Snapshot `diffmap share` saved for each commit the spec references. */
|
|
15
|
+
changes: Record<Rev, Changes>;
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
export type GistLoadResult =
|
|
@@ -95,6 +98,25 @@ export async function loadGistMarkdown(
|
|
|
95
98
|
return { status: "missing-file", htmlUrl, files };
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
const snapshotFile = apiFiles[GIST_CHANGES_FILE];
|
|
102
|
+
const snapshot =
|
|
103
|
+
snapshotFile === undefined
|
|
104
|
+
? "{}"
|
|
105
|
+
: await readGistFile(snapshotFile, signal);
|
|
106
|
+
if (snapshot instanceof Error) {
|
|
107
|
+
return { status: "error", message: snapshot.message };
|
|
108
|
+
}
|
|
109
|
+
let changes: Record<Rev, Changes>;
|
|
110
|
+
try {
|
|
111
|
+
// SAFETY: diffmap share writes this file from readDocumentChanges.
|
|
112
|
+
changes = JSON.parse(snapshot) as Record<Rev, Changes>;
|
|
113
|
+
} catch {
|
|
114
|
+
return {
|
|
115
|
+
status: "error",
|
|
116
|
+
message: `${GIST_CHANGES_FILE} is not valid JSON.`,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
98
120
|
return {
|
|
99
121
|
status: "ok",
|
|
100
122
|
gist: {
|
|
@@ -102,6 +124,7 @@ export async function loadGistMarkdown(
|
|
|
102
124
|
htmlUrl,
|
|
103
125
|
files,
|
|
104
126
|
chosen,
|
|
127
|
+
changes,
|
|
105
128
|
},
|
|
106
129
|
};
|
|
107
130
|
}
|