@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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { BundledAsset, PathMXEnvironment } from "./environment.ts"
|
|
2
|
+
import { documentAssetRank } from "./document.ts"
|
|
3
|
+
|
|
4
|
+
type BrowserBundleHost = Pick<PathMXEnvironment, "bundle">
|
|
5
|
+
|
|
6
|
+
type BrowserDocumentAsset =
|
|
7
|
+
| Readonly<{
|
|
8
|
+
type: "stylesheet"
|
|
9
|
+
name: string
|
|
10
|
+
text: string
|
|
11
|
+
}>
|
|
12
|
+
| Readonly<{
|
|
13
|
+
type: "script"
|
|
14
|
+
name: string
|
|
15
|
+
text: string
|
|
16
|
+
module: true
|
|
17
|
+
}>
|
|
18
|
+
|
|
19
|
+
function documentAsset(
|
|
20
|
+
stem: string,
|
|
21
|
+
output: BundledAsset,
|
|
22
|
+
): BrowserDocumentAsset {
|
|
23
|
+
if (output.type === "file") {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Browser bundle ${stem} emitted unsupported file asset ${output.path}.`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
return output.type === "style"
|
|
29
|
+
? {
|
|
30
|
+
type: "stylesheet",
|
|
31
|
+
name: `${stem}.css`,
|
|
32
|
+
text: output.text,
|
|
33
|
+
}
|
|
34
|
+
: {
|
|
35
|
+
type: "script",
|
|
36
|
+
name: `${stem}.js`,
|
|
37
|
+
text: output.text,
|
|
38
|
+
module: true,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** @internal Bundle one browser entrypoint into ordered document assets. */
|
|
43
|
+
export async function browserBundleAssets(
|
|
44
|
+
host: BrowserBundleHost,
|
|
45
|
+
entrypoint: string | URL,
|
|
46
|
+
name: string,
|
|
47
|
+
) {
|
|
48
|
+
const stem = name.replaceAll(":", "-")
|
|
49
|
+
const names = new Set<string>()
|
|
50
|
+
const assets = (await host.bundle(entrypoint)).map((output) => {
|
|
51
|
+
const asset = documentAsset(stem, output)
|
|
52
|
+
if (names.has(asset.name)) {
|
|
53
|
+
throw new Error(`Browser bundle ${stem} emitted duplicate ${asset.name}.`)
|
|
54
|
+
}
|
|
55
|
+
names.add(asset.name)
|
|
56
|
+
return asset
|
|
57
|
+
})
|
|
58
|
+
return assets.sort(
|
|
59
|
+
(left, right) => documentAssetRank(left) - documentAssetRank(right),
|
|
60
|
+
)
|
|
61
|
+
}
|
package/canonical.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export class CanonicalPathError extends Error {
|
|
2
|
+
constructor(value: string) {
|
|
3
|
+
super(`Invalid canonical path: ${value}`)
|
|
4
|
+
this.name = "CanonicalPathError"
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function canonicalPath(value: string) {
|
|
9
|
+
if (
|
|
10
|
+
!value.startsWith("/") ||
|
|
11
|
+
value.includes("\\") ||
|
|
12
|
+
value.includes("\0") ||
|
|
13
|
+
value.includes("?") ||
|
|
14
|
+
value.includes("#") ||
|
|
15
|
+
(value.length > 1 && value.endsWith("/"))
|
|
16
|
+
) {
|
|
17
|
+
throw new CanonicalPathError(value)
|
|
18
|
+
}
|
|
19
|
+
if (value === "/") return value
|
|
20
|
+
const segments = value.slice(1).split("/")
|
|
21
|
+
if (
|
|
22
|
+
segments.some((segment) => !segment || segment === "." || segment === "..")
|
|
23
|
+
) {
|
|
24
|
+
throw new CanonicalPathError(value)
|
|
25
|
+
}
|
|
26
|
+
return value
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function canonicalSourceId(value: string) {
|
|
30
|
+
const path = canonicalPath(value)
|
|
31
|
+
if (path === "/" || path.endsWith(".md")) throw new CanonicalPathError(value)
|
|
32
|
+
return path
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function tryCanonicalPath(value: string) {
|
|
36
|
+
try {
|
|
37
|
+
return canonicalPath(value)
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined
|
|
40
|
+
}
|
|
41
|
+
}
|
package/cli/dev.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import watcher from "@parcel/watcher"
|
|
2
|
+
import { fileURLToPath } from "node:url"
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
4
|
+
import type { Command } from "commander"
|
|
5
|
+
import { defaultPluginsRoot } from "../environments/bun-plugins.ts"
|
|
6
|
+
import { fail, rootFromCommand } from "./utils.ts"
|
|
7
|
+
import { listenPort, startServer } from "./serve.ts"
|
|
8
|
+
|
|
9
|
+
// `pmx dev` runs in two layers. Bun restarts imported server modules; the outer
|
|
10
|
+
// watcher covers newly discovered and browser-only plugin files. Source edits
|
|
11
|
+
// stay on the in-process sync path shared by `serve` and `dev`.
|
|
12
|
+
const DEV_CHILD_ENV = "PMX_DEV_CHILD"
|
|
13
|
+
|
|
14
|
+
export function registerDev(program: Command) {
|
|
15
|
+
program
|
|
16
|
+
.command("dev")
|
|
17
|
+
.description("Start the development server (hot-restarts on code changes)")
|
|
18
|
+
.argument("[root]", "source root (defaults to ./paths)")
|
|
19
|
+
.option("-p, --port <port>", "port to listen on", listenPort)
|
|
20
|
+
.action(
|
|
21
|
+
async (
|
|
22
|
+
root: string | undefined,
|
|
23
|
+
opts: { port?: number },
|
|
24
|
+
cmd: Command,
|
|
25
|
+
) => {
|
|
26
|
+
const resolved = rootFromCommand(cmd, root)
|
|
27
|
+
if (process.env[DEV_CHILD_ENV]) {
|
|
28
|
+
try {
|
|
29
|
+
await startServer({
|
|
30
|
+
root: resolved.root,
|
|
31
|
+
actionsRoot: resolved.actionsRoot,
|
|
32
|
+
port: opts.port,
|
|
33
|
+
verbose: true,
|
|
34
|
+
mode: "dev",
|
|
35
|
+
})
|
|
36
|
+
} catch (error) {
|
|
37
|
+
// Don't exit: `bun --watch` keeps the process alive so the server
|
|
38
|
+
// can come back on the next code change instead of dying for good.
|
|
39
|
+
console.error(error)
|
|
40
|
+
}
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await runWatched(resolved, opts.port)
|
|
45
|
+
} catch (error) {
|
|
46
|
+
fail(error)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function runWatched(
|
|
53
|
+
resolved: { root: string; actionsRoot?: string },
|
|
54
|
+
port?: number,
|
|
55
|
+
) {
|
|
56
|
+
const entry = fileURLToPath(new URL("./index.ts", import.meta.url))
|
|
57
|
+
const args = [process.execPath, "--watch", entry, "dev", resolved.root]
|
|
58
|
+
if (resolved.actionsRoot) args.push("--actions", resolved.actionsRoot)
|
|
59
|
+
if (port !== undefined) args.push("--port", String(port))
|
|
60
|
+
|
|
61
|
+
const pluginsRoot = resolve(defaultPluginsRoot(resolved.root))
|
|
62
|
+
const projectRoot = dirname(pluginsRoot)
|
|
63
|
+
let child: ReturnType<typeof Bun.spawn> | undefined
|
|
64
|
+
let stopped = false
|
|
65
|
+
let requestedGeneration = 0
|
|
66
|
+
let restartTimer: ReturnType<typeof setTimeout> | undefined
|
|
67
|
+
|
|
68
|
+
const spawn = () =>
|
|
69
|
+
Bun.spawn(args, {
|
|
70
|
+
env: { ...process.env, [DEV_CHILD_ENV]: "1" },
|
|
71
|
+
stdin: "inherit",
|
|
72
|
+
stdout: "inherit",
|
|
73
|
+
stderr: "inherit",
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const restart = () => {
|
|
77
|
+
requestedGeneration++
|
|
78
|
+
if (restartTimer) clearTimeout(restartTimer)
|
|
79
|
+
restartTimer = setTimeout(() => {
|
|
80
|
+
restartTimer = undefined
|
|
81
|
+
child?.kill()
|
|
82
|
+
}, 75)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const subscription = await watcher.subscribe(
|
|
86
|
+
projectRoot,
|
|
87
|
+
(error, events) => {
|
|
88
|
+
if (error) {
|
|
89
|
+
console.error(error)
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (events.some((event) => isPluginEvent(pluginsRoot, event.path))) {
|
|
93
|
+
restart()
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
ignore: [
|
|
98
|
+
resolve(resolved.root),
|
|
99
|
+
join(projectRoot, ".git"),
|
|
100
|
+
join(projectRoot, "node_modules"),
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
106
|
+
process.on(signal, () => {
|
|
107
|
+
stopped = true
|
|
108
|
+
if (restartTimer) clearTimeout(restartTimer)
|
|
109
|
+
child?.kill(signal)
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let exitCode = 0
|
|
114
|
+
while (!stopped) {
|
|
115
|
+
const generation = requestedGeneration
|
|
116
|
+
child = spawn()
|
|
117
|
+
exitCode = await child.exited
|
|
118
|
+
child = undefined
|
|
119
|
+
if (requestedGeneration === generation) break
|
|
120
|
+
}
|
|
121
|
+
await subscription.unsubscribe()
|
|
122
|
+
process.exit(exitCode)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isPluginEvent(root: string, path: string) {
|
|
126
|
+
const file = relative(root, path)
|
|
127
|
+
return (
|
|
128
|
+
!!file &&
|
|
129
|
+
!isAbsolute(file) &&
|
|
130
|
+
file !== ".." &&
|
|
131
|
+
!file.startsWith(`..${sep}`) &&
|
|
132
|
+
!file.split(/[\\/]/).includes("node_modules")
|
|
133
|
+
)
|
|
134
|
+
}
|
package/cli/export.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path"
|
|
3
|
+
import { publishToDirectory } from "../environments/bun-publish.ts"
|
|
4
|
+
import { PathMX } from "../index.ts"
|
|
5
|
+
import { projectDirectory } from "../project.ts"
|
|
6
|
+
import { createPublication } from "../publish/build.ts"
|
|
7
|
+
import { fail, rootFromCommand } from "./utils.ts"
|
|
8
|
+
|
|
9
|
+
function contains(parent: string, child: string) {
|
|
10
|
+
const path = relative(parent, child)
|
|
11
|
+
return path === "" || (!path.startsWith("..") && !isAbsolute(path))
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function overlaps(left: string, right: string) {
|
|
15
|
+
return contains(left, right) || contains(right, left)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function outputPath(root: string, explicit?: string) {
|
|
19
|
+
const output = resolve(explicit ?? projectDirectory(root, "dist"))
|
|
20
|
+
if (overlaps(output, resolve(root))) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"Export output must not contain or be inside the paths root.",
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
return output
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerExport(program: Command) {
|
|
29
|
+
program
|
|
30
|
+
.command("export")
|
|
31
|
+
.description("Export the public application as a static publication")
|
|
32
|
+
.argument("[root]", "source root (defaults to ./paths)")
|
|
33
|
+
.requiredOption("--origin <url>", "canonical public HTTP(S) origin")
|
|
34
|
+
.option("--base-path <path>", "public mount path (defaults to /)")
|
|
35
|
+
.option("-o, --out <path>", "output directory (defaults beside paths)")
|
|
36
|
+
.action(
|
|
37
|
+
async (
|
|
38
|
+
root: string | undefined,
|
|
39
|
+
options: { origin: string; basePath?: string; out?: string },
|
|
40
|
+
command: Command,
|
|
41
|
+
) => {
|
|
42
|
+
let app
|
|
43
|
+
let failed = false
|
|
44
|
+
let failure: unknown
|
|
45
|
+
try {
|
|
46
|
+
const resolved = rootFromCommand(command, root)
|
|
47
|
+
const output = outputPath(resolved.root, options.out)
|
|
48
|
+
const exportingApp = await PathMX(resolved.root, {
|
|
49
|
+
actionsRoot: resolved.actionsRoot,
|
|
50
|
+
diagnostics: "throw",
|
|
51
|
+
runtime: { mode: "production", target: "static" },
|
|
52
|
+
})
|
|
53
|
+
app = exportingApp
|
|
54
|
+
const manifest = await publishToDirectory(output, async (store) => {
|
|
55
|
+
const publication = await createPublication(exportingApp, {
|
|
56
|
+
origin: options.origin,
|
|
57
|
+
basePath: options.basePath,
|
|
58
|
+
store,
|
|
59
|
+
})
|
|
60
|
+
await exportingApp.close()
|
|
61
|
+
app = undefined
|
|
62
|
+
return publication
|
|
63
|
+
})
|
|
64
|
+
console.log(`Exported ${manifest.routes.length} routes to ${output}`)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
failed = true
|
|
67
|
+
failure = error
|
|
68
|
+
} finally {
|
|
69
|
+
await app?.close().catch(() => undefined)
|
|
70
|
+
}
|
|
71
|
+
if (failed) fail(failure)
|
|
72
|
+
},
|
|
73
|
+
)
|
|
74
|
+
}
|
package/cli/index.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { Command } from "commander"
|
|
3
|
+
import { registerDev } from "./dev.ts"
|
|
4
|
+
import { registerExport } from "./export.ts"
|
|
5
|
+
import { registerLint } from "./lint.ts"
|
|
6
|
+
import { registerMv } from "./mv.ts"
|
|
7
|
+
import { registerPerms } from "./perms.ts"
|
|
8
|
+
import { registerRm } from "./rm.ts"
|
|
9
|
+
import { registerServe } from "./serve.ts"
|
|
10
|
+
import { registerTree } from "./tree.ts"
|
|
11
|
+
import { registerPluginCommands } from "./plugins.ts"
|
|
12
|
+
import { registerPluginPackages } from "./plugin-packages.ts"
|
|
13
|
+
import { registerQuery } from "./query.ts"
|
|
14
|
+
import { checkBunVersion, release } from "./release.ts"
|
|
15
|
+
import { registerUpdate } from "./update.ts"
|
|
16
|
+
|
|
17
|
+
export function createProgram() {
|
|
18
|
+
const program = new Command()
|
|
19
|
+
.name("pathmx")
|
|
20
|
+
.description("Manage and serve PathMX projects")
|
|
21
|
+
.version(release.version)
|
|
22
|
+
.option("-r, --root <path>", "source root (defaults to ./paths)")
|
|
23
|
+
.option("-a, --actions <path>", "actions root")
|
|
24
|
+
|
|
25
|
+
registerServe(program)
|
|
26
|
+
registerDev(program)
|
|
27
|
+
registerExport(program)
|
|
28
|
+
registerMv(program)
|
|
29
|
+
registerRm(program)
|
|
30
|
+
registerTree(program)
|
|
31
|
+
registerPerms(program)
|
|
32
|
+
registerLint(program)
|
|
33
|
+
registerQuery(program)
|
|
34
|
+
registerPluginPackages(program)
|
|
35
|
+
registerUpdate(program)
|
|
36
|
+
return program
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runCli(argv = process.argv) {
|
|
40
|
+
checkBunVersion()
|
|
41
|
+
const program = createProgram()
|
|
42
|
+
const close = await registerPluginCommands(program, argv)
|
|
43
|
+
try {
|
|
44
|
+
await program.parseAsync(argv)
|
|
45
|
+
} finally {
|
|
46
|
+
await close()
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (import.meta.main) {
|
|
51
|
+
await runCli()
|
|
52
|
+
}
|
package/cli/lint.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import pc from "picocolors"
|
|
3
|
+
import type { Engine } from "../host/engine.ts"
|
|
4
|
+
import { Log } from "../log.ts"
|
|
5
|
+
import { SourceDiagnosticError } from "../source-diagnostic.ts"
|
|
6
|
+
import { fail, loadApp, rootFromCommand } from "./utils.ts"
|
|
7
|
+
|
|
8
|
+
export type UnresolvedLinkIssue = Readonly<{
|
|
9
|
+
type: "link.unresolved"
|
|
10
|
+
source: string
|
|
11
|
+
href: string
|
|
12
|
+
label?: string
|
|
13
|
+
}>
|
|
14
|
+
|
|
15
|
+
export type InvalidSourceIssue = Readonly<{
|
|
16
|
+
type: "source.invalid"
|
|
17
|
+
source: string
|
|
18
|
+
plugin: string
|
|
19
|
+
message: string
|
|
20
|
+
}>
|
|
21
|
+
|
|
22
|
+
export type LintIssue = UnresolvedLinkIssue | InvalidSourceIssue
|
|
23
|
+
|
|
24
|
+
export function collectUnresolved(app: Engine) {
|
|
25
|
+
const issues: UnresolvedLinkIssue[] = []
|
|
26
|
+
for (const edge of app.graph.unresolvedLinks()) {
|
|
27
|
+
const source = app.repo.atPath(edge.from)
|
|
28
|
+
const link = source?.links.find((item) => item.href === edge.href)
|
|
29
|
+
issues.push({
|
|
30
|
+
type: "link.unresolved",
|
|
31
|
+
source: edge.from,
|
|
32
|
+
href: edge.href,
|
|
33
|
+
...(link?.label ? { label: link.label } : {}),
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
return issues
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function issueKey(issue: LintIssue) {
|
|
40
|
+
return issue.type === "link.unresolved"
|
|
41
|
+
? `${issue.type}\0${issue.source}\0${issue.href}\0${issue.label ?? ""}`
|
|
42
|
+
: `${issue.type}\0${issue.source}\0${issue.plugin}\0${issue.message}`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compareIssues(a: LintIssue, b: LintIssue) {
|
|
46
|
+
return (
|
|
47
|
+
a.type.localeCompare(b.type) ||
|
|
48
|
+
a.source.localeCompare(b.source) ||
|
|
49
|
+
issueKey(a).localeCompare(issueKey(b))
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function linkText(issue: UnresolvedLinkIssue) {
|
|
54
|
+
const link = issue.label ? `[${issue.label}](${issue.href})` : issue.href
|
|
55
|
+
return `${issue.source}: ${link}`
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatLintIssues(issues: readonly LintIssue[]) {
|
|
59
|
+
if (!issues.length) return pc.green("No issues found")
|
|
60
|
+
|
|
61
|
+
const links: UnresolvedLinkIssue[] = []
|
|
62
|
+
const authoring: InvalidSourceIssue[] = []
|
|
63
|
+
for (const issue of issues) {
|
|
64
|
+
switch (issue.type) {
|
|
65
|
+
case "link.unresolved":
|
|
66
|
+
links.push(issue)
|
|
67
|
+
break
|
|
68
|
+
case "source.invalid":
|
|
69
|
+
authoring.push(issue)
|
|
70
|
+
break
|
|
71
|
+
default:
|
|
72
|
+
issue satisfies never
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const sections: string[] = []
|
|
76
|
+
if (links.length) {
|
|
77
|
+
sections.push(
|
|
78
|
+
[
|
|
79
|
+
pc.bold(`Unresolved links (${links.length})`),
|
|
80
|
+
...links.map(linkText),
|
|
81
|
+
].join("\n"),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
if (authoring.length) {
|
|
85
|
+
sections.push(
|
|
86
|
+
[
|
|
87
|
+
pc.bold(`Authoring issues (${authoring.length})`),
|
|
88
|
+
...authoring.map((issue) => issue.message),
|
|
89
|
+
].join("\n"),
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const noun = issues.length === 1 ? "issue" : "issues"
|
|
94
|
+
sections.push(pc.red(`${issues.length} ${noun} found`))
|
|
95
|
+
return sections.join("\n\n")
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function collectLintIssues(app: Engine) {
|
|
99
|
+
const issues = new Map<string, LintIssue>(
|
|
100
|
+
collectUnresolved(app).map((issue) => [issueKey(issue), issue]),
|
|
101
|
+
)
|
|
102
|
+
const addDiagnostic = (diagnostic: {
|
|
103
|
+
source: string
|
|
104
|
+
plugin: string
|
|
105
|
+
message: string
|
|
106
|
+
}) => {
|
|
107
|
+
const issue: InvalidSourceIssue = {
|
|
108
|
+
type: "source.invalid",
|
|
109
|
+
...diagnostic,
|
|
110
|
+
}
|
|
111
|
+
issues.set(issueKey(issue), issue)
|
|
112
|
+
}
|
|
113
|
+
for (const diagnostic of app.sourceDiagnostics()) {
|
|
114
|
+
addDiagnostic(diagnostic)
|
|
115
|
+
}
|
|
116
|
+
for (const source of app.repo.all()) {
|
|
117
|
+
if (!app.isPage(source)) continue
|
|
118
|
+
try {
|
|
119
|
+
await app.compilePage(source)
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (!(error instanceof SourceDiagnosticError)) throw error
|
|
122
|
+
addDiagnostic(error.diagnostic)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const plugin of app.plugins) {
|
|
126
|
+
if (!plugin.lint) continue
|
|
127
|
+
await plugin.lint((source, message) => {
|
|
128
|
+
const normalized = message.trim()
|
|
129
|
+
if (!normalized) {
|
|
130
|
+
throw new Error(`Plugin ${plugin.id} reported an empty lint message.`)
|
|
131
|
+
}
|
|
132
|
+
addDiagnostic({
|
|
133
|
+
source: source.path,
|
|
134
|
+
plugin: plugin.id,
|
|
135
|
+
message: normalized,
|
|
136
|
+
})
|
|
137
|
+
}, app.appContext(plugin.id))
|
|
138
|
+
}
|
|
139
|
+
return [...issues.values()].sort(compareIssues)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function registerLint(program: Command) {
|
|
143
|
+
program
|
|
144
|
+
.command("lint")
|
|
145
|
+
.description("Validate authored Sources without starting a server")
|
|
146
|
+
.argument("[root]", "source root (defaults to ./paths)")
|
|
147
|
+
.action(async (root: string | undefined, _opts: unknown, cmd: Command) => {
|
|
148
|
+
try {
|
|
149
|
+
const resolved = rootFromCommand(cmd, root)
|
|
150
|
+
const app = await loadApp(resolved.root, {
|
|
151
|
+
actionsRoot: resolved.actionsRoot,
|
|
152
|
+
buildCache: false,
|
|
153
|
+
diagnostics: "collect",
|
|
154
|
+
log: new Log({ sink() {} }),
|
|
155
|
+
})
|
|
156
|
+
const issues = await collectLintIssues(app)
|
|
157
|
+
console.log(formatLintIssues(issues))
|
|
158
|
+
if (!issues.length) return
|
|
159
|
+
process.exitCode = 1
|
|
160
|
+
} catch (error) {
|
|
161
|
+
fail(error)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
}
|
package/cli/mv.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Command } from "commander"
|
|
2
|
+
import { applySourceOpPlan, moveSource } from "../ops/index.ts"
|
|
3
|
+
import { fail, loadApp, printPlan, rootFromCommand } from "./utils.ts"
|
|
4
|
+
|
|
5
|
+
export function registerMv(program: Command) {
|
|
6
|
+
program
|
|
7
|
+
.command("mv")
|
|
8
|
+
.description("Move a source and rewrite linked references")
|
|
9
|
+
.argument("<old-path>", "existing source path")
|
|
10
|
+
.argument("<new-path>", "destination markdown path")
|
|
11
|
+
.option("--dry-run", "print the plan without writing")
|
|
12
|
+
.action(
|
|
13
|
+
async (
|
|
14
|
+
oldPath: string,
|
|
15
|
+
newPath: string,
|
|
16
|
+
opts: { dryRun?: boolean },
|
|
17
|
+
cmd: Command,
|
|
18
|
+
) => {
|
|
19
|
+
try {
|
|
20
|
+
const { root, actionsRoot } = rootFromCommand(cmd)
|
|
21
|
+
const app = await loadApp(root, { actionsRoot, buildCache: false })
|
|
22
|
+
const plan = await moveSource(app, oldPath, newPath)
|
|
23
|
+
printPlan(plan, Boolean(opts.dryRun))
|
|
24
|
+
if (!opts.dryRun) await applySourceOpPlan(app, plan)
|
|
25
|
+
} catch (error) {
|
|
26
|
+
fail(error)
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
)
|
|
30
|
+
}
|