@peaske7/readit 0.1.8 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/CLAUDE.md +118 -76
- package/.claude/commands/review.md +1 -1
- package/.claude/roadmap.md +32 -9
- package/.claude/user-stories.md +100 -15
- package/AGENTS.md +30 -26
- package/Makefile +32 -0
- package/README.md +90 -5
- package/biome.json +18 -8
- package/bun.lock +426 -710
- package/bunfig.toml +2 -0
- package/docs/perf-baseline.md +130 -0
- package/docs/superpowers/plans/2026-03-26-surgical-pruning.md +1176 -0
- package/docs/superpowers/specs/2026-03-27-go-server-rewrite-design.md +284 -0
- package/e2e/comments.spec.ts +14 -58
- package/e2e/document-load.spec.ts +1 -23
- package/e2e/export.spec.ts +4 -4
- package/e2e/perf/add-comment.spec.ts +116 -0
- package/e2e/perf/fixtures/generate.ts +327 -0
- package/e2e/perf/initial-load.spec.ts +49 -0
- package/e2e/perf/perf.setup.ts +23 -0
- package/e2e/perf/perf.teardown.ts +9 -0
- package/e2e/perf/screenshot-final.png +0 -0
- package/e2e/perf/scroll.spec.ts +39 -0
- package/e2e/perf/tab-switch.spec.ts +69 -0
- package/e2e/perf/text-selection.spec.ts +119 -0
- package/e2e/perf/utils/metrics.ts +350 -0
- package/e2e/perf/utils/perf-cli.ts +86 -0
- package/e2e/persistence-file.spec.ts +41 -26
- package/e2e/utils/selection.ts +17 -73
- package/go/cmd/readit/main.go +416 -0
- package/go/go.mod +20 -0
- package/go/go.sum +41 -0
- package/go/internal/server/anchor.go +302 -0
- package/go/internal/server/anchor_test.go +111 -0
- package/go/internal/server/comments.go +390 -0
- package/go/internal/server/documents.go +113 -0
- package/go/internal/server/embed.go +17 -0
- package/go/internal/server/headings.go +33 -0
- package/go/internal/server/headings_test.go +75 -0
- package/go/internal/server/htmltext.go +123 -0
- package/go/internal/server/markdown.go +157 -0
- package/go/internal/server/markdown_bench_test.go +42 -0
- package/go/internal/server/markdown_test.go +79 -0
- package/go/internal/server/server.go +453 -0
- package/go/internal/server/server_bench_test.go +122 -0
- package/go/internal/server/settings.go +110 -0
- package/go/internal/server/sse.go +140 -0
- package/go/internal/server/storage.go +275 -0
- package/go/internal/server/storage_test.go +118 -0
- package/go/internal/server/template.go +66 -0
- package/go/internal/server/types.go +101 -0
- package/go/internal/server/watcher.go +74 -0
- package/index.html +4 -14
- package/nvim-readit/lua/readit/health.lua +64 -0
- package/nvim-readit/lua/readit/init.lua +463 -0
- package/nvim-readit/plugin/readit.lua +19 -0
- package/package.json +24 -41
- package/playwright.config.ts +12 -0
- package/shell/_readit +158 -0
- package/shell/readit.zsh +87 -0
- package/src/App.svelte +881 -0
- package/src/{cli/index.ts → cli.ts} +216 -70
- package/src/components/ActionsMenu.svelte +95 -0
- package/src/components/CommentBadge.svelte +67 -0
- package/src/components/CommentErrorBanner.svelte +33 -0
- package/src/components/CommentInput.svelte +75 -0
- package/src/components/CommentListItem.svelte +95 -0
- package/src/components/CommentManager.svelte +129 -0
- package/src/components/CommentNav.svelte +109 -0
- package/src/components/DocumentViewer.svelte +218 -0
- package/src/components/FloatingComment.svelte +107 -0
- package/src/components/Header.svelte +76 -0
- package/src/components/InlineEditor.svelte +72 -0
- package/src/components/MarginNote.svelte +167 -0
- package/src/components/MarginNotesContainer.svelte +33 -0
- package/src/components/RawModal.svelte +126 -0
- package/src/components/ReanchorConfirm.svelte +30 -0
- package/src/components/SettingsModal.svelte +220 -0
- package/src/components/ShortcutCapture.svelte +82 -0
- package/src/components/ShortcutList.svelte +145 -0
- package/src/components/TabBar.svelte +52 -0
- package/src/components/TableOfContents.svelte +125 -0
- package/src/components/ui/ActionLink.svelte +40 -0
- package/src/components/ui/Button.svelte +53 -0
- package/src/components/ui/Dialog.svelte +97 -0
- package/src/components/ui/DropdownMenu.svelte +85 -0
- package/src/components/ui/DropdownMenuItem.svelte +38 -0
- package/src/components/ui/DropdownMenuSeparator.svelte +11 -0
- package/src/components/ui/Text.svelte +42 -0
- package/src/env.d.ts +6 -0
- package/src/index.css +36 -166
- package/src/lib/__fixtures__/bench-data.ts +1 -54
- package/src/lib/anchor.bench.ts +47 -68
- package/src/lib/anchor.test.ts +5 -9
- package/src/lib/anchor.ts +9 -93
- package/src/lib/comment-storage.bench.ts +6 -20
- package/src/lib/comment-storage.test.ts +45 -37
- package/src/lib/comment-storage.ts +23 -64
- package/src/lib/export.bench.ts +9 -23
- package/src/lib/export.ts +7 -14
- package/src/lib/headings.test.ts +103 -0
- package/src/lib/headings.ts +44 -0
- package/src/lib/highlight/core.test.ts +1 -6
- package/src/lib/highlight/dom.ts +53 -280
- package/src/lib/highlight/highlight-registry.ts +221 -0
- package/src/lib/highlight/highlight.bench.ts +92 -0
- package/src/lib/highlight/highlighter.ts +122 -302
- package/src/lib/highlight/{core.ts → resolver.ts} +3 -19
- package/src/lib/highlight/types.ts +0 -40
- package/src/lib/html-text.test.ts +162 -0
- package/src/lib/html-text.ts +161 -0
- package/src/lib/i18n/en.ts +13 -36
- package/src/lib/i18n/ja.ts +14 -37
- package/src/lib/i18n/types.ts +13 -36
- package/src/lib/margin-layout.bench.ts +48 -15
- package/src/lib/margin-layout.ts +2 -31
- package/src/lib/markdown-renderer.test.ts +154 -0
- package/src/lib/markdown-renderer.ts +177 -0
- package/src/lib/mermaid-config.ts +38 -0
- package/src/lib/mermaid-renderer.ts +162 -0
- package/src/lib/mermaid-worker.ts +60 -0
- package/src/lib/positions.ts +157 -0
- package/src/lib/shortcut-registry.ts +138 -103
- package/src/lib/utils.ts +2 -48
- package/src/main.ts +16 -0
- package/src/schema.ts +92 -0
- package/src/{server/index.ts → server.ts} +427 -163
- package/src/stores/app.svelte.ts +231 -0
- package/src/stores/locale.svelte.ts +46 -0
- package/src/stores/settings.svelte.ts +90 -0
- package/src/stores/shortcuts.svelte.ts +104 -0
- package/src/stores/ui.svelte.ts +12 -0
- package/src/template.ts +104 -0
- package/src/test-setup.ts +47 -0
- package/svelte.config.js +5 -0
- package/tsconfig.json +2 -2
- package/vite.config.ts +31 -3
- package/vscode-readit/.mcp.json +7 -0
- package/vscode-readit/.vscodeignore +7 -0
- package/vscode-readit/bun.lock +78 -0
- package/vscode-readit/icon.svg +10 -0
- package/vscode-readit/package.json +110 -0
- package/vscode-readit/src/extension.ts +117 -0
- package/vscode-readit/src/server-manager.ts +272 -0
- package/vscode-readit/src/webview-provider.ts +204 -0
- package/vscode-readit/tsconfig.json +20 -0
- package/e2e/fixtures/sample.html +0 -13
- package/src/App.tsx +0 -416
- package/src/components/ActionsMenu.tsx +0 -112
- package/src/components/DocumentViewer/CodeBlock.tsx +0 -160
- package/src/components/DocumentViewer/DocumentViewer.tsx +0 -259
- package/src/components/DocumentViewer/IframeContainer.tsx +0 -251
- package/src/components/DocumentViewer/InlineCode.tsx +0 -60
- package/src/components/DocumentViewer/MermaidDiagram.tsx +0 -137
- package/src/components/DocumentViewer/index.ts +0 -1
- package/src/components/FloatingTOC.tsx +0 -61
- package/src/components/Header.tsx +0 -65
- package/src/components/InlineEditor.tsx +0 -74
- package/src/components/MarginNote.tsx +0 -207
- package/src/components/MarginNotes.tsx +0 -50
- package/src/components/RawModal.tsx +0 -143
- package/src/components/ReanchorConfirm.tsx +0 -36
- package/src/components/SettingsModal.tsx +0 -310
- package/src/components/ShortcutCapture.tsx +0 -48
- package/src/components/ShortcutList.tsx +0 -198
- package/src/components/TabBar.tsx +0 -60
- package/src/components/TableOfContents.tsx +0 -108
- package/src/components/comments/CommentBadge.tsx +0 -49
- package/src/components/comments/CommentInput.tsx +0 -114
- package/src/components/comments/CommentListItem.tsx +0 -92
- package/src/components/comments/CommentManager.tsx +0 -113
- package/src/components/comments/CommentMinimap.tsx +0 -62
- package/src/components/comments/CommentNav.tsx +0 -109
- package/src/components/ui/ActionBar.tsx +0 -16
- package/src/components/ui/ActionLink.tsx +0 -32
- package/src/components/ui/Button.tsx +0 -55
- package/src/components/ui/Dialog.tsx +0 -156
- package/src/components/ui/DropdownMenu.tsx +0 -114
- package/src/components/ui/SeparatorDot.tsx +0 -9
- package/src/components/ui/Text.tsx +0 -54
- package/src/contexts/CommentContext.tsx +0 -229
- package/src/contexts/LayoutContext.tsx +0 -88
- package/src/contexts/LocaleContext.tsx +0 -35
- package/src/hooks/useClickOutside.ts +0 -35
- package/src/hooks/useClipboard.ts +0 -82
- package/src/hooks/useCommentNavigation.ts +0 -130
- package/src/hooks/useComments.ts +0 -323
- package/src/hooks/useDocument.ts +0 -156
- package/src/hooks/useEditorScheme.ts +0 -51
- package/src/hooks/useFontPreference.ts +0 -59
- package/src/hooks/useHeadings.test.ts +0 -159
- package/src/hooks/useHeadings.ts +0 -129
- package/src/hooks/useKeybindings.ts +0 -108
- package/src/hooks/useKeyboardShortcuts.ts +0 -63
- package/src/hooks/useLayoutMode.ts +0 -44
- package/src/hooks/useLocalePreference.ts +0 -42
- package/src/hooks/useReanchorMode.ts +0 -33
- package/src/hooks/useScrollMetrics.ts +0 -56
- package/src/hooks/useScrollSpy.ts +0 -81
- package/src/hooks/useTextSelection.ts +0 -123
- package/src/hooks/useThemePreference.ts +0 -66
- package/src/lib/context.bench.ts +0 -41
- package/src/lib/context.test.ts +0 -224
- package/src/lib/context.ts +0 -193
- package/src/lib/editor-links.ts +0 -59
- package/src/lib/highlight/colors.ts +0 -37
- package/src/lib/highlight/index.ts +0 -23
- package/src/lib/highlight/script-builder.ts +0 -485
- package/src/lib/html-processor.test.tsx +0 -170
- package/src/lib/html-processor.tsx +0 -95
- package/src/lib/i18n/completeness.test.ts +0 -51
- package/src/lib/i18n/translations.test.ts +0 -39
- package/src/lib/layout-constants.ts +0 -12
- package/src/lib/scroll.test.ts +0 -118
- package/src/lib/scroll.ts +0 -47
- package/src/lib/shortcut-registry.test.ts +0 -173
- package/src/lib/utils.test.ts +0 -110
- package/src/main.tsx +0 -13
- package/src/store/index.test.ts +0 -242
- package/src/store/index.ts +0 -254
- package/src/types/index.ts +0 -127
package/vite.config.ts
CHANGED
|
@@ -1,10 +1,37 @@
|
|
|
1
|
+
import { svelte } from "@sveltejs/vite-plugin-svelte";
|
|
1
2
|
import tailwindcss from "@tailwindcss/vite";
|
|
2
|
-
import
|
|
3
|
-
|
|
3
|
+
import { createLogger, defineConfig } from "vite";
|
|
4
|
+
|
|
5
|
+
// Suppress lightningcss warnings about ::highlight() pseudo-element.
|
|
6
|
+
// The CSS Custom Highlight API is a valid standard; lightningcss doesn't
|
|
7
|
+
// recognise it yet (fix merged upstream, pending release).
|
|
8
|
+
const logger = createLogger();
|
|
9
|
+
const originalWarn = logger.warn.bind(logger);
|
|
10
|
+
logger.warn = (msg, options) => {
|
|
11
|
+
if (msg.includes("::highlight")) return;
|
|
12
|
+
originalWarn(msg, options);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Tailwind CSS v4 calls console.warn directly for lightningcss warnings
|
|
16
|
+
// (bypassing Vite's logger), so we also intercept that.
|
|
17
|
+
const originalConsoleWarn = console.warn.bind(console);
|
|
18
|
+
console.warn = (...args: unknown[]) => {
|
|
19
|
+
if (typeof args[0] === "string" && args[0].includes("::highlight")) return;
|
|
20
|
+
originalConsoleWarn(...args);
|
|
21
|
+
};
|
|
4
22
|
|
|
5
23
|
export default defineConfig({
|
|
6
|
-
|
|
24
|
+
customLogger: logger,
|
|
25
|
+
plugins: [tailwindcss(), svelte()],
|
|
7
26
|
server: {
|
|
27
|
+
port: 24678,
|
|
28
|
+
strictPort: true,
|
|
29
|
+
host: "127.0.0.1",
|
|
30
|
+
hmr: {
|
|
31
|
+
host: "127.0.0.1",
|
|
32
|
+
port: 24678,
|
|
33
|
+
clientPort: 24678,
|
|
34
|
+
},
|
|
8
35
|
proxy: {
|
|
9
36
|
"/api": {
|
|
10
37
|
target: "http://localhost:4567",
|
|
@@ -15,5 +42,6 @@ export default defineConfig({
|
|
|
15
42
|
build: {
|
|
16
43
|
outDir: "dist",
|
|
17
44
|
emptyOutDir: true,
|
|
45
|
+
manifest: true,
|
|
18
46
|
},
|
|
19
47
|
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "vscode-readit",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/node": "^25.5.2",
|
|
9
|
+
"@types/vscode": "^1.110.0",
|
|
10
|
+
"esbuild": "^0.25.12",
|
|
11
|
+
"typescript": "^5.9.3",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
"packages": {
|
|
16
|
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "https://npm.flatt.tech/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
|
|
17
|
+
|
|
18
|
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "https://npm.flatt.tech/@esbuild/android-arm/-/android-arm-0.25.12.tgz", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
|
|
19
|
+
|
|
20
|
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
|
|
21
|
+
|
|
22
|
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "https://npm.flatt.tech/@esbuild/android-x64/-/android-x64-0.25.12.tgz", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
|
|
23
|
+
|
|
24
|
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
|
|
25
|
+
|
|
26
|
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "https://npm.flatt.tech/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
|
|
27
|
+
|
|
28
|
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
|
|
29
|
+
|
|
30
|
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
|
|
31
|
+
|
|
32
|
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "https://npm.flatt.tech/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
|
|
33
|
+
|
|
34
|
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
|
|
35
|
+
|
|
36
|
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "https://npm.flatt.tech/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
|
|
37
|
+
|
|
38
|
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
|
|
39
|
+
|
|
40
|
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "https://npm.flatt.tech/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
|
|
41
|
+
|
|
42
|
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
|
|
43
|
+
|
|
44
|
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
|
|
45
|
+
|
|
46
|
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "https://npm.flatt.tech/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
|
|
47
|
+
|
|
48
|
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "https://npm.flatt.tech/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
|
|
49
|
+
|
|
50
|
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
|
|
51
|
+
|
|
52
|
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
|
|
53
|
+
|
|
54
|
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
|
|
55
|
+
|
|
56
|
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "https://npm.flatt.tech/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
|
|
57
|
+
|
|
58
|
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
|
|
59
|
+
|
|
60
|
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "https://npm.flatt.tech/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
|
|
61
|
+
|
|
62
|
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "https://npm.flatt.tech/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
|
|
63
|
+
|
|
64
|
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "https://npm.flatt.tech/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
|
|
65
|
+
|
|
66
|
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "https://npm.flatt.tech/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
|
|
67
|
+
|
|
68
|
+
"@types/node": ["@types/node@25.5.2", "https://npm.flatt.tech/@types/node/-/node-25.5.2.tgz", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg=="],
|
|
69
|
+
|
|
70
|
+
"@types/vscode": ["@types/vscode@1.110.0", "https://npm.flatt.tech/@types/vscode/-/vscode-1.110.0.tgz", {}, "sha512-AGuxUEpU4F4mfuQjxPPaQVyuOMhs+VT/xRok1jiHVBubHK7lBRvCuOMZG0LKUwxncrPorJ5qq/uil3IdZBd5lA=="],
|
|
71
|
+
|
|
72
|
+
"esbuild": ["esbuild@0.25.12", "https://npm.flatt.tech/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
|
|
73
|
+
|
|
74
|
+
"typescript": ["typescript@5.9.3", "https://npm.flatt.tech/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
75
|
+
|
|
76
|
+
"undici-types": ["undici-types@7.18.2", "https://npm.flatt.tech/undici-types/-/undici-types-7.18.2.tgz", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg viewBox="-75 -80 150 160" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<g fill="#E07A50">
|
|
3
|
+
<path d="M-8 -68 C-24 -66, -48 -52, -56 -30 C-64 -8, -58 18, -44 36 C-30 54, -8 66, 14 66 C36 66, 54 52, 60 32 C66 12, 58 -12, 44 -30 C30 -48, 10 -56, -2 -62 C-6 -64, -4 -70, -8 -68Z"/>
|
|
4
|
+
<circle cx="6" cy="-4" r="26" fill="#fff"/>
|
|
5
|
+
<circle cx="6" cy="-4" r="18" fill="#E07A50"/>
|
|
6
|
+
<circle cx="6" cy="-4" r="10" fill="#fff"/>
|
|
7
|
+
<circle cx="10" cy="-6" r="4.5"/>
|
|
8
|
+
<path d="M30 -18 C38 -26, 50 -34, 56 -44 C60 -50, 56 -56, 50 -52 C44 -46, 36 -32, 30 -22Z"/>
|
|
9
|
+
</g>
|
|
10
|
+
</svg>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vscode-readit",
|
|
3
|
+
"displayName": "readit",
|
|
4
|
+
"description": "Review Markdown documents with inline comments — powered by readit",
|
|
5
|
+
"version": "0.0.1",
|
|
6
|
+
"publisher": "peaske7",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/peaske7/readit"
|
|
11
|
+
},
|
|
12
|
+
"engines": {
|
|
13
|
+
"vscode": "^1.85.0"
|
|
14
|
+
},
|
|
15
|
+
"categories": [
|
|
16
|
+
"Other",
|
|
17
|
+
"Visualization"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"markdown",
|
|
21
|
+
"review",
|
|
22
|
+
"comments",
|
|
23
|
+
"preview"
|
|
24
|
+
],
|
|
25
|
+
"activationEvents": [
|
|
26
|
+
"onLanguage:markdown",
|
|
27
|
+
"onCommand:readit.openPreview",
|
|
28
|
+
"onCommand:readit.openPreviewToSide"
|
|
29
|
+
],
|
|
30
|
+
"icon": "icon.svg",
|
|
31
|
+
"main": "./dist/extension.js",
|
|
32
|
+
"contributes": {
|
|
33
|
+
"commands": [
|
|
34
|
+
{
|
|
35
|
+
"command": "readit.openPreview",
|
|
36
|
+
"title": "readit: Open Preview",
|
|
37
|
+
"icon": {
|
|
38
|
+
"light": "icon.svg",
|
|
39
|
+
"dark": "icon.svg"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"command": "readit.openPreviewToSide",
|
|
44
|
+
"title": "readit: Open Preview to the Side",
|
|
45
|
+
"icon": {
|
|
46
|
+
"light": "icon.svg",
|
|
47
|
+
"dark": "icon.svg"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
],
|
|
51
|
+
"menus": {
|
|
52
|
+
"editor/title": [
|
|
53
|
+
{
|
|
54
|
+
"when": "resourceLangId == markdown",
|
|
55
|
+
"command": "readit.openPreviewToSide",
|
|
56
|
+
"group": "navigation"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"explorer/context": [
|
|
60
|
+
{
|
|
61
|
+
"when": "resourceLangId == markdown",
|
|
62
|
+
"command": "readit.openPreview",
|
|
63
|
+
"group": "navigation"
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"editor/context": [
|
|
67
|
+
{
|
|
68
|
+
"when": "resourceLangId == markdown",
|
|
69
|
+
"command": "readit.openPreview",
|
|
70
|
+
"group": "navigation"
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
"keybindings": [
|
|
75
|
+
{
|
|
76
|
+
"command": "readit.openPreviewToSide",
|
|
77
|
+
"key": "ctrl+shift+r",
|
|
78
|
+
"mac": "cmd+shift+r",
|
|
79
|
+
"when": "resourceLangId == markdown"
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
"configuration": {
|
|
83
|
+
"title": "readit",
|
|
84
|
+
"properties": {
|
|
85
|
+
"readit.bunPath": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"default": "bun",
|
|
88
|
+
"description": "Path to the Bun runtime executable."
|
|
89
|
+
},
|
|
90
|
+
"readit.serverPort": {
|
|
91
|
+
"type": "number",
|
|
92
|
+
"default": 0,
|
|
93
|
+
"description": "Port for the readit server. 0 = auto-select a free port."
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"scripts": {
|
|
99
|
+
"build": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --minify",
|
|
100
|
+
"watch": "esbuild src/extension.ts --bundle --outfile=dist/extension.js --external:vscode --format=cjs --platform=node --sourcemap --watch",
|
|
101
|
+
"typecheck": "tsc --noEmit",
|
|
102
|
+
"package": "vsce package"
|
|
103
|
+
},
|
|
104
|
+
"devDependencies": {
|
|
105
|
+
"@types/node": "^25.5.2",
|
|
106
|
+
"@types/vscode": "^1.110.0",
|
|
107
|
+
"esbuild": "^0.25.12",
|
|
108
|
+
"typescript": "^5.9.3"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as vscode from "vscode";
|
|
4
|
+
import { ServerManager } from "./server-manager";
|
|
5
|
+
import { ReaditWebviewProvider } from "./webview-provider";
|
|
6
|
+
|
|
7
|
+
let serverManager: ServerManager | undefined;
|
|
8
|
+
let webviewProvider: ReaditWebviewProvider | undefined;
|
|
9
|
+
let outputChannel: vscode.OutputChannel | undefined;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolves the readit CLI dist/ directory for the mono-repo layout and throws
|
|
13
|
+
* if the built CLI is not available before the extension tries to start it.
|
|
14
|
+
*/
|
|
15
|
+
function resolveReaditDistDir(extensionPath: string): string {
|
|
16
|
+
const monoRepoDist = path.resolve(extensionPath, "..", "dist");
|
|
17
|
+
if (!existsSync(path.join(monoRepoDist, "index.js"))) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
"Could not find readit/dist/index.js. Build the readit CLI before using the VS Code extension.",
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return monoRepoDist;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets the absolute file path from a command argument or the active editor.
|
|
27
|
+
*/
|
|
28
|
+
function resolveFilePath(arg?: vscode.Uri): string | undefined {
|
|
29
|
+
// When invoked from explorer context menu or editor title, arg is a Uri
|
|
30
|
+
if (arg instanceof vscode.Uri) {
|
|
31
|
+
return arg.fsPath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Fall back to active editor
|
|
35
|
+
const editor = vscode.window.activeTextEditor;
|
|
36
|
+
if (editor && editor.document.languageId === "markdown") {
|
|
37
|
+
return editor.document.uri.fsPath;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function activate(context: vscode.ExtensionContext): void {
|
|
44
|
+
outputChannel = vscode.window.createOutputChannel("readit");
|
|
45
|
+
serverManager = new ServerManager(outputChannel);
|
|
46
|
+
webviewProvider = new ReaditWebviewProvider(
|
|
47
|
+
context.extensionUri,
|
|
48
|
+
serverManager,
|
|
49
|
+
outputChannel,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
let readitDistDir: string;
|
|
53
|
+
try {
|
|
54
|
+
readitDistDir = resolveReaditDistDir(context.extensionPath);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
outputChannel.appendLine(message);
|
|
58
|
+
void vscode.window.showErrorMessage(`readit: ${message}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// readit.openPreview — opens in the active column
|
|
63
|
+
const openPreview = vscode.commands.registerCommand(
|
|
64
|
+
"readit.openPreview",
|
|
65
|
+
async (arg?: vscode.Uri) => {
|
|
66
|
+
const filePath = resolveFilePath(arg);
|
|
67
|
+
if (!filePath) {
|
|
68
|
+
vscode.window.showWarningMessage(
|
|
69
|
+
"readit: No Markdown file is open or selected.",
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await webviewProvider!.openPreview(
|
|
75
|
+
filePath,
|
|
76
|
+
readitDistDir,
|
|
77
|
+
vscode.ViewColumn.Active,
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
// readit.openPreviewToSide — opens beside the current editor
|
|
83
|
+
const openPreviewToSide = vscode.commands.registerCommand(
|
|
84
|
+
"readit.openPreviewToSide",
|
|
85
|
+
async (arg?: vscode.Uri) => {
|
|
86
|
+
const filePath = resolveFilePath(arg);
|
|
87
|
+
if (!filePath) {
|
|
88
|
+
vscode.window.showWarningMessage(
|
|
89
|
+
"readit: No Markdown file is open or selected.",
|
|
90
|
+
);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
await webviewProvider!.openPreview(
|
|
95
|
+
filePath,
|
|
96
|
+
readitDistDir,
|
|
97
|
+
vscode.ViewColumn.Beside,
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
context.subscriptions.push(
|
|
103
|
+
outputChannel,
|
|
104
|
+
serverManager,
|
|
105
|
+
webviewProvider,
|
|
106
|
+
openPreview,
|
|
107
|
+
openPreviewToSide,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function deactivate(): void {
|
|
112
|
+
// Disposables registered on context.subscriptions are cleaned up automatically.
|
|
113
|
+
// Explicit cleanup as a safety net:
|
|
114
|
+
webviewProvider?.dispose();
|
|
115
|
+
serverManager?.dispose();
|
|
116
|
+
outputChannel?.dispose();
|
|
117
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { type ChildProcess, spawn } from "node:child_process";
|
|
2
|
+
import * as net from "node:net";
|
|
3
|
+
import * as vscode from "vscode";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manages the lifecycle of a readit Bun server process.
|
|
7
|
+
*
|
|
8
|
+
* Spawns `bun dist/index.js <file> --no-open --port <port>` as a child process.
|
|
9
|
+
* Subsequent files are attached via POST /api/documents to the running server.
|
|
10
|
+
* The server is killed when the extension deactivates or the last panel closes.
|
|
11
|
+
*/
|
|
12
|
+
export class ServerManager implements vscode.Disposable {
|
|
13
|
+
private process: ChildProcess | null = null;
|
|
14
|
+
private port: number | null = null;
|
|
15
|
+
private outputChannel: vscode.OutputChannel;
|
|
16
|
+
private startPromise: Promise<number> | null = null;
|
|
17
|
+
|
|
18
|
+
constructor(outputChannel: vscode.OutputChannel) {
|
|
19
|
+
this.outputChannel = outputChannel;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Returns the port of the running server, or null if not started. */
|
|
23
|
+
getPort(): number | null {
|
|
24
|
+
return this.port;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Returns the base URL of the running server. */
|
|
28
|
+
getBaseUrl(): string | null {
|
|
29
|
+
if (this.port === null) return null;
|
|
30
|
+
return `http://127.0.0.1:${this.port}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** True if the server process is running. */
|
|
34
|
+
isRunning(): boolean {
|
|
35
|
+
return this.process !== null && this.process.exitCode === null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ensures the server is running and the given file is loaded.
|
|
40
|
+
* If the server is not started, spawns it with the file.
|
|
41
|
+
* If already running, attaches the file via the HTTP API.
|
|
42
|
+
* Returns the port number.
|
|
43
|
+
*/
|
|
44
|
+
async ensureRunning(
|
|
45
|
+
filePath: string,
|
|
46
|
+
readitDistDir: string,
|
|
47
|
+
): Promise<number> {
|
|
48
|
+
if (this.startPromise) {
|
|
49
|
+
const port = await this.startPromise;
|
|
50
|
+
await this.attachFile(filePath);
|
|
51
|
+
return port;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (this.isRunning() && this.port !== null) {
|
|
55
|
+
await this.attachFile(filePath);
|
|
56
|
+
return this.port;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return this.start(filePath, readitDistDir);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Starts the Bun server process.
|
|
64
|
+
* Resolves with the port once the server is healthy.
|
|
65
|
+
*/
|
|
66
|
+
private start(filePath: string, readitDistDir: string): Promise<number> {
|
|
67
|
+
this.startPromise = this.doStart(filePath, readitDistDir);
|
|
68
|
+
return this.startPromise;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async doStart(
|
|
72
|
+
filePath: string,
|
|
73
|
+
readitDistDir: string,
|
|
74
|
+
): Promise<number> {
|
|
75
|
+
const config = vscode.workspace.getConfiguration("readit");
|
|
76
|
+
const bunPath = config.get<string>("bunPath", "bun");
|
|
77
|
+
const configPort = config.get<number>("serverPort", 0);
|
|
78
|
+
|
|
79
|
+
const port = configPort > 0 ? configPort : await findFreePort();
|
|
80
|
+
|
|
81
|
+
const cliEntry = vscode.Uri.joinPath(
|
|
82
|
+
vscode.Uri.file(readitDistDir),
|
|
83
|
+
"index.js",
|
|
84
|
+
).fsPath;
|
|
85
|
+
|
|
86
|
+
const args = [cliEntry, filePath, "--no-open", "--port", String(port)];
|
|
87
|
+
|
|
88
|
+
this.outputChannel.appendLine(
|
|
89
|
+
`Starting readit server: ${bunPath} ${args.join(" ")}`,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return new Promise<number>((resolve, reject) => {
|
|
93
|
+
const child = spawn(bunPath, args, {
|
|
94
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
95
|
+
env: {
|
|
96
|
+
...process.env,
|
|
97
|
+
NODE_ENV: "production",
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
this.process = child;
|
|
102
|
+
let settled = false;
|
|
103
|
+
let timer: NodeJS.Timeout | undefined;
|
|
104
|
+
|
|
105
|
+
const rejectStartup = (message: string) => {
|
|
106
|
+
if (settled) return;
|
|
107
|
+
settled = true;
|
|
108
|
+
if (timer) {
|
|
109
|
+
clearInterval(timer);
|
|
110
|
+
}
|
|
111
|
+
this.cleanup(child);
|
|
112
|
+
reject(new Error(message));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
116
|
+
this.outputChannel.appendLine(data.toString().trimEnd());
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
child.stderr?.on("data", (data: Buffer) => {
|
|
120
|
+
this.outputChannel.appendLine(`[stderr] ${data.toString().trimEnd()}`);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
child.on("error", (err) => {
|
|
124
|
+
this.outputChannel.appendLine(`Server process error: ${err.message}`);
|
|
125
|
+
rejectStartup(
|
|
126
|
+
`Failed to start readit server. Is Bun installed? (${err.message})`,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const handleProcessExit = (
|
|
131
|
+
code: number | null,
|
|
132
|
+
signal: NodeJS.Signals | null,
|
|
133
|
+
) => {
|
|
134
|
+
this.outputChannel.appendLine(
|
|
135
|
+
`Server process exited with code ${code}${signal ? ` (signal: ${signal})` : ""}`,
|
|
136
|
+
);
|
|
137
|
+
if (!settled) {
|
|
138
|
+
rejectStartup(
|
|
139
|
+
`readit server exited before it became ready (${code === null ? (signal ?? "unknown") : `code ${code}`})`,
|
|
140
|
+
);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this.cleanup(child);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
child.on("exit", handleProcessExit);
|
|
147
|
+
|
|
148
|
+
// Poll for server readiness
|
|
149
|
+
const maxWaitMs = 15_000;
|
|
150
|
+
const intervalMs = 200;
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
|
|
153
|
+
timer = setInterval(async () => {
|
|
154
|
+
if (Date.now() - startTime > maxWaitMs) {
|
|
155
|
+
clearInterval(timer);
|
|
156
|
+
child.kill("SIGTERM");
|
|
157
|
+
rejectStartup("readit server failed to start within 15 seconds");
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch(`http://127.0.0.1:${port}/api/health`);
|
|
163
|
+
if (res.ok) {
|
|
164
|
+
clearInterval(timer);
|
|
165
|
+
settled = true;
|
|
166
|
+
this.port = port;
|
|
167
|
+
this.outputChannel.appendLine(`Server ready on port ${port}`);
|
|
168
|
+
resolve(port);
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Not ready yet, keep polling
|
|
172
|
+
}
|
|
173
|
+
}, intervalMs);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Attaches a file to the running server via POST /api/documents.
|
|
179
|
+
*/
|
|
180
|
+
private async attachFile(filePath: string): Promise<void> {
|
|
181
|
+
if (this.port === null) return;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const res = await fetch(`http://127.0.0.1:${this.port}/api/documents`, {
|
|
185
|
+
method: "POST",
|
|
186
|
+
headers: { "Content-Type": "application/json" },
|
|
187
|
+
body: JSON.stringify({ path: filePath }),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
const raw = await res.text();
|
|
192
|
+
let errorMessage = res.statusText;
|
|
193
|
+
if (raw) {
|
|
194
|
+
try {
|
|
195
|
+
const data = JSON.parse(raw) as { error?: string };
|
|
196
|
+
errorMessage = data.error ?? raw;
|
|
197
|
+
} catch {
|
|
198
|
+
errorMessage = raw;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
this.outputChannel.appendLine(
|
|
202
|
+
`Failed to attach file ${filePath}: ${errorMessage}`,
|
|
203
|
+
);
|
|
204
|
+
} else {
|
|
205
|
+
const data = (await res.json()) as {
|
|
206
|
+
status?: string;
|
|
207
|
+
fileName?: string;
|
|
208
|
+
};
|
|
209
|
+
this.outputChannel.appendLine(
|
|
210
|
+
`File ${data.fileName ?? filePath}: ${data.status ?? "attached"}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
} catch (err) {
|
|
214
|
+
this.outputChannel.appendLine(
|
|
215
|
+
`Error attaching file: ${err instanceof Error ? err.message : String(err)}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** Stops the server process. */
|
|
221
|
+
stop(): void {
|
|
222
|
+
const child = this.process;
|
|
223
|
+
if (child) {
|
|
224
|
+
this.outputChannel.appendLine("Stopping readit server...");
|
|
225
|
+
child.kill("SIGTERM");
|
|
226
|
+
let hasExited = false;
|
|
227
|
+
|
|
228
|
+
// Force kill after 3 seconds
|
|
229
|
+
const forceKillTimer = setTimeout(() => {
|
|
230
|
+
if (!hasExited && child.exitCode === null) {
|
|
231
|
+
child.kill("SIGKILL");
|
|
232
|
+
}
|
|
233
|
+
}, 3_000);
|
|
234
|
+
|
|
235
|
+
child.once("exit", () => {
|
|
236
|
+
hasExited = true;
|
|
237
|
+
clearTimeout(forceKillTimer);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
this.cleanup();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
private cleanup(child?: ChildProcess): void {
|
|
244
|
+
if (child && this.process !== child) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.process = null;
|
|
248
|
+
this.port = null;
|
|
249
|
+
this.startPromise = null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
dispose(): void {
|
|
253
|
+
this.stop();
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** Finds a free TCP port by binding to port 0 and reading the assigned port. */
|
|
258
|
+
function findFreePort(): Promise<number> {
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
const server = net.createServer();
|
|
261
|
+
server.listen(0, "127.0.0.1", () => {
|
|
262
|
+
const addr = server.address();
|
|
263
|
+
if (addr && typeof addr === "object") {
|
|
264
|
+
const port = addr.port;
|
|
265
|
+
server.close(() => resolve(port));
|
|
266
|
+
} else {
|
|
267
|
+
server.close(() => reject(new Error("Could not determine free port")));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
server.on("error", reject);
|
|
271
|
+
});
|
|
272
|
+
}
|