@shivaedev/work-board 0.0.0 → 0.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +146 -2
- package/dist/board.d.ts +8 -0
- package/dist/board.d.ts.map +1 -0
- package/dist/board.js +27 -0
- package/dist/board.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +17 -0
- package/dist/cli.js.map +1 -0
- package/dist/files/changes.d.ts +17 -0
- package/dist/files/changes.d.ts.map +1 -0
- package/dist/files/changes.js +38 -0
- package/dist/files/changes.js.map +1 -0
- package/dist/files/home.d.ts +11 -0
- package/dist/files/home.d.ts.map +1 -0
- package/dist/files/home.js +33 -0
- package/dist/files/home.js.map +1 -0
- package/dist/files/list.d.ts +9 -0
- package/dist/files/list.d.ts.map +1 -0
- package/dist/files/list.js +38 -0
- package/dist/files/list.js.map +1 -0
- package/dist/http/assets.d.ts +15 -0
- package/dist/http/assets.d.ts.map +1 -0
- package/dist/http/assets.js +32 -0
- package/dist/http/assets.js.map +1 -0
- package/dist/http/events.d.ts +5 -0
- package/dist/http/events.d.ts.map +1 -0
- package/dist/http/events.js +17 -0
- package/dist/http/events.js.map +1 -0
- package/dist/http/loopback.d.ts +4 -0
- package/dist/http/loopback.d.ts.map +1 -0
- package/dist/http/loopback.js +22 -0
- package/dist/http/loopback.js.map +1 -0
- package/dist/http/page.d.ts +9 -0
- package/dist/http/page.d.ts.map +1 -0
- package/dist/http/page.js +43 -0
- package/dist/http/page.js.map +1 -0
- package/dist/http/respond.d.ts +8 -0
- package/dist/http/respond.d.ts.map +1 -0
- package/dist/http/respond.js +15 -0
- package/dist/http/respond.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/page/board.d.ts +4 -0
- package/dist/page/board.d.ts.map +1 -0
- package/dist/page/board.js +32 -0
- package/dist/page/board.js.map +1 -0
- package/dist/page/client.d.ts +2 -0
- package/dist/page/client.d.ts.map +1 -0
- package/dist/page/client.js +91 -0
- package/dist/page/client.js.map +1 -0
- package/dist/page/diagrams.d.ts +2 -0
- package/dist/page/diagrams.d.ts.map +1 -0
- package/dist/page/diagrams.js +73 -0
- package/dist/page/diagrams.js.map +1 -0
- package/dist/page/document.d.ts +4 -0
- package/dist/page/document.d.ts.map +1 -0
- package/dist/page/document.js +9 -0
- package/dist/page/document.js.map +1 -0
- package/dist/page/escape.d.ts +2 -0
- package/dist/page/escape.d.ts.map +1 -0
- package/dist/page/escape.js +3 -0
- package/dist/page/escape.js.map +1 -0
- package/dist/page/nav.d.ts +3 -0
- package/dist/page/nav.d.ts.map +1 -0
- package/dist/page/nav.js +22 -0
- package/dist/page/nav.js.map +1 -0
- package/dist/page/shell.d.ts +2 -0
- package/dist/page/shell.d.ts.map +1 -0
- package/dist/page/shell.js +11 -0
- package/dist/page/shell.js.map +1 -0
- package/dist/page/style.d.ts +2 -0
- package/dist/page/style.d.ts.map +1 -0
- package/dist/page/style.js +75 -0
- package/dist/page/style.js.map +1 -0
- package/dist/page/swap.d.ts +2 -0
- package/dist/page/swap.d.ts.map +1 -0
- package/dist/page/swap.js +123 -0
- package/dist/page/swap.js.map +1 -0
- package/dist/page/updated.d.ts +2 -0
- package/dist/page/updated.d.ts.map +1 -0
- package/dist/page/updated.js +2 -0
- package/dist/page/updated.js.map +1 -0
- package/dist/render/board.d.ts +20 -0
- package/dist/render/board.d.ts.map +1 -0
- package/dist/render/board.js +54 -0
- package/dist/render/board.js.map +1 -0
- package/dist/render/code.d.ts +10 -0
- package/dist/render/code.d.ts.map +1 -0
- package/dist/render/code.js +47 -0
- package/dist/render/code.js.map +1 -0
- package/dist/render/failed.d.ts +9 -0
- package/dist/render/failed.d.ts.map +1 -0
- package/dist/render/failed.js +4 -0
- package/dist/render/failed.js.map +1 -0
- package/dist/render/footnotes.d.ts +19 -0
- package/dist/render/footnotes.d.ts.map +1 -0
- package/dist/render/footnotes.js +23 -0
- package/dist/render/footnotes.js.map +1 -0
- package/dist/render/highlighter.d.ts +13 -0
- package/dist/render/highlighter.d.ts.map +1 -0
- package/dist/render/highlighter.js +11 -0
- package/dist/render/highlighter.js.map +1 -0
- package/dist/render/markdown.d.ts +5 -0
- package/dist/render/markdown.d.ts.map +1 -0
- package/dist/render/markdown.js +15 -0
- package/dist/render/markdown.js.map +1 -0
- package/dist/serve.d.ts +9 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/serve.js +9 -0
- package/dist/serve.js.map +1 -0
- package/package.json +60 -3
- package/src/board.ts +44 -0
- package/src/cli.ts +24 -0
- package/src/files/changes.ts +71 -0
- package/src/files/home.ts +37 -0
- package/src/files/list.ts +55 -0
- package/src/http/assets.ts +38 -0
- package/src/http/events.ts +29 -0
- package/src/http/loopback.ts +29 -0
- package/src/http/page.ts +54 -0
- package/src/http/respond.ts +18 -0
- package/src/index.ts +1 -0
- package/src/page/board.ts +37 -0
- package/src/page/client.ts +90 -0
- package/src/page/diagrams.ts +72 -0
- package/src/page/document.ts +10 -0
- package/src/page/escape.ts +3 -0
- package/src/page/nav.ts +28 -0
- package/src/page/shell.ts +12 -0
- package/src/page/style.ts +79 -0
- package/src/page/swap.ts +122 -0
- package/src/page/updated.ts +1 -0
- package/src/render/board.ts +82 -0
- package/src/render/code.ts +54 -0
- package/src/render/failed.ts +3 -0
- package/src/render/footnotes.ts +32 -0
- package/src/render/highlighter.ts +17 -0
- package/src/render/markdown.ts +16 -0
- package/src/serve.ts +16 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 - 2026-09-26
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add the `work-board` command: `work-board <dir> [--port 4747] [--home <file>]`
|
|
8
|
+
serves every markdown file under a folder on 127.0.0.1 from one long-lived
|
|
9
|
+
process, with a top bar listing them and how long ago each changed. Files
|
|
10
|
+
and folders below it starting with a dot and `node_modules` are never
|
|
11
|
+
entered, the folder itself may be a dot folder, paths and symlinks leading
|
|
12
|
+
outside the folder are never served, requests whose `Host` header is not a
|
|
13
|
+
loopback host are refused, and a missing `--home` file stops the command
|
|
14
|
+
with an error.
|
|
15
|
+
- Pages render GitHub Flavored Markdown on the server with raw HTML allowed,
|
|
16
|
+
including `<details>` blocks with markdown inside, and highlight fenced code
|
|
17
|
+
with Shiki in light and dark. Mermaid diagrams are drawn in the browser from
|
|
18
|
+
the installed package, only on pages that have one, into reserved space and
|
|
19
|
+
without ever showing their source while drawing; they follow the colour
|
|
20
|
+
scheme, and a diagram that does not parse shows its source and the error.
|
|
21
|
+
- The `--home` file is shown at `/` as a board: `##` sections, `###` items, a
|
|
22
|
+
footer after a `---` that follows the last heading, and a count per section
|
|
23
|
+
derived from its items. Reference links and footnotes work inside cards.
|
|
24
|
+
- Pages update in place: the server watches the folder recursively and pushes
|
|
25
|
+
the changed markdown paths over server-sent events at `/events`, and the page
|
|
26
|
+
replaces only the changed blocks without reloading, keeping which `<details>`
|
|
27
|
+
are open, every drawn diagram that did not change, and an edited diagram's old
|
|
28
|
+
drawing until its new one is ready. A refresh that gets no page back keeps the
|
|
29
|
+
page and says so. A failed watch is restarted, waiting longer after each
|
|
30
|
+
failure, while pages show "reconnecting" and then catch up. Ctrl-C stops the
|
|
31
|
+
command at once, even with pages open.
|
|
32
|
+
- Add `boardLayer`, the router Layer the command serves, for embedding the board
|
|
33
|
+
in another Effect HTTP server or as a fetch handler.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ShivaeDev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,147 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@shivaedev/work-board`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A tiny local server that shows a folder of markdown files as a live page. Edit a
|
|
4
|
+
file and every open page updates in place, with no reload, no rebuild and
|
|
5
|
+
nothing to publish.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add --global @shivaedev/work-board
|
|
9
|
+
work-board ./project-notes --port 4747 --home plan.md
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
It listens on `127.0.0.1` only and answers only requests addressed to a
|
|
13
|
+
loopback host. Every `.md` file under the folder appears in the top bar with how
|
|
14
|
+
long ago it changed. Below the folder, files and folders starting with a dot and
|
|
15
|
+
`node_modules` are never entered, and anything that is not markdown is skipped;
|
|
16
|
+
the folder itself may be a dot folder such as `.notes`. No path outside the
|
|
17
|
+
folder is served, and a symlink is followed only when it leads to a place
|
|
18
|
+
inside the folder. One long-lived process picks up new files and edits without
|
|
19
|
+
a restart.
|
|
20
|
+
|
|
21
|
+
Pages render GitHub Flavored Markdown on the server: tables, task lists,
|
|
22
|
+
footnotes, raw HTML (including `<details>` with markdown inside) and fenced code,
|
|
23
|
+
highlighted with Shiki's GitHub light and dark themes. Raw HTML is not
|
|
24
|
+
sanitized, so serve only folders you trust. Pages send a content security
|
|
25
|
+
policy that allows only the board's own scripts, and `Cache-Control: no-store`.
|
|
26
|
+
The page uses system fonts and follows the light or dark colour scheme.
|
|
27
|
+
|
|
28
|
+
| Option | Default | Meaning |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `<dir>` | required | The folder to serve. |
|
|
31
|
+
| `--port` | `4747` | The port on `127.0.0.1`. `0` picks a free one. |
|
|
32
|
+
| `--home` | none | The file shown at `/` as a board, relative to the folder. The command stops with an error unless it leads to one of the markdown files listed from the folder. Without it, `/` shows the first file as a document. |
|
|
33
|
+
|
|
34
|
+
## Diagrams
|
|
35
|
+
|
|
36
|
+
A ` ```mermaid ` block is drawn in the browser. Mermaid loads from the installed
|
|
37
|
+
package, and only on pages with a diagram.
|
|
38
|
+
|
|
39
|
+
- A diagram is drawn into space reserved for it; its source is never shown
|
|
40
|
+
while it is drawn or redrawn.
|
|
41
|
+
- A drawn diagram stays in place when the page around it updates. An edited
|
|
42
|
+
diagram keeps its old drawing until the new one is ready, as long as the page
|
|
43
|
+
keeps the same number of diagrams; otherwise it shows its reserved space.
|
|
44
|
+
- Diagrams follow the light or dark colour scheme; switching schemes redraws
|
|
45
|
+
them in place.
|
|
46
|
+
- A diagram that does not parse shows its source and the error, without the
|
|
47
|
+
drawing it had before.
|
|
48
|
+
|
|
49
|
+
## Boards
|
|
50
|
+
|
|
51
|
+
The home file is shown as a board: a title, an intro, then sections of cards
|
|
52
|
+
with a count per section in the header. The counts are derived from the
|
|
53
|
+
markdown, so they are never typed by hand.
|
|
54
|
+
|
|
55
|
+
```md
|
|
56
|
+
# Project notes
|
|
57
|
+
|
|
58
|
+
What is open this week.
|
|
59
|
+
|
|
60
|
+
## In review
|
|
61
|
+
|
|
62
|
+
### `docs #12` [Refresh the install guide](https://example.com/pull/12)
|
|
63
|
+
|
|
64
|
+
Checks are green.
|
|
65
|
+
|
|
66
|
+
## To do
|
|
67
|
+
|
|
68
|
+
### `ops` Rotate the deploy key
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>Steps</summary>
|
|
72
|
+
|
|
73
|
+
1. Create the key.
|
|
74
|
+
2. Replace the old one.
|
|
75
|
+
|
|
76
|
+
</details>
|
|
77
|
+
|
|
78
|
+
## Later
|
|
79
|
+
|
|
80
|
+
Nothing planned yet.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
Finished work moves to the changelog.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- `#` is the title, and anything before the first `##` is the intro.
|
|
88
|
+
- Each `##` is a section. Its `###` headings are its items, and its count is
|
|
89
|
+
the number of items. A section without `###` items, such as "Later", has no
|
|
90
|
+
count and shows its text as one card.
|
|
91
|
+
- An item's heading may start with a code span, shown as a quiet tag before the
|
|
92
|
+
title, and the title may be a link. Everything up to the next heading is the
|
|
93
|
+
item's body.
|
|
94
|
+
- Markdown inside `<details>` needs a blank line after `<summary>` and before
|
|
95
|
+
`</details>`.
|
|
96
|
+
- A `---` after the last heading starts the footer; a `---` inside a card stays
|
|
97
|
+
in the card.
|
|
98
|
+
- Reference links and footnotes work inside cards, wherever their definitions
|
|
99
|
+
are in the file, and each card's footnotes get their own ids.
|
|
100
|
+
|
|
101
|
+
## Live updates
|
|
102
|
+
|
|
103
|
+
The server watches the folder recursively. Changes settle for 100 ms, then
|
|
104
|
+
`/events` sends one server-sent event naming the markdown files that changed:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
event: change
|
|
108
|
+
data: {"paths":["plan.md"]}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Open pages fetch their file again and replace only the blocks that changed,
|
|
112
|
+
also when blocks were added or removed around them; the rest of the page,
|
|
113
|
+
including which `<details>` are open and every drawn diagram, stays as it is,
|
|
114
|
+
and the page never reloads. A page that loses the connection shows
|
|
115
|
+
"reconnecting" and catches up once it is back. When the open file is deleted
|
|
116
|
+
or renamed, the page shows that it is gone. A refresh that gets no page back
|
|
117
|
+
keeps the page as it is and shows "refresh failed" until the next one
|
|
118
|
+
succeeds.
|
|
119
|
+
|
|
120
|
+
If watching the folder fails, the server restarts the watch, waiting longer
|
|
121
|
+
after each failure. Meanwhile `/events` sends `event: down`, open pages show
|
|
122
|
+
"reconnecting", and once watching resumes `event: ready` makes them catch up on
|
|
123
|
+
anything they missed.
|
|
124
|
+
|
|
125
|
+
Each open page keeps one connection to `/events`, and browsers allow only a few
|
|
126
|
+
per host, so the intended use is one board per browser.
|
|
127
|
+
|
|
128
|
+
## Embedding
|
|
129
|
+
|
|
130
|
+
`boardLayer({ root, home })` is the router Layer the command serves. Serve it
|
|
131
|
+
with `HttpRouter.serve` on any Effect HTTP server, or turn it into a fetch
|
|
132
|
+
handler with `HttpRouter.toWebHandler`. It needs `FileSystem` and `Path`, for
|
|
133
|
+
example from `NodeServices.layer`. It still judges every request by its `Host`
|
|
134
|
+
header, so pass requests on with that header: a request without one, or whose
|
|
135
|
+
`Host` is not a loopback host, is refused, and so is a request from a
|
|
136
|
+
non-loopback address when the server knows the address.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { NodeServices } from "@effect/platform-node"
|
|
140
|
+
import { Layer } from "effect"
|
|
141
|
+
import { HttpRouter } from "effect/unstable/http"
|
|
142
|
+
import { boardLayer } from "@shivaedev/work-board"
|
|
143
|
+
|
|
144
|
+
const board = HttpRouter.toWebHandler(
|
|
145
|
+
Layer.provide(boardLayer({ root: "./project-notes", home: "plan.md" }), NodeServices.layer),
|
|
146
|
+
)
|
|
147
|
+
```
|
package/dist/board.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type FileSystem, Layer, type Path } from "effect";
|
|
2
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
3
|
+
export interface BoardOptions {
|
|
4
|
+
readonly root: string;
|
|
5
|
+
readonly home?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export declare const boardLayer: (options: BoardOptions) => Layer.Layer<never, import("./files/home.ts").HomeMissing | import("./http/assets.ts").MermaidMissing | import("effect/PlatformError").PlatformError | import("./render/failed.ts").RenderFailed, FileSystem.FileSystem | HttpRouter.HttpRouter | Path.Path | HttpRouter.Request<"Error", import("effect/PlatformError").PlatformError>>;
|
|
8
|
+
//# sourceMappingURL=board.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,UAAU,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,UAAU,EAAmD,MAAM,sBAAsB,CAAC;AAUnG,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AA6BD,eAAO,MAAM,UAAU,GAAI,SAAS,YAAY,4UAA2D,CAAC"}
|
package/dist/board.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Effect, Layer } from "effect";
|
|
2
|
+
import { HttpRouter } from "effect/unstable/http";
|
|
3
|
+
import { watchChanges } from "./files/changes.js";
|
|
4
|
+
import { homeIn } from "./files/home.js";
|
|
5
|
+
import { ASSETS, MERMAID_ROUTE, mermaidFile, mermaidRoot } from "./http/assets.js";
|
|
6
|
+
import { events } from "./http/events.js";
|
|
7
|
+
import { loopbackOnly } from "./http/loopback.js";
|
|
8
|
+
import { page } from "./http/page.js";
|
|
9
|
+
import { respond } from "./http/respond.js";
|
|
10
|
+
import { Highlighter } from "./render/highlighter.js";
|
|
11
|
+
const routes = (options) => HttpRouter.use((router) => Effect.gen(function* () {
|
|
12
|
+
const home = yield* homeIn(options.root, options.home);
|
|
13
|
+
const mermaid = yield* mermaidRoot();
|
|
14
|
+
const changes = yield* watchChanges(options.root);
|
|
15
|
+
const context = yield* Effect.context();
|
|
16
|
+
const serve = (route, handler) => router.add("GET", route, loopbackOnly((request) => Effect.provideContext(handler(request), context)));
|
|
17
|
+
yield* serve("/events", events(changes));
|
|
18
|
+
for (const [route, body, contentType] of ASSETS) {
|
|
19
|
+
yield* serve(route, () => Effect.succeed(respond(body, contentType)));
|
|
20
|
+
}
|
|
21
|
+
yield* serve(MERMAID_ROUTE, mermaidFile(mermaid));
|
|
22
|
+
const pages = page({ root: options.root, home }, changes);
|
|
23
|
+
yield* serve("/", pages);
|
|
24
|
+
yield* serve("/*", pages);
|
|
25
|
+
}));
|
|
26
|
+
export const boardLayer = (options) => routes(options).pipe(Layer.provide(Highlighter.layer));
|
|
27
|
+
//# sourceMappingURL=board.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"board.js","sourceRoot":"","sources":["../src/board.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAmB,KAAK,EAAa,MAAM,QAAQ,CAAC;AACnE,OAAO,EAAE,UAAU,EAAmD,MAAM,sBAAsB,CAAC;AACnG,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACnF,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAUtD,MAAM,MAAM,GAAG,CAAC,OAAqB,EAAE,EAAE,CACxC,UAAU,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CACzB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,EAAY,CAAC;IAClD,MAAM,KAAK,GAAG,CAAI,KAA2B,EAAE,OAAmB,EAAE,EAAE,CACrE,MAAM,CAAC,GAAG,CACT,KAAK,EACL,KAAK,EACL,YAAY,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAC3E,CAAC;IACH,KAAK,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,EAAE,CAAC;QACjD,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,KAAK,CAAC,CAAC,KAAK,CAAC,aAAa,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;IAC1D,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACzB,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC,CAAC,CACF,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
3
|
+
import { Effect, Layer, Option, Path } from "effect";
|
|
4
|
+
import { Argument, Command, Flag } from "effect/unstable/cli";
|
|
5
|
+
import { HOST, serveBoard } from "./serve.js";
|
|
6
|
+
const VERSION = "0.1.0";
|
|
7
|
+
const serve = Effect.fn("WorkBoard.serve")(function* (input) {
|
|
8
|
+
const path = yield* Path.Path;
|
|
9
|
+
return yield* Layer.launch(serveBoard({ root: path.resolve(input.dir), home: Option.getOrUndefined(input.home), port: input.port }));
|
|
10
|
+
});
|
|
11
|
+
const workBoard = Command.make("work-board", {
|
|
12
|
+
dir: Argument.directory("dir", { mustExist: true }).pipe(Argument.withDescription("The folder of markdown files to serve")),
|
|
13
|
+
port: Flag.integer("port").pipe(Flag.withDefault(4747), Flag.withDescription(`The port to listen on at ${HOST}`)),
|
|
14
|
+
home: Flag.string("home").pipe(Flag.optional, Flag.withDescription("The file shown as a board at /, relative to the folder")),
|
|
15
|
+
}, serve).pipe(Command.withDescription("Serve a folder of markdown files as a live page that updates in place when a file changes."));
|
|
16
|
+
Command.run(workBoard, { version: VERSION }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);
|
|
17
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,OAAO,GAAG,OAAO,CAAC;AAExB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,QAAQ,CAAC,EAAE,KAA4F;IACjJ,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,OAAO,KAAK,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AACtI,CAAC,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAC7B,YAAY,EACZ;IACC,GAAG,EAAE,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,uCAAuC,CAAC,CAAC;IAC3H,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,4BAA4B,IAAI,EAAE,CAAC,CAAC;IACjH,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,eAAe,CAAC,wDAAwD,CAAC,CAAC;CAC7H,EACD,KAAK,CACL,CAAC,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,4FAA4F,CAAC,CAAC,CAAC;AAE9H,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, type PlatformError, PubSub, Ref } from "effect";
|
|
2
|
+
import { type MarkdownFile } from "./list.ts";
|
|
3
|
+
export type Change = {
|
|
4
|
+
readonly _tag: "Changed";
|
|
5
|
+
readonly paths: ReadonlyArray<string>;
|
|
6
|
+
} | {
|
|
7
|
+
readonly _tag: "Watching";
|
|
8
|
+
readonly watching: boolean;
|
|
9
|
+
};
|
|
10
|
+
export interface Changes {
|
|
11
|
+
readonly realRoot: string;
|
|
12
|
+
readonly events: PubSub.PubSub<Change>;
|
|
13
|
+
readonly watching: Ref.Ref<boolean>;
|
|
14
|
+
readonly files: Effect.Effect<ReadonlyArray<MarkdownFile>, PlatformError.PlatformError>;
|
|
15
|
+
}
|
|
16
|
+
export declare const watchChanges: (root: string) => Effect.Effect<Changes, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | import("effect/Scope").Scope>;
|
|
17
|
+
//# sourceMappingURL=changes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.d.ts","sourceRoot":"","sources":["../../src/files/changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,GAAG,EAAoB,MAAM,QAAQ,CAAC;AAC7H,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,WAAW,CAAC;AAExE,MAAM,MAAM,MAAM,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CAAC;AAErJ,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,aAAa,CAAC,CAAC;CACxF;AAcD,eAAO,MAAM,YAAY,yIA8CvB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Data, Deferred, Effect, FileSystem, Option, Path, PubSub, Ref, Schedule, Stream } from "effect";
|
|
2
|
+
import { isMarkdown, listMarkdown } from "./list.js";
|
|
3
|
+
class WatchEnded extends Data.TaggedError("WatchEnded") {
|
|
4
|
+
}
|
|
5
|
+
const SETTLE = "100 millis";
|
|
6
|
+
const BATCH = 256;
|
|
7
|
+
const BACKLOG = 16;
|
|
8
|
+
const RETRY = Schedule.min([Schedule.exponential("100 millis"), Schedule.spaced("5 seconds")]);
|
|
9
|
+
export const watchChanges = Effect.fn("WorkBoard.watchChanges")(function* (root) {
|
|
10
|
+
const fs = yield* FileSystem.FileSystem;
|
|
11
|
+
const path = yield* Path.Path;
|
|
12
|
+
const realRoot = yield* fs.realPath(root);
|
|
13
|
+
const events = yield* Effect.acquireRelease(PubSub.sliding(BACKLOG), PubSub.shutdown);
|
|
14
|
+
const watching = yield* Ref.make(false);
|
|
15
|
+
const generation = yield* Ref.make(0);
|
|
16
|
+
const cached = yield* Ref.make(Option.none());
|
|
17
|
+
const settled = yield* Deferred.make();
|
|
18
|
+
const stale = Ref.update(generation, (count) => count + 1);
|
|
19
|
+
const relative = (changed) => (path.isAbsolute(changed) ? path.relative(root, changed) : changed).split(path.sep).join("/");
|
|
20
|
+
const setWatching = (now) => Effect.andThen(Effect.flatMap(Ref.getAndSet(watching, now), (was) => was === now ? Effect.void : Effect.andThen(stale, PubSub.publish(events, { _tag: "Watching", watching: now }))), Deferred.succeed(settled, undefined));
|
|
21
|
+
const watchOnce = fs.watch(root, { recursive: true }).pipe(Stream.tap(() => stale), Stream.map((event) => relative(event.path)), Stream.filter(isMarkdown), Stream.groupedWithin(BATCH, SETTLE), Stream.runForEach((paths) => PubSub.publish(events, { _tag: "Changed", paths: [...new Set(paths)] })), Effect.andThen(Effect.fail(new WatchEnded())));
|
|
22
|
+
const attempt = Effect.scoped(Effect.andThen(Effect.forkScoped(Effect.delay(setWatching(true), SETTLE)), watchOnce));
|
|
23
|
+
yield* attempt.pipe(Effect.sandbox, Effect.tapError((cause) => Effect.andThen(setWatching(false), Effect.logWarning("Watching the folder failed; retrying", cause))), Effect.retry(RETRY), Effect.forkScoped);
|
|
24
|
+
yield* Deferred.await(settled);
|
|
25
|
+
const files = Effect.gen(function* () {
|
|
26
|
+
const now = yield* Ref.get(generation);
|
|
27
|
+
const hit = Option.filter(yield* Ref.get(cached), (entry) => entry.generation === now);
|
|
28
|
+
if ((yield* Ref.get(watching)) && Option.isSome(hit)) {
|
|
29
|
+
return hit.value.files;
|
|
30
|
+
}
|
|
31
|
+
const listed = yield* listMarkdown(root, realRoot).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
|
|
32
|
+
yield* Ref.set(cached, Option.some({ generation: now, files: listed }));
|
|
33
|
+
return listed;
|
|
34
|
+
});
|
|
35
|
+
const changes = { realRoot, events, watching, files };
|
|
36
|
+
return changes;
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=changes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changes.js","sourceRoot":"","sources":["../../src/files/changes.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
declare const HomeMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
3
|
+
readonly _tag: "HomeMissing";
|
|
4
|
+
} & Readonly<A>;
|
|
5
|
+
export declare class HomeMissing extends HomeMissing_base<{
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}> {
|
|
8
|
+
}
|
|
9
|
+
export declare const homeIn: (root: string, home: string | undefined) => Effect.Effect<string | undefined, HomeMissing | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=home.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"home.d.ts","sourceRoot":"","sources":["../../src/files/home.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,MAAM,QAAQ,CAAC;;;;AAGhE,qBAAa,WAAY,SAAQ,iBAAgC;IAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;CAAG;AAEjG,eAAO,MAAM,MAAM,8KA+BjB,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Data, Effect, FileSystem, Option, Path } from "effect";
|
|
2
|
+
import { listMarkdown } from "./list.js";
|
|
3
|
+
export class HomeMissing extends Data.TaggedError("HomeMissing") {
|
|
4
|
+
}
|
|
5
|
+
export const homeIn = Effect.fn("WorkBoard.homeIn")(function* (root, home) {
|
|
6
|
+
if (home === undefined) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const fs = yield* FileSystem.FileSystem;
|
|
10
|
+
const path = yield* Path.Path;
|
|
11
|
+
const missing = new HomeMissing({ message: `The home file ${home} is not a markdown file in ${root}` });
|
|
12
|
+
const realRoot = yield* fs.realPath(root);
|
|
13
|
+
const real = yield* Effect.option(fs.realPath(path.resolve(root, home)));
|
|
14
|
+
if (Option.isNone(real)) {
|
|
15
|
+
return yield* missing;
|
|
16
|
+
}
|
|
17
|
+
const target = yield* Effect.option(fs.stat(real.value));
|
|
18
|
+
if (Option.isNone(target)) {
|
|
19
|
+
return yield* missing;
|
|
20
|
+
}
|
|
21
|
+
const files = yield* listMarkdown(root, realRoot);
|
|
22
|
+
const relative = path.relative(realRoot, real.value).split(path.sep).join("/");
|
|
23
|
+
if (files.some((file) => file.path === relative)) {
|
|
24
|
+
return relative;
|
|
25
|
+
}
|
|
26
|
+
const inode = Option.getOrUndefined(target.value.ino);
|
|
27
|
+
const same = (info) => inode !== undefined && info.dev === target.value.dev && Option.getOrUndefined(info.ino) === inode;
|
|
28
|
+
const matches = yield* Effect.filter(files, (file) => Effect.map(Effect.option(fs.stat(path.join(realRoot, file.path))), (info) => Option.isSome(info) && same(info.value)));
|
|
29
|
+
const direct = yield* Effect.filter(matches, (file) => Effect.map(Effect.option(fs.realPath(path.join(realRoot, file.path))), (real) => Option.contains(real, path.join(realRoot, file.path))));
|
|
30
|
+
const chosen = matches.find((file) => file.path.toLowerCase() === relative.toLowerCase()) ?? direct[0] ?? matches[0];
|
|
31
|
+
return chosen === undefined ? yield* missing : chosen.path;
|
|
32
|
+
});
|
|
33
|
+
//# sourceMappingURL=home.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"home.js","sourceRoot":"","sources":["../../src/files/home.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path, type PlatformError } from "effect";
|
|
2
|
+
export interface MarkdownFile {
|
|
3
|
+
readonly path: string;
|
|
4
|
+
readonly modified: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const isMarkdown: (path: string) => boolean;
|
|
7
|
+
export declare const within: (realRoot: string, separator: string, real: string) => boolean;
|
|
8
|
+
export declare const listMarkdown: (root: string, realRoot: string) => Effect.Effect<MarkdownFile[], PlatformError.PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
9
|
+
//# sourceMappingURL=list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/files/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAU,IAAI,EAAE,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAE9E,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC1B;AAID,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,OAAiE,CAAC;AAE5G,eAAO,MAAM,MAAM,GAAI,UAAU,MAAM,EAAE,WAAW,MAAM,EAAE,MAAM,MAAM,KAAG,OAA0E,CAAC;AAQtJ,eAAO,MAAM,YAAY,mIAmCvB,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Effect, FileSystem, Option, Path } from "effect";
|
|
2
|
+
const skipped = (segment) => segment.startsWith(".") || segment === "node_modules";
|
|
3
|
+
export const isMarkdown = (path) => path.endsWith(".md") && !path.split("/").some(skipped);
|
|
4
|
+
export const within = (realRoot, separator, real) => real === realRoot || real.startsWith(`${realRoot}${separator}`);
|
|
5
|
+
const folderOf = (path) => path.slice(0, Math.max(0, path.lastIndexOf("/")));
|
|
6
|
+
const byFolder = (left, right) => folderOf(left).localeCompare(folderOf(right)) || left.localeCompare(right);
|
|
7
|
+
export const listMarkdown = Effect.fn("WorkBoard.listMarkdown")(function* (root, realRoot) {
|
|
8
|
+
const fs = yield* FileSystem.FileSystem;
|
|
9
|
+
const path = yield* Path.Path;
|
|
10
|
+
const visited = new Set();
|
|
11
|
+
const entry = (relative) => Effect.gen(function* () {
|
|
12
|
+
const real = yield* Effect.option(fs.realPath(path.join(root, relative)));
|
|
13
|
+
if (Option.isNone(real) || !within(realRoot, path.sep, real.value)) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const info = yield* Effect.option(fs.stat(real.value));
|
|
17
|
+
if (Option.isNone(info)) {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
if (info.value.type === "Directory") {
|
|
21
|
+
return yield* Effect.orElseSucceed(folder(relative, real.value), () => []);
|
|
22
|
+
}
|
|
23
|
+
const modified = Option.match(info.value.mtime, { onNone: () => 0, onSome: (date) => date.getTime() });
|
|
24
|
+
return info.value.type === "File" && relative.endsWith(".md") ? [{ path: relative, modified }] : [];
|
|
25
|
+
});
|
|
26
|
+
const folder = (relative, real) => Effect.gen(function* () {
|
|
27
|
+
if (visited.has(real)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
visited.add(real);
|
|
31
|
+
const names = yield* fs.readDirectory(real);
|
|
32
|
+
const found = yield* Effect.forEach(names.filter((name) => !skipped(name)), (name) => entry(relative === "" ? name : `${relative}/${name}`));
|
|
33
|
+
return found.flat();
|
|
34
|
+
});
|
|
35
|
+
const files = yield* folder("", realRoot);
|
|
36
|
+
return files.toSorted((left, right) => byFolder(left.path, right.path));
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/files/list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Effect, FileSystem, Path } from "effect";
|
|
2
|
+
import type { HttpRouter, HttpServerRequest } from "effect/unstable/http";
|
|
3
|
+
export declare const ASSETS: ReadonlyArray<readonly [HttpRouter.PathInput, string, string]>;
|
|
4
|
+
declare const MermaidMissing_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
5
|
+
readonly _tag: "MermaidMissing";
|
|
6
|
+
} & Readonly<A>;
|
|
7
|
+
export declare class MermaidMissing extends MermaidMissing_base<{
|
|
8
|
+
readonly cause: unknown;
|
|
9
|
+
}> {
|
|
10
|
+
}
|
|
11
|
+
export declare const mermaidRoot: () => Effect.Effect<string, MermaidMissing, Path.Path>;
|
|
12
|
+
export declare const mermaidFile: (mermaidRoot: string) => (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<import("effect/unstable/http/HttpServerResponse").HttpServerResponse, import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
|
|
13
|
+
export declare const MERMAID_ROUTE: HttpRouter.PathInput;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=assets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/http/assets.ts"],"names":[],"mappings":"AACA,OAAO,EAAQ,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAO1E,eAAO,MAAM,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAKjF,CAAC;;;;AAIF,qBAAa,cAAe,SAAQ,oBAAmC;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;CAAG;AAEtG,eAAO,MAAM,WAAW,wDAItB,CAAC;AAEH,eAAO,MAAM,WAAW,GAAI,aAAa,MAAM,2NAS5C,CAAC;AAEJ,eAAO,MAAM,aAAa,EAAE,UAAU,CAAC,SAAyB,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { Data, Effect, FileSystem, Path } from "effect";
|
|
3
|
+
import { client } from "../page/client.js";
|
|
4
|
+
import { diagrams } from "../page/diagrams.js";
|
|
5
|
+
import { style } from "../page/style.js";
|
|
6
|
+
import { swap } from "../page/swap.js";
|
|
7
|
+
import { respond } from "./respond.js";
|
|
8
|
+
export const ASSETS = [
|
|
9
|
+
["/_board/style.css", style, "text/css"],
|
|
10
|
+
["/_board/client.js", client, "text/javascript"],
|
|
11
|
+
["/_board/diagrams.js", diagrams, "text/javascript"],
|
|
12
|
+
["/_board/swap.js", swap, "text/javascript"],
|
|
13
|
+
];
|
|
14
|
+
const MERMAID = "/_board/mermaid/";
|
|
15
|
+
export class MermaidMissing extends Data.TaggedError("MermaidMissing") {
|
|
16
|
+
}
|
|
17
|
+
export const mermaidRoot = Effect.fn("WorkBoard.mermaidRoot")(function* () {
|
|
18
|
+
const path = yield* Path.Path;
|
|
19
|
+
const entry = yield* Effect.try({ try: () => createRequire(import.meta.url).resolve("mermaid"), catch: (cause) => new MermaidMissing({ cause }) });
|
|
20
|
+
return path.dirname(entry);
|
|
21
|
+
});
|
|
22
|
+
export const mermaidFile = (mermaidRoot) => Effect.fn("WorkBoard.mermaidFile")(function* (request) {
|
|
23
|
+
const fs = yield* FileSystem.FileSystem;
|
|
24
|
+
const path = yield* Path.Path;
|
|
25
|
+
const file = path.resolve(mermaidRoot, new URL(request.url, "http://127.0.0.1").pathname.slice(MERMAID.length));
|
|
26
|
+
if (!file.startsWith(`${mermaidRoot}${path.sep}`) || !file.endsWith(".mjs") || !(yield* fs.exists(file))) {
|
|
27
|
+
return respond("Not found", "text/plain", 404);
|
|
28
|
+
}
|
|
29
|
+
return respond(yield* fs.readFile(file), "text/javascript");
|
|
30
|
+
});
|
|
31
|
+
export const MERMAID_ROUTE = `${MERMAID}*`;
|
|
32
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/http/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,CAAC,MAAM,MAAM,GAAmE;IACrF,CAAC,mBAAmB,EAAE,KAAK,EAAE,UAAU,CAAC;IACxC,CAAC,mBAAmB,EAAE,MAAM,EAAE,iBAAiB,CAAC;IAChD,CAAC,qBAAqB,EAAE,QAAQ,EAAE,iBAAiB,CAAC;IACpD,CAAC,iBAAiB,EAAE,IAAI,EAAE,iBAAiB,CAAC;CAC5C,CAAC;AAEF,MAAM,OAAO,GAAG,kBAAkB,CAAC;AAEnC,MAAM,OAAO,cAAe,SAAQ,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAA8B;CAAG;AAEtG,MAAM,CAAC,MAAM,WAAW,GAAG,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC;IACtE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,cAAc,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACnJ,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,WAAmB,EAAE,EAAE,CAClD,MAAM,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,QAAQ,CAAC,EAAE,OAA4C;IACzF,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;IAC9B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,kBAAkB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAChH,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QAC1G,OAAO,OAAO,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,iBAAiB,CAAC,CAAC;AAC7D,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,aAAa,GAAyB,GAAG,OAAO,GAAG,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { HttpServerResponse } from "effect/unstable/http";
|
|
3
|
+
import type { Changes } from "../files/changes.ts";
|
|
4
|
+
export declare const events: (changes: Changes) => () => Effect.Effect<HttpServerResponse.HttpServerResponse, never, never>;
|
|
5
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/http/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAuB,MAAM,QAAQ,CAAC;AAErD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,KAAK,EAAU,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAmB3D,eAAO,MAAM,MAAM,GAAI,SAAS,OAAO,6EAMrC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Effect, PubSub, Ref, Stream } from "effect";
|
|
2
|
+
import { Sse } from "effect/unstable/encoding";
|
|
3
|
+
import { HttpServerResponse } from "effect/unstable/http";
|
|
4
|
+
import { HEADERS } from "./respond.js";
|
|
5
|
+
const event = (name, data = "") => ({ _tag: "Event", event: name, id: undefined, data });
|
|
6
|
+
const watching = (up) => event(up ? "ready" : "down");
|
|
7
|
+
const eventOf = (change) => change._tag === "Changed" ? event("change", JSON.stringify({ paths: change.paths })) : watching(change.watching);
|
|
8
|
+
const subscribed = (changes) => Stream.unwrap(Effect.gen(function* () {
|
|
9
|
+
const subscription = yield* PubSub.subscribe(changes.events);
|
|
10
|
+
const now = yield* Ref.get(changes.watching);
|
|
11
|
+
return Stream.concat(Stream.succeed(watching(now)), Stream.map(Stream.fromSubscription(subscription), eventOf));
|
|
12
|
+
}));
|
|
13
|
+
export const events = (changes) => () => Effect.succeed(HttpServerResponse.stream(Stream.encodeText(Stream.map(subscribed(changes), (message) => Sse.encoder.write(message))), {
|
|
14
|
+
contentType: "text/event-stream",
|
|
15
|
+
headers: HEADERS,
|
|
16
|
+
}));
|
|
17
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/http/events.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrD,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,IAAI,GAAG,EAAE,EAAa,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAE5G,MAAM,QAAQ,GAAG,CAAC,EAAW,EAAa,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAE1E,MAAM,OAAO,GAAG,CAAC,MAAc,EAAa,EAAE,CAC7C,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAElH,MAAM,UAAU,GAAG,CAAC,OAAgB,EAAE,EAAE,CACvC,MAAM,CAAC,MAAM,CACZ,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,gBAAgB,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;AACjH,CAAC,CAAC,CACF,CAAC;AAEH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,OAAgB,EAAE,EAAE,CAAC,GAAG,EAAE,CAChD,MAAM,CAAC,OAAO,CACb,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;IACtH,WAAW,EAAE,mBAAmB;IAChC,OAAO,EAAE,OAAO;CAChB,CAAC,CACF,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
3
|
+
export declare const loopbackOnly: <E, R>(handler: (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>) => (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, R>;
|
|
4
|
+
//# sourceMappingURL=loopback.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.d.ts","sourceRoot":"","sources":["../../src/http/loopback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AACxC,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAwBlF,eAAO,MAAM,YAAY,GACvB,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,iBAAiB,CAAC,iBAAiB,KAAK,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,kBAAkB,EAAE,CAAC,EAAE,CAAC,CAAC,MAC3H,SAAS,iBAAiB,CAAC,iBAAiB,+DAC2E,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Effect, Option } from "effect";
|
|
2
|
+
import { respond } from "./respond.js";
|
|
3
|
+
const LOOPBACK_NAMES = new Set(["127.0.0.1", "localhost", "[::1]"]);
|
|
4
|
+
const hostnameOf = (host) => {
|
|
5
|
+
try {
|
|
6
|
+
return new URL(`http://${host}/`).hostname;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
const loopbackAddress = (address) => {
|
|
13
|
+
const unmapped = address.replace(/^::ffff:/i, "");
|
|
14
|
+
return unmapped === "::1" || /^127(?:\.\d{1,3}){3}$/.test(unmapped);
|
|
15
|
+
};
|
|
16
|
+
const fromLoopback = (request) => {
|
|
17
|
+
const host = request.headers.host;
|
|
18
|
+
const addressed = host !== undefined && LOOPBACK_NAMES.has(hostnameOf(host) ?? "");
|
|
19
|
+
return addressed && Option.match(request.remoteAddress, { onNone: () => true, onSome: loopbackAddress });
|
|
20
|
+
};
|
|
21
|
+
export const loopbackOnly = (handler) => (request) => fromLoopback(request) ? handler(request) : Effect.succeed(respond("Only loopback hosts are served", "text/plain", 403));
|
|
22
|
+
//# sourceMappingURL=loopback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopback.js","sourceRoot":"","sources":["../../src/http/loopback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAExC,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAEvC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE,MAAM,UAAU,GAAG,CAAC,IAAY,EAAsB,EAAE;IACvD,IAAI,CAAC;QACJ,OAAO,IAAI,GAAG,CAAC,UAAU,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC,CAAC;AAEF,MAAM,eAAe,GAAG,CAAC,OAAe,EAAW,EAAE;IACpD,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,QAAQ,KAAK,KAAK,IAAI,uBAAuB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACrE,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,OAA4C,EAAW,EAAE;IAC9E,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,KAAK,SAAS,IAAI,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACnF,OAAO,SAAS,IAAI,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC,CAAC;AAC1G,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GACxB,CAAO,OAAqH,EAAE,EAAE,CAChI,CAAC,OAA4C,EAAE,EAAE,CAChD,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,gCAAgC,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC,CAAC"}
|