@nawc/cli 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +13 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +210 -0
- package/dist/index.d.mts +26 -0
- package/dist/index.mjs +15 -0
- package/dist/server-B0NGps95.mjs +1646 -0
- package/package.json +71 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { n as assertGitRepository, t as createNawcServer } from "./server-B0NGps95.mjs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { createJiti } from "jiti";
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { parseFragment } from "parse5";
|
|
9
|
+
import { Command } from "commander";
|
|
10
|
+
//#region src/splash.ts
|
|
11
|
+
const execFileAsync = promisify(execFile);
|
|
12
|
+
function isElement(node) {
|
|
13
|
+
return "tagName" in node && Array.isArray(node.attrs);
|
|
14
|
+
}
|
|
15
|
+
function notePathFor(target) {
|
|
16
|
+
return target.endsWith(".html") ? target : `${target}.html`;
|
|
17
|
+
}
|
|
18
|
+
function normalizeWikiLinkTarget(target) {
|
|
19
|
+
return notePathFor(target.trim());
|
|
20
|
+
}
|
|
21
|
+
function extractWikiLinks(html) {
|
|
22
|
+
const root = parseFragment(html);
|
|
23
|
+
const seen = /* @__PURE__ */ new Set();
|
|
24
|
+
const visit = (node) => {
|
|
25
|
+
if (isElement(node)) {
|
|
26
|
+
if (node.nodeName === "a") {
|
|
27
|
+
const explicit = node.attrs.find((attr) => attr.name === "data-wiki-link")?.value.trim();
|
|
28
|
+
if (explicit) seen.add(normalizeWikiLinkTarget(explicit));
|
|
29
|
+
else {
|
|
30
|
+
const match = node.childNodes.map((child) => "value" in child ? child.value ?? "" : "").join("").trim().match(/^\[\[([^\]]+)\]\]$/);
|
|
31
|
+
if (match) seen.add(normalizeWikiLinkTarget(match[1]));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const children = "childNodes" in node ? node.childNodes : [];
|
|
36
|
+
for (const child of children) visit(child);
|
|
37
|
+
};
|
|
38
|
+
visit(root);
|
|
39
|
+
return [...seen].sort();
|
|
40
|
+
}
|
|
41
|
+
function extractFileReferences(html, plugins) {
|
|
42
|
+
const seen = /* @__PURE__ */ new Set();
|
|
43
|
+
for (const plugin of plugins) {
|
|
44
|
+
if (!plugin.references) continue;
|
|
45
|
+
for (const ref of plugin.references({ html })) {
|
|
46
|
+
const trimmed = ref.path.trim();
|
|
47
|
+
if (trimmed) seen.add(trimmed);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return [...seen].sort();
|
|
51
|
+
}
|
|
52
|
+
const MERGE_CONFLICT_STATUSES = /* @__PURE__ */ new Set([
|
|
53
|
+
"UU",
|
|
54
|
+
"AA",
|
|
55
|
+
"DD",
|
|
56
|
+
"AU",
|
|
57
|
+
"UA",
|
|
58
|
+
"UD",
|
|
59
|
+
"DU"
|
|
60
|
+
]);
|
|
61
|
+
/**
|
|
62
|
+
* Returns project files modified in the working tree relative to the index,
|
|
63
|
+
* including untracked files but excluding ignored and merge-conflict entries.
|
|
64
|
+
*/
|
|
65
|
+
async function getModifiedProjectFiles(baseDir) {
|
|
66
|
+
const { stdout } = await execFileAsync("git", [
|
|
67
|
+
"-C",
|
|
68
|
+
baseDir,
|
|
69
|
+
"status",
|
|
70
|
+
"--porcelain",
|
|
71
|
+
"--untracked-files=all"
|
|
72
|
+
], {
|
|
73
|
+
encoding: "utf8",
|
|
74
|
+
maxBuffer: 16 * 1024 * 1024
|
|
75
|
+
});
|
|
76
|
+
const files = /* @__PURE__ */ new Set();
|
|
77
|
+
for (const line of stdout.split("\n")) {
|
|
78
|
+
if (line.length < 4) continue;
|
|
79
|
+
const status = line.slice(0, 2);
|
|
80
|
+
if (status === "!!") continue;
|
|
81
|
+
if (MERGE_CONFLICT_STATUSES.has(status)) continue;
|
|
82
|
+
const rest = line.slice(3);
|
|
83
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
84
|
+
const match = rest.match(/^(.+) -> (.+)$/);
|
|
85
|
+
if (match) files.add(match[2].trim());
|
|
86
|
+
} else files.add(rest.trim());
|
|
87
|
+
}
|
|
88
|
+
return files;
|
|
89
|
+
}
|
|
90
|
+
async function readNoteFile(srcDir, note) {
|
|
91
|
+
return readFile(path.join(srcDir, note), "utf8");
|
|
92
|
+
}
|
|
93
|
+
async function computeSplash(inputs) {
|
|
94
|
+
const depth = Math.max(0, Math.floor(inputs.depth));
|
|
95
|
+
const modifiedFiles = await getModifiedProjectFiles(inputs.baseDir);
|
|
96
|
+
const notePaths = await readDirectory(inputs.srcDir);
|
|
97
|
+
const zone = [];
|
|
98
|
+
const cache = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const note of notePaths) {
|
|
100
|
+
const html = await readNoteFile(inputs.srcDir, note);
|
|
101
|
+
cache.set(note, html);
|
|
102
|
+
if (extractFileReferences(html, inputs.plugins).some((ref) => modifiedFiles.has(ref))) zone.push(note);
|
|
103
|
+
}
|
|
104
|
+
zone.sort();
|
|
105
|
+
const layers = [];
|
|
106
|
+
let frontier = new Set(zone);
|
|
107
|
+
const visited = new Set(zone);
|
|
108
|
+
for (let current = 1; current <= depth; current += 1) {
|
|
109
|
+
const next = /* @__PURE__ */ new Set();
|
|
110
|
+
const links = [];
|
|
111
|
+
for (const source of [...frontier].sort()) {
|
|
112
|
+
const html = cache.get(source) ?? await readNoteFile(inputs.srcDir, source);
|
|
113
|
+
cache.set(source, html);
|
|
114
|
+
const targets = extractWikiLinks(html);
|
|
115
|
+
if (targets.length === 0) continue;
|
|
116
|
+
links.push({
|
|
117
|
+
source,
|
|
118
|
+
targets
|
|
119
|
+
});
|
|
120
|
+
for (const target of targets) if (!visited.has(target)) {
|
|
121
|
+
visited.add(target);
|
|
122
|
+
next.add(target);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (links.length === 0) break;
|
|
126
|
+
layers.push({
|
|
127
|
+
depth: current,
|
|
128
|
+
links
|
|
129
|
+
});
|
|
130
|
+
if (next.size === 0) break;
|
|
131
|
+
frontier = next;
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
zone,
|
|
135
|
+
layers,
|
|
136
|
+
modifiedFiles: [...modifiedFiles].sort()
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
async function readDirectory(directory) {
|
|
140
|
+
const { readdir } = await import("node:fs/promises");
|
|
141
|
+
const notes = [];
|
|
142
|
+
const walk = async (dir) => {
|
|
143
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
144
|
+
const file = path.join(dir, entry.name);
|
|
145
|
+
if (entry.isDirectory()) await walk(file);
|
|
146
|
+
else if (entry.isFile() && entry.name.endsWith(".html")) notes.push(path.relative(directory, file));
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
await walk(directory);
|
|
150
|
+
return notes.sort();
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region src/cli.ts
|
|
154
|
+
async function loadConfig(projectDir, configFile) {
|
|
155
|
+
const file = path.resolve(projectDir, configFile);
|
|
156
|
+
return createJiti(import.meta.url, { interopDefault: true }).import(file, { default: true });
|
|
157
|
+
}
|
|
158
|
+
function formatSplash(result) {
|
|
159
|
+
const lines = [];
|
|
160
|
+
lines.push("# Splash Zone");
|
|
161
|
+
if (result.zone.length === 0) lines.push("- (none)");
|
|
162
|
+
else for (const note of result.zone) lines.push(`- ${note}`);
|
|
163
|
+
for (const layer of result.layers) {
|
|
164
|
+
if (layer.links.length === 0) continue;
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`# Related - Depth ${layer.depth}`);
|
|
167
|
+
for (const link of layer.links) lines.push(`- ${link.source} references ${link.targets.join(", ")}`);
|
|
168
|
+
}
|
|
169
|
+
return `${lines.join("\n")}\n`;
|
|
170
|
+
}
|
|
171
|
+
async function runSplash(projectDir, options) {
|
|
172
|
+
const config = await loadConfig(projectDir, options.config);
|
|
173
|
+
const baseDir = path.resolve(projectDir, config.baseDir);
|
|
174
|
+
await assertGitRepository(baseDir);
|
|
175
|
+
const result = await computeSplash({
|
|
176
|
+
srcDir: path.join(projectDir, "src"),
|
|
177
|
+
baseDir,
|
|
178
|
+
plugins: config.plugins,
|
|
179
|
+
depth: options.depth
|
|
180
|
+
});
|
|
181
|
+
process.stdout.write(formatSplash(result));
|
|
182
|
+
}
|
|
183
|
+
const program = new Command().name("nawc").description("Start a NAWC notebook").option("-p, --port <port>", "port to listen on", Number).option("-H, --host <host>", "host/interface to bind to").option("-c, --config <file>", "configuration file", "nawc.config.ts").action(async (options) => {
|
|
184
|
+
const running = await createNawcServer({
|
|
185
|
+
projectDir: process.cwd(),
|
|
186
|
+
configFile: options.config,
|
|
187
|
+
port: options.port,
|
|
188
|
+
host: options.host
|
|
189
|
+
});
|
|
190
|
+
console.log(`NAWC is ready at ${running.url}`);
|
|
191
|
+
const close = async () => {
|
|
192
|
+
await running.close();
|
|
193
|
+
process.exit(0);
|
|
194
|
+
};
|
|
195
|
+
process.once("SIGINT", () => void close());
|
|
196
|
+
process.once("SIGTERM", () => void close());
|
|
197
|
+
});
|
|
198
|
+
program.command("splash").description("List notes whose referenced files are modified in the worktree").option("-d, --depth <depth>", "how many wiki-link hops to follow", (value) => {
|
|
199
|
+
const parsed = Number(value);
|
|
200
|
+
if (!Number.isInteger(parsed) || parsed < 0) throw new Error("Depth must be a non-negative integer");
|
|
201
|
+
return parsed;
|
|
202
|
+
}, 0).option("-c, --config <file>", "configuration file", "nawc.config.ts").action(async (options) => {
|
|
203
|
+
await runSplash(process.cwd(), options);
|
|
204
|
+
});
|
|
205
|
+
program.parseAsync().catch((error) => {
|
|
206
|
+
console.error(error instanceof Error ? error.message : error);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
|
209
|
+
//#endregion
|
|
210
|
+
export {};
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EditorLocation, EditorTarget, NawcAgentAttachment, NawcConfig, NawcConfig as NawcConfig$1, NawcEditor, NawcEditor as NawcEditor$1, NawcProvider, NawcProviderCapability, NawcProviderMode, NawcProviderModel, NawcProviderOption, NawcProviderOptionSelection, NawcProviderReasoningEffort, NawcProviderSession, NawcProviderSettings, NawcProviderSkill, NawcProviderSlashCommand, NawcSyntax, NawcTheme, NawcTheme as NawcTheme$1 } from "@nawc/config";
|
|
2
|
+
import { nawcDark, nawcLight } from "@nawc/theme-nawc";
|
|
3
|
+
import { Server } from "node:http";
|
|
4
|
+
import { ViteDevServer } from "vite";
|
|
5
|
+
//#region src/define-config.d.ts
|
|
6
|
+
declare function defineConfig<const T extends NawcConfig$1>(config: T): T & {
|
|
7
|
+
readonly editor: NawcEditor$1;
|
|
8
|
+
readonly theme: NawcTheme$1;
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/server.d.ts
|
|
12
|
+
type ServerOptions = {
|
|
13
|
+
readonly projectDir: string;
|
|
14
|
+
readonly configFile?: string;
|
|
15
|
+
readonly port?: number;
|
|
16
|
+
readonly host?: string;
|
|
17
|
+
};
|
|
18
|
+
type RunningServer = {
|
|
19
|
+
readonly server: Server;
|
|
20
|
+
readonly vite: ViteDevServer;
|
|
21
|
+
readonly url: string;
|
|
22
|
+
close(): Promise<void>;
|
|
23
|
+
};
|
|
24
|
+
declare function createNawcServer(options: ServerOptions): Promise<RunningServer>;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { type EditorLocation, type EditorTarget, type NawcAgentAttachment, type NawcConfig, type NawcEditor, type NawcProvider, type NawcProviderCapability, type NawcProviderMode, type NawcProviderModel, type NawcProviderOption, type NawcProviderOptionSelection, type NawcProviderReasoningEffort, type NawcProviderSession, type NawcProviderSettings, type NawcProviderSkill, type NawcProviderSlashCommand, type NawcSyntax, type NawcTheme, createNawcServer, defineConfig, nawcDark, nawcLight };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { t as createNawcServer } from "./server-B0NGps95.mjs";
|
|
2
|
+
import { configShape } from "@nawc/config";
|
|
3
|
+
import { vscode } from "@nawc/editor-vscode";
|
|
4
|
+
import { nawcDark, nawcLight, nawcLight as nawcLight$1 } from "@nawc/theme-nawc";
|
|
5
|
+
//#region src/define-config.ts
|
|
6
|
+
function defineConfig(config) {
|
|
7
|
+
configShape.parse(config);
|
|
8
|
+
return {
|
|
9
|
+
...config,
|
|
10
|
+
editor: config.editor ?? vscode(),
|
|
11
|
+
theme: config.theme ?? nawcLight$1()
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { createNawcServer, defineConfig, nawcDark, nawcLight };
|