@pathmx/core 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +172 -0
- package/README.md +111 -0
- package/actions/actions.ts +307 -0
- package/actions/draft.ts +105 -0
- package/actions/transaction.ts +164 -0
- package/assets.ts +149 -0
- package/authority/access.ts +820 -0
- package/authority/actors.ts +271 -0
- package/authority/agents.ts +95 -0
- package/authority/credentials.ts +144 -0
- package/authority/model.ts +71 -0
- package/authority/sharing.ts +89 -0
- package/browser-bundle.ts +61 -0
- package/canonical.ts +41 -0
- package/cli/dev.ts +134 -0
- package/cli/export.ts +74 -0
- package/cli/index.ts +52 -0
- package/cli/lint.ts +164 -0
- package/cli/mv.ts +30 -0
- package/cli/perms.ts +182 -0
- package/cli/plugin-packages.ts +159 -0
- package/cli/plugins.ts +95 -0
- package/cli/query.ts +225 -0
- package/cli/readme.md +95 -0
- package/cli/release.ts +75 -0
- package/cli/rm.ts +22 -0
- package/cli/serve.ts +100 -0
- package/cli/tree.ts +131 -0
- package/cli/update.ts +78 -0
- package/cli/utils.ts +53 -0
- package/core.ts +96 -0
- package/database.ts +189 -0
- package/dependencies.ts +60 -0
- package/dist/pathmx.js +1224 -0
- package/document.ts +194 -0
- package/environment.ts +110 -0
- package/environments/bun-plugins.ts +68 -0
- package/environments/bun-publish.ts +194 -0
- package/environments/bun.ts +223 -0
- package/globals.d.ts +14 -0
- package/graph.ts +153 -0
- package/host/compiler.ts +314 -0
- package/host/create.ts +66 -0
- package/host/engine.ts +630 -0
- package/host/plugin-host.ts +487 -0
- package/host/render.ts +102 -0
- package/host/response.ts +20 -0
- package/host/shell.html +15 -0
- package/host/source-sync.ts +254 -0
- package/html-attributes.ts +15 -0
- package/html.ts +48 -0
- package/icons/LICENSE +43 -0
- package/icons/lucide.ts +54 -0
- package/index.ts +75 -0
- package/invalidation.ts +85 -0
- package/log.ts +84 -0
- package/ops/href.ts +87 -0
- package/ops/index.ts +9 -0
- package/ops/move-source.ts +114 -0
- package/ops/plan.ts +47 -0
- package/ops/remove-source.ts +70 -0
- package/package.json +46 -0
- package/path.ts +75 -0
- package/pattern.ts +314 -0
- package/perf.ts +88 -0
- package/plugins/actor-path.ts +43 -0
- package/plugins/actor-profile.ts +55 -0
- package/plugins/agent-browsing.ts +183 -0
- package/plugins/app-component.ts +23 -0
- package/plugins/block-layout/index.ts +78 -0
- package/plugins/block-layout/theme.css +90 -0
- package/plugins/callout/index.ts +236 -0
- package/plugins/callout/theme.css +138 -0
- package/plugins/canonical-links.ts +144 -0
- package/plugins/code-highlight/client.ts +89 -0
- package/plugins/code-highlight/index.ts +147 -0
- package/plugins/code-highlight/theme.css +89 -0
- package/plugins/context.ts +131 -0
- package/plugins/datatype/Datatype[wdth,wght].woff2 +0 -0
- package/plugins/datatype/OFL.txt +96 -0
- package/plugins/datatype/index.ts +287 -0
- package/plugins/definition-list.ts +236 -0
- package/plugins/directive-definitions.ts +13 -0
- package/plugins/embedding/client.ts +88 -0
- package/plugins/embedding/index.ts +43 -0
- package/plugins/image/index.ts +68 -0
- package/plugins/image/theme.css +43 -0
- package/plugins/include.ts +119 -0
- package/plugins/index.ts +91 -0
- package/plugins/input/authoring.ts +236 -0
- package/plugins/input/config.ts +85 -0
- package/plugins/input/index.ts +359 -0
- package/plugins/input/model.ts +173 -0
- package/plugins/input/state.ts +272 -0
- package/plugins/layout.ts +313 -0
- package/plugins/literate/client.ts +208 -0
- package/plugins/literate/index.ts +742 -0
- package/plugins/math/index.ts +140 -0
- package/plugins/meta.ts +42 -0
- package/plugins/navigation/index.ts +158 -0
- package/plugins/navigation/model.ts +364 -0
- package/plugins/preset.ts +65 -0
- package/plugins/query/index.ts +322 -0
- package/plugins/rss/index.ts +46 -0
- package/plugins/runtime/actions.ts +62 -0
- package/plugins/runtime/client.ts +8 -0
- package/plugins/runtime/document.ts +506 -0
- package/plugins/runtime/error.ts +154 -0
- package/plugins/runtime/heading-links.ts +31 -0
- package/plugins/runtime/index.ts +104 -0
- package/plugins/runtime/live.ts +51 -0
- package/plugins/runtime/navigation.ts +113 -0
- package/plugins/runtime/pending.ts +53 -0
- package/plugins/runtime/publication.ts +19 -0
- package/plugins/runtime/start.ts +177 -0
- package/plugins/runtime/static-client.ts +6 -0
- package/plugins/runtime/static-live.ts +188 -0
- package/plugins/runtime/view-transition.ts +72 -0
- package/plugins/runtime/viewport.ts +110 -0
- package/plugins/schema.ts +24 -0
- package/plugins/scripts.ts +140 -0
- package/plugins/source-preview/client.ts +256 -0
- package/plugins/source-preview/index.ts +132 -0
- package/plugins/source-preview/model.ts +11 -0
- package/plugins/source-preview/preview.css +107 -0
- package/plugins/styles.ts +145 -0
- package/plugins/theme/components.ts +10 -0
- package/plugins/theme/foundation.css +358 -0
- package/plugins/theme/index.ts +333 -0
- package/plugins/theme/prose.css +284 -0
- package/plugins/theme/scheme.js +63 -0
- package/plugins/theme/toggle.css +44 -0
- package/plugins/theme/view-transition.css +68 -0
- package/plugins/toc/client.ts +146 -0
- package/plugins/toc/index.ts +232 -0
- package/plugins/toc/theme.css +172 -0
- package/plugins/types.ts +538 -0
- package/problem.ts +41 -0
- package/project.ts +6 -0
- package/publish/build.ts +402 -0
- package/publish/concurrent.ts +32 -0
- package/publish/identity.ts +63 -0
- package/publish/index.ts +11 -0
- package/publish/location.ts +167 -0
- package/publish/model.ts +58 -0
- package/publish/plan.ts +300 -0
- package/publish/validate.ts +150 -0
- package/query/candidate-index.ts +101 -0
- package/query/dependency-index.ts +99 -0
- package/query/fields.ts +216 -0
- package/query/invalidation.ts +48 -0
- package/query/kernel.ts +776 -0
- package/query/model.ts +79 -0
- package/repository.ts +374 -0
- package/runtime-profile.ts +34 -0
- package/schema/model.ts +92 -0
- package/schema/registry.ts +642 -0
- package/schema/standard.ts +226 -0
- package/server/context.ts +94 -0
- package/server/css-imports.ts +66 -0
- package/server/http.ts +333 -0
- package/server/page.ts +21 -0
- package/server/protocols.ts +12 -0
- package/server/representations.ts +66 -0
- package/server/router.ts +131 -0
- package/server/routes.ts +202 -0
- package/server/serve.ts +5 -0
- package/server/watch.ts +120 -0
- package/source/blocks.ts +71 -0
- package/source/format.ts +164 -0
- package/source/index.ts +67 -0
- package/source/interpolate.ts +63 -0
- package/source/markdown/blocks.ts +46 -0
- package/source/markdown/comments.ts +32 -0
- package/source/markdown/fences.ts +67 -0
- package/source/markdown/headings.ts +88 -0
- package/source/markdown/index.ts +34 -0
- package/source/markdown/inline-code.ts +6 -0
- package/source/markdown/links.ts +326 -0
- package/source/markdown/lists.ts +223 -0
- package/source/markdown/prose.ts +74 -0
- package/source/markdown/scan.ts +79 -0
- package/source/markdown/text.ts +82 -0
- package/source/meta.ts +87 -0
- package/source/source.ts +121 -0
- package/source/topmatter.ts +57 -0
- package/source/utils.ts +21 -0
- package/source-diagnostic.ts +63 -0
- package/text-file.ts +26 -0
- package/util.ts +35 -0
- package/view/components.ts +499 -0
- package/view/model.ts +91 -0
- package/view/projection.ts +162 -0
- package/view/session.ts +146 -0
- package/view/syntax.ts +372 -0
package/cli/readme.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# PathMX CLI
|
|
2
|
+
|
|
3
|
+
The PathMX CLI is a command-line tool for managing and serving PathMX projects.
|
|
4
|
+
|
|
5
|
+
_Note: Source edits and Action commits keep connected browsers in sync in both
|
|
6
|
+
`serve` and `dev`. Development mode additionally restarts for code changes._
|
|
7
|
+
|
|
8
|
+
## Usage
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
pathmx <command>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`pmx` is a shorter alias. In this repository, `bun run cli -- <command>` runs
|
|
15
|
+
the source CLI.
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
- `pathmx serve [root] [--port <port>]` starts the production server and watches authored Markdown and CSS
|
|
20
|
+
- `pathmx dev [root] [--port <port>]` starts the development server with the same Source watcher plus process restarts for engine, plugin, or Action code changes
|
|
21
|
+
- `pathmx export [root] --origin <url> [--out <path>]` writes the anonymous static publication and canonical route manifest
|
|
22
|
+
- `pathmx mv <old-path> <new-path> [--dry-run]` moves a source and rewrites linked references
|
|
23
|
+
- `pathmx rm <path> [--dry-run]` removes a source and unlinks references
|
|
24
|
+
- `pathmx tree [root] [--all]` shows the source/graph tree
|
|
25
|
+
- `pathmx perms [source] [--as <actor>] [--format text|json]` inspects effective Source permissions
|
|
26
|
+
- `pathmx lint [root]` runs Source, link, compile, foundation, and repository Plugin validation without starting a server
|
|
27
|
+
- `pathmx query <sources|blocks|plugin:name>` runs an access-controlled query and writes JSON, JSON Lines, or a schema-derived table
|
|
28
|
+
- `pathmx plugins list` lists installed official packages
|
|
29
|
+
- `pathmx plugins add <name...>` installs exact releases from the CLI's channel
|
|
30
|
+
- `pathmx plugins update [name...]` updates installed packages from the CLI's channel
|
|
31
|
+
- `pathmx update [--check]` checks or updates the globally installed CLI
|
|
32
|
+
- `pathmx help` shows help for a command
|
|
33
|
+
|
|
34
|
+
Repository plugins can add trusted administrator commands beneath their plugin
|
|
35
|
+
id, such as `pmx auth setup`, `pmx auth doctor`, `pmx auth users list`, and
|
|
36
|
+
`pmx auth sessions revoke-all`. The plugin must already be installed under the
|
|
37
|
+
project's sibling `plugins/` directory for its commands to be discovered.
|
|
38
|
+
Plugin command contexts include the portable Pattern installer for safely
|
|
39
|
+
planning and creating user-owned files beneath the source or project root.
|
|
40
|
+
|
|
41
|
+
`create` is not implemented yet. Global `--root` / `--actions` override the default `./paths` and sibling `actions/` directories.
|
|
42
|
+
|
|
43
|
+
## Export
|
|
44
|
+
|
|
45
|
+
`pmx export` defaults to a sibling `dist/` directory. It stages the complete
|
|
46
|
+
build before replacing the prior output and writes the adapter contract to
|
|
47
|
+
`.pathmx/manifest.json`. Static pages retain enhanced navigation and poll the
|
|
48
|
+
build marker for deployments; the marker points to an immutable page-leaf map
|
|
49
|
+
and is staged last. Completed page bodies stream into private directory staging
|
|
50
|
+
before the Merkle deployment root is known. Actions and other server-only
|
|
51
|
+
authored behavior produce export diagnostics.
|
|
52
|
+
|
|
53
|
+
The Book App collaboration fixture can be inspected directly:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
pmx --root labs/book-app/paths perms /users/alice/papers/reading-reflection.paper
|
|
57
|
+
pmx --root labs/book-app/paths perms /users/alice/papers/reading-reflection.paper --as /users/bob.user
|
|
58
|
+
pmx --root labs/book-app/paths perms --as /users/bob.user --format json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Source mode shows every known Actor plus anonymous access. Actor mode lists only
|
|
62
|
+
Sources that Actor can read. `--as` is trusted repository inspection; it does
|
|
63
|
+
not create a credential session or impersonate the Actor.
|
|
64
|
+
|
|
65
|
+
## Query
|
|
66
|
+
|
|
67
|
+
`pmx query` uses the same installed Schema, query kernel, access checks, field
|
|
68
|
+
catalog, and normalized request as `View.query`; it does not start a server or
|
|
69
|
+
read around View access.
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
pmx query sources \
|
|
73
|
+
--from /work/issues \
|
|
74
|
+
--type issue \
|
|
75
|
+
--where status=open \
|
|
76
|
+
--sort priority:desc
|
|
77
|
+
|
|
78
|
+
pmx query blocks --from /course --type task --where completed=false
|
|
79
|
+
|
|
80
|
+
pmx query schedule:occurrences \
|
|
81
|
+
--input '{"start":"2026-08-01T00:00:00Z","end":"2026-09-01T00:00:00Z"}' \
|
|
82
|
+
--format jsonl
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Common options are `--from`, repeatable `--where field=value`, repeatable
|
|
86
|
+
`--sort field[:asc|desc]`, `--limit`, `--format json|jsonl|table`, and trusted
|
|
87
|
+
`--as <actor>` inspection. Built-in Source and Block roots also support
|
|
88
|
+
`--type`, repeatable `--has <registered-prop>`, and `--no-recursive`. Named
|
|
89
|
+
queries accept their Standard Schema-validated input through `--input <json>`.
|
|
90
|
+
`--request <json-or-yaml-file>` (or `-` for stdin) accepts the complete request
|
|
91
|
+
shape and composes with explicit flags.
|
|
92
|
+
|
|
93
|
+
JSON is deterministic and intended to pipe into `jq` when custom projection or
|
|
94
|
+
transformation is needed. Table columns are discovered from the same required
|
|
95
|
+
Standard JSON Schema representation used by authored query tables.
|
package/cli/release.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import manifest from "../package.json" with { type: "json" }
|
|
2
|
+
|
|
3
|
+
type ReleaseManifest = Readonly<{
|
|
4
|
+
name: string
|
|
5
|
+
version: string
|
|
6
|
+
engines: { bun: string }
|
|
7
|
+
pathmxRelease: {
|
|
8
|
+
channel: "beta" | "latest"
|
|
9
|
+
testedBun: string
|
|
10
|
+
}
|
|
11
|
+
}>
|
|
12
|
+
|
|
13
|
+
const releaseManifest = manifest as ReleaseManifest
|
|
14
|
+
|
|
15
|
+
export const release = Object.freeze({
|
|
16
|
+
package: releaseManifest.name,
|
|
17
|
+
version: releaseManifest.version,
|
|
18
|
+
channel: releaseManifest.pathmxRelease.channel,
|
|
19
|
+
minimumBun: releaseManifest.engines.bun.replace(/^>=/, ""),
|
|
20
|
+
testedBun: releaseManifest.pathmxRelease.testedBun,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
function numericVersion(version: string) {
|
|
24
|
+
const [major = 0, minor = 0, patch = 0] = version
|
|
25
|
+
.replace(/^v/, "")
|
|
26
|
+
.split("-", 1)[0]!
|
|
27
|
+
.split(".")
|
|
28
|
+
.map((part) => Number.parseInt(part, 10))
|
|
29
|
+
return [major, minor, patch] as const
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function compareVersions(left: string, right: string) {
|
|
33
|
+
const a = numericVersion(left)
|
|
34
|
+
const b = numericVersion(right)
|
|
35
|
+
for (let index = 0; index < a.length; index++) {
|
|
36
|
+
const difference = a[index]! - b[index]!
|
|
37
|
+
if (difference) return Math.sign(difference)
|
|
38
|
+
}
|
|
39
|
+
return 0
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type BunCompatibility = Readonly<{
|
|
43
|
+
type: "supported" | "outdated" | "unsupported"
|
|
44
|
+
message?: string
|
|
45
|
+
}>
|
|
46
|
+
|
|
47
|
+
export function bunCompatibility(
|
|
48
|
+
current: string,
|
|
49
|
+
minimum = release.minimumBun,
|
|
50
|
+
tested = release.testedBun,
|
|
51
|
+
): BunCompatibility {
|
|
52
|
+
if (compareVersions(current, minimum) < 0) {
|
|
53
|
+
return {
|
|
54
|
+
type: "unsupported",
|
|
55
|
+
message: `PathMX ${release.version} requires Bun ${minimum} or newer; you are running ${current}. Run \`bun upgrade\` and try again.`,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (compareVersions(current, tested) < 0) {
|
|
59
|
+
return {
|
|
60
|
+
type: "outdated",
|
|
61
|
+
message: `PathMX ${release.version} is tested with Bun ${tested}; you are running ${current}. Run \`bun upgrade\` when convenient.`,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { type: "supported" }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function checkBunVersion(current = Bun.version) {
|
|
68
|
+
const compatibility = bunCompatibility(current)
|
|
69
|
+
if (compatibility.type === "unsupported") {
|
|
70
|
+
throw new Error(compatibility.message)
|
|
71
|
+
}
|
|
72
|
+
if (compatibility.type === "outdated") {
|
|
73
|
+
console.warn(`warning: ${compatibility.message}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
package/cli/rm.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { applySourceOpPlan, removeSource } from "../ops/index.ts"
|
|
3
|
+
import { fail, loadApp, printPlan, rootFromCommand } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
export function registerRm(program: Command) {
|
|
6
|
+
program
|
|
7
|
+
.command("rm")
|
|
8
|
+
.description("Remove a source and unlink references")
|
|
9
|
+
.argument("<path>", "source path to remove")
|
|
10
|
+
.option("--dry-run", "print the plan without writing")
|
|
11
|
+
.action(async (path: string, opts: { dryRun?: boolean }, cmd: Command) => {
|
|
12
|
+
try {
|
|
13
|
+
const { root, actionsRoot } = rootFromCommand(cmd)
|
|
14
|
+
const app = await loadApp(root, { actionsRoot, buildCache: false })
|
|
15
|
+
const plan = await removeSource(app, path)
|
|
16
|
+
printPlan(plan, Boolean(opts.dryRun))
|
|
17
|
+
if (!opts.dryRun) await applySourceOpPlan(app, plan)
|
|
18
|
+
} catch (error) {
|
|
19
|
+
fail(error)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
package/cli/serve.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { PathMX, handleRequest } from "../index.ts"
|
|
3
|
+
import { watchPaths } from "../server/watch.ts"
|
|
4
|
+
import { fail, rootFromCommand } from "./utils.ts"
|
|
5
|
+
|
|
6
|
+
export type ServerMode = "serve" | "dev"
|
|
7
|
+
|
|
8
|
+
export async function startServer(options: {
|
|
9
|
+
root: string
|
|
10
|
+
actionsRoot?: string
|
|
11
|
+
port?: number
|
|
12
|
+
hostname?: string
|
|
13
|
+
verbose?: boolean
|
|
14
|
+
mode?: ServerMode
|
|
15
|
+
}) {
|
|
16
|
+
const mode = options.mode ?? "serve"
|
|
17
|
+
const app = await PathMX(options.root, {
|
|
18
|
+
actionsRoot: options.actionsRoot,
|
|
19
|
+
runtime: {
|
|
20
|
+
mode: mode === "dev" ? "development" : "production",
|
|
21
|
+
},
|
|
22
|
+
diagnostics: mode === "dev" ? "collect" : "throw",
|
|
23
|
+
})
|
|
24
|
+
if (options.verbose) app.log.level = "debug"
|
|
25
|
+
const watch = watchPaths(app, options.root)
|
|
26
|
+
try {
|
|
27
|
+
await watch.subscription
|
|
28
|
+
const port = options.port ?? (Number(process.env.PORT) || 3000)
|
|
29
|
+
const hostname =
|
|
30
|
+
options.hostname ?? (mode === "dev" ? "127.0.0.1" : undefined)
|
|
31
|
+
const server = Bun.serve({
|
|
32
|
+
port,
|
|
33
|
+
...(hostname ? { hostname } : {}),
|
|
34
|
+
fetch(req, srv) {
|
|
35
|
+
// Quiet SSE streams count as idle. Bun's default 10s idleTimeout
|
|
36
|
+
// then kills /api/runtime/live mid-chunk; Chrome logs
|
|
37
|
+
// ERR_INCOMPLETE_CHUNKED_ENCODING and EventSource reconnects.
|
|
38
|
+
if (new URL(req.url).pathname === "/api/runtime/live")
|
|
39
|
+
srv.timeout(req, 0)
|
|
40
|
+
return handleRequest(app, req)
|
|
41
|
+
},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
app.log.info("listen", {
|
|
45
|
+
url: String(server.url),
|
|
46
|
+
sources: app.repo.all().length,
|
|
47
|
+
mode,
|
|
48
|
+
})
|
|
49
|
+
return {
|
|
50
|
+
app,
|
|
51
|
+
server,
|
|
52
|
+
async close() {
|
|
53
|
+
await watch.close()
|
|
54
|
+
await app.close()
|
|
55
|
+
server.stop(true)
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
await watch.close()
|
|
60
|
+
await app.close()
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function listenPort(value: string) {
|
|
66
|
+
const port = Number(value)
|
|
67
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
68
|
+
throw new Error(`Invalid port: ${value}`)
|
|
69
|
+
}
|
|
70
|
+
return port
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function registerServe(program: Command) {
|
|
74
|
+
program
|
|
75
|
+
.command("serve")
|
|
76
|
+
.description("Start the live production server")
|
|
77
|
+
.argument("[root]", "source root (defaults to ./paths)")
|
|
78
|
+
.option("-p, --port <port>", "port to listen on", listenPort)
|
|
79
|
+
.option("--host <hostname>", "hostname or address to listen on")
|
|
80
|
+
.action(
|
|
81
|
+
async (
|
|
82
|
+
root: string | undefined,
|
|
83
|
+
opts: { port?: number; host?: string },
|
|
84
|
+
cmd: Command,
|
|
85
|
+
) => {
|
|
86
|
+
try {
|
|
87
|
+
const resolved = rootFromCommand(cmd, root)
|
|
88
|
+
await startServer({
|
|
89
|
+
root: resolved.root,
|
|
90
|
+
actionsRoot: resolved.actionsRoot,
|
|
91
|
+
port: opts.port,
|
|
92
|
+
hostname: opts.host,
|
|
93
|
+
mode: "serve",
|
|
94
|
+
})
|
|
95
|
+
} catch (error) {
|
|
96
|
+
fail(error)
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
}
|
package/cli/tree.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { basename } from "node:path"
|
|
2
|
+
import type { Command } from "commander"
|
|
3
|
+
import pc from "picocolors"
|
|
4
|
+
import type { Engine } from "../host/engine.ts"
|
|
5
|
+
import type { Source } from "../source/index.ts"
|
|
6
|
+
import { fail, loadApp, rootFromCommand } from "./utils.ts"
|
|
7
|
+
|
|
8
|
+
type TrieNode = {
|
|
9
|
+
name: string
|
|
10
|
+
source?: Source
|
|
11
|
+
asset?: boolean
|
|
12
|
+
children: Map<string, TrieNode>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function insert(
|
|
16
|
+
root: TrieNode,
|
|
17
|
+
parts: string[],
|
|
18
|
+
source?: Source,
|
|
19
|
+
asset?: boolean,
|
|
20
|
+
) {
|
|
21
|
+
let node = root
|
|
22
|
+
for (const part of parts) {
|
|
23
|
+
let child = node.children.get(part)
|
|
24
|
+
if (!child) {
|
|
25
|
+
child = { name: part, children: new Map() }
|
|
26
|
+
node.children.set(part, child)
|
|
27
|
+
}
|
|
28
|
+
node = child
|
|
29
|
+
}
|
|
30
|
+
if (source) node.source = source
|
|
31
|
+
if (asset) node.asset = true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function annotate(source: Source, app: Engine) {
|
|
35
|
+
const outgoing = app.graph.linksFrom(source.path)
|
|
36
|
+
const inbound = app.graph.linksTo(source.path)
|
|
37
|
+
const unresolved = outgoing.filter(
|
|
38
|
+
(edge) => edge.type === "unresolved",
|
|
39
|
+
).length
|
|
40
|
+
const parts = [source.type]
|
|
41
|
+
if (source.blocks.length) {
|
|
42
|
+
parts.push(
|
|
43
|
+
`${source.blocks.length} block${source.blocks.length === 1 ? "" : "s"}`,
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
if (outgoing.length || inbound.length) {
|
|
47
|
+
parts.push(`${outgoing.length}→ ${inbound.length}←`)
|
|
48
|
+
}
|
|
49
|
+
const suffix = unresolved ? ` · ${pc.red(`${unresolved} unresolved`)}` : ""
|
|
50
|
+
return ` ${pc.dim(`(${parts.join(" · ")})`)}${suffix}`
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function render(
|
|
54
|
+
node: TrieNode,
|
|
55
|
+
prefix: string,
|
|
56
|
+
isLast: boolean,
|
|
57
|
+
lines: string[],
|
|
58
|
+
app: Engine,
|
|
59
|
+
) {
|
|
60
|
+
const branch = isLast ? "└── " : "├── "
|
|
61
|
+
const extra = node.source
|
|
62
|
+
? annotate(node.source, app)
|
|
63
|
+
: node.asset
|
|
64
|
+
? ` ${pc.dim("(asset)")}`
|
|
65
|
+
: ""
|
|
66
|
+
lines.push(`${prefix}${branch}${node.name}${extra}`)
|
|
67
|
+
const next = `${prefix}${isLast ? " " : "│ "}`
|
|
68
|
+
const children = [...node.children.values()].sort((a, b) =>
|
|
69
|
+
a.name.localeCompare(b.name),
|
|
70
|
+
)
|
|
71
|
+
children.forEach((child, index) => {
|
|
72
|
+
render(child, next, index === children.length - 1, lines, app)
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function formatTree(app: Engine, options: { all?: boolean } = {}) {
|
|
77
|
+
const trie: TrieNode = {
|
|
78
|
+
name: basename(app.repo.rootPath) || "paths",
|
|
79
|
+
children: new Map(),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const source of app.repo.all()) {
|
|
83
|
+
const parts = source.path.replace(/^\/+/, "").split("/").filter(Boolean)
|
|
84
|
+
insert(trie, parts, source)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (options.all) {
|
|
88
|
+
for (const file of new Bun.Glob("**/*").scanSync({
|
|
89
|
+
cwd: app.repo.rootPath,
|
|
90
|
+
onlyFiles: true,
|
|
91
|
+
})) {
|
|
92
|
+
if (file.endsWith(".md")) continue
|
|
93
|
+
insert(trie, file.split("/").filter(Boolean), undefined, true)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const lines = [`${trie.name}/`]
|
|
98
|
+
const children = [...trie.children.values()].sort((a, b) =>
|
|
99
|
+
a.name.localeCompare(b.name),
|
|
100
|
+
)
|
|
101
|
+
children.forEach((child, index) => {
|
|
102
|
+
render(child, "", index === children.length - 1, lines, app)
|
|
103
|
+
})
|
|
104
|
+
return lines.join("\n")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function registerTree(program: Command) {
|
|
108
|
+
program
|
|
109
|
+
.command("tree")
|
|
110
|
+
.description("Show the source/graph tree")
|
|
111
|
+
.argument("[root]", "source root (defaults to ./paths)")
|
|
112
|
+
.option("--all", "include non-markdown files")
|
|
113
|
+
.action(
|
|
114
|
+
async (
|
|
115
|
+
root: string | undefined,
|
|
116
|
+
opts: { all?: boolean },
|
|
117
|
+
cmd: Command,
|
|
118
|
+
) => {
|
|
119
|
+
try {
|
|
120
|
+
const resolved = rootFromCommand(cmd, root)
|
|
121
|
+
const app = await loadApp(resolved.root, {
|
|
122
|
+
actionsRoot: resolved.actionsRoot,
|
|
123
|
+
buildCache: false,
|
|
124
|
+
})
|
|
125
|
+
console.log(formatTree(app, { all: opts.all }))
|
|
126
|
+
} catch (error) {
|
|
127
|
+
fail(error)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
}
|
package/cli/update.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { compareVersions, release } from "./release.ts"
|
|
3
|
+
|
|
4
|
+
type RegistryMetadata = Readonly<{
|
|
5
|
+
"dist-tags"?: Readonly<Record<string, string>>
|
|
6
|
+
}>
|
|
7
|
+
|
|
8
|
+
type Fetcher = (
|
|
9
|
+
input: string | URL | Request,
|
|
10
|
+
init?: RequestInit,
|
|
11
|
+
) => Promise<Response>
|
|
12
|
+
|
|
13
|
+
export function releaseSpec() {
|
|
14
|
+
return `${release.package}@${release.channel}`
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function availableRelease(
|
|
18
|
+
fetcher: Fetcher = fetch,
|
|
19
|
+
): Promise<string> {
|
|
20
|
+
const response = await fetcher(
|
|
21
|
+
`https://registry.npmjs.org/${encodeURIComponent(release.package)}`,
|
|
22
|
+
{ headers: { Accept: "application/json" } },
|
|
23
|
+
)
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Unable to check ${releaseSpec()} (${response.status} ${response.statusText}).`,
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
const metadata = (await response.json()) as RegistryMetadata
|
|
30
|
+
const version = metadata["dist-tags"]?.[release.channel]
|
|
31
|
+
if (!version) {
|
|
32
|
+
throw new Error(`${releaseSpec()} does not have a published release.`)
|
|
33
|
+
}
|
|
34
|
+
return version
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function installRelease(spec: string) {
|
|
38
|
+
const child = Bun.spawn([process.execPath, "add", "--global", spec], {
|
|
39
|
+
stdin: "inherit",
|
|
40
|
+
stdout: "inherit",
|
|
41
|
+
stderr: "inherit",
|
|
42
|
+
})
|
|
43
|
+
const exitCode = await child.exited
|
|
44
|
+
if (exitCode !== 0) {
|
|
45
|
+
throw new Error(`Bun could not install ${spec} (exit ${exitCode}).`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function registerUpdate(program: Command) {
|
|
50
|
+
program
|
|
51
|
+
.command("update")
|
|
52
|
+
.description(`update the PathMX CLI from the ${release.channel} channel`)
|
|
53
|
+
.option("--check", "check for an update without installing it")
|
|
54
|
+
.action(async ({ check }: { check?: boolean }) => {
|
|
55
|
+
const available = await availableRelease()
|
|
56
|
+
const comparison = compareVersions(release.version, available)
|
|
57
|
+
if (comparison >= 0) {
|
|
58
|
+
const qualifier = comparison > 0 ? "newer than" : "matches"
|
|
59
|
+
console.log(
|
|
60
|
+
`PathMX ${release.version} ${qualifier} ${releaseSpec()} (${available}).`,
|
|
61
|
+
)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
if (check) {
|
|
65
|
+
console.log(
|
|
66
|
+
`PathMX ${available} is available on the ${release.channel} channel (installed: ${release.version}).`,
|
|
67
|
+
)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
console.log(
|
|
71
|
+
`Updating ${release.package} ${release.version} → ${available}`,
|
|
72
|
+
)
|
|
73
|
+
await installRelease(releaseSpec())
|
|
74
|
+
console.log(
|
|
75
|
+
`PathMX ${available} installed. Run \`pathmx --version\` to verify.`,
|
|
76
|
+
)
|
|
77
|
+
})
|
|
78
|
+
}
|
package/cli/utils.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { resolve } from "node:path"
|
|
2
|
+
import type { Command } from "commander"
|
|
3
|
+
import pc from "picocolors"
|
|
4
|
+
import { PathMX } from "../index.ts"
|
|
5
|
+
import type { SourceOpPlan } from "../ops/index.ts"
|
|
6
|
+
|
|
7
|
+
export function defaultSourceRoot() {
|
|
8
|
+
return resolve(process.cwd(), "paths")
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function resolveRoot(explicit?: string) {
|
|
12
|
+
return explicit ? resolve(explicit) : defaultSourceRoot()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function rootFromCommand(command: Command, positional?: string) {
|
|
16
|
+
const globals = command.optsWithGlobals() as {
|
|
17
|
+
root?: string
|
|
18
|
+
actions?: string
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
root: resolveRoot(positional ?? globals.root),
|
|
22
|
+
actionsRoot: globals.actions ? resolve(globals.actions) : undefined,
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function loadApp(
|
|
27
|
+
root: string,
|
|
28
|
+
options: Parameters<typeof PathMX>[1] = {},
|
|
29
|
+
) {
|
|
30
|
+
return PathMX(root, options)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function printPlan(plan: SourceOpPlan, dryRun: boolean) {
|
|
34
|
+
if (dryRun) console.log(pc.bold("Dry run"))
|
|
35
|
+
for (const write of plan.writes) {
|
|
36
|
+
console.log(`${pc.green("write")} ${write.path}`)
|
|
37
|
+
}
|
|
38
|
+
for (const path of plan.deletes) {
|
|
39
|
+
console.log(`${pc.red("delete")} ${path}`)
|
|
40
|
+
}
|
|
41
|
+
if (!plan.writes.length && !plan.deletes.length) {
|
|
42
|
+
console.log(pc.dim("No file changes"))
|
|
43
|
+
}
|
|
44
|
+
for (const warning of plan.warnings) {
|
|
45
|
+
console.log(`${pc.yellow("warning")} ${warning}`)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function fail(error: unknown): never {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
51
|
+
console.error(pc.red(message))
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
package/core.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export type { Actor, CredentialSession, UserActor } from "./authority/model.ts"
|
|
2
|
+
export type {
|
|
3
|
+
BlockSummary,
|
|
4
|
+
SourceDescription,
|
|
5
|
+
SourceRelation,
|
|
6
|
+
SourceSummary,
|
|
7
|
+
SourceView,
|
|
8
|
+
View,
|
|
9
|
+
ViewIdentity,
|
|
10
|
+
} from "./view/model.ts"
|
|
11
|
+
export { html } from "./html.ts"
|
|
12
|
+
export type {
|
|
13
|
+
ActorAdmission,
|
|
14
|
+
CredentialDirectory,
|
|
15
|
+
CredentialHost,
|
|
16
|
+
CredentialProvider,
|
|
17
|
+
} from "./authority/credentials.ts"
|
|
18
|
+
export {
|
|
19
|
+
anonymousCredentialHost,
|
|
20
|
+
createCredentialHost,
|
|
21
|
+
} from "./authority/credentials.ts"
|
|
22
|
+
export type {
|
|
23
|
+
AppContext,
|
|
24
|
+
Plugin,
|
|
25
|
+
PluginCommandContext,
|
|
26
|
+
PluginContext,
|
|
27
|
+
PluginFactory,
|
|
28
|
+
PluginLog,
|
|
29
|
+
PublishContext,
|
|
30
|
+
ServerContext,
|
|
31
|
+
} from "./plugins/types.ts"
|
|
32
|
+
export type { PluginPreset } from "./plugins/preset.ts"
|
|
33
|
+
export { defineSchema } from "./schema/model.ts"
|
|
34
|
+
export {
|
|
35
|
+
definePlugin,
|
|
36
|
+
defineViewComponent,
|
|
37
|
+
defineViewVariable,
|
|
38
|
+
route,
|
|
39
|
+
} from "./plugins/types.ts"
|
|
40
|
+
export { definePreset } from "./plugins/preset.ts"
|
|
41
|
+
export { defineAppComponent } from "./plugins/app-component.ts"
|
|
42
|
+
export type { PluginDatabase, SqlDatabase, SqlValue } from "./database.ts"
|
|
43
|
+
export type {
|
|
44
|
+
MarkdownList,
|
|
45
|
+
MarkdownListItem,
|
|
46
|
+
MarkdownTask,
|
|
47
|
+
Source,
|
|
48
|
+
SourceBlock,
|
|
49
|
+
} from "./source/index.ts"
|
|
50
|
+
export {
|
|
51
|
+
formatFrontmatter,
|
|
52
|
+
isFenceClose,
|
|
53
|
+
markdownListItemLink,
|
|
54
|
+
matchFenceOpen,
|
|
55
|
+
splitFrontmatter,
|
|
56
|
+
} from "./source/index.ts"
|
|
57
|
+
export type {
|
|
58
|
+
Action,
|
|
59
|
+
ActionContext,
|
|
60
|
+
ActionAffordance,
|
|
61
|
+
} from "./actions/actions.ts"
|
|
62
|
+
export type {
|
|
63
|
+
ApplicationSchema,
|
|
64
|
+
BlockType,
|
|
65
|
+
BlockTypeDefinition,
|
|
66
|
+
DataProp,
|
|
67
|
+
NodeSchema,
|
|
68
|
+
NodeType,
|
|
69
|
+
QuerySchema,
|
|
70
|
+
SchemaContribution,
|
|
71
|
+
SourceType,
|
|
72
|
+
SourceTypeDefinition,
|
|
73
|
+
} from "./schema/model.ts"
|
|
74
|
+
export type {
|
|
75
|
+
PathMXDataSchema,
|
|
76
|
+
StandardJSONSchemaV1,
|
|
77
|
+
StandardSchemaV1,
|
|
78
|
+
} from "./schema/standard.ts"
|
|
79
|
+
export type {
|
|
80
|
+
BlockQueryRequest,
|
|
81
|
+
QueryDefinition,
|
|
82
|
+
QueryRequest,
|
|
83
|
+
QueryResolverContext,
|
|
84
|
+
QueryScalar,
|
|
85
|
+
QuerySort,
|
|
86
|
+
SourceQueryRequest,
|
|
87
|
+
ViewQuery,
|
|
88
|
+
} from "./query/model.ts"
|
|
89
|
+
export { definePattern } from "./pattern.ts"
|
|
90
|
+
export { defineAction } from "./actions/actions.ts"
|
|
91
|
+
export type { SourceTransaction } from "./actions/transaction.ts"
|
|
92
|
+
export type { PathMXEnvironment } from "./environment.ts"
|
|
93
|
+
export { createApp } from "./host/create.ts"
|
|
94
|
+
export { handleRequest } from "./server/http.ts"
|
|
95
|
+
export { privateResponse } from "./host/response.ts"
|
|
96
|
+
export { Log } from "./log.ts"
|