@lepid-labs/weft 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/LICENSE +21 -0
- package/bin/weft.js +2 -0
- package/dist/api-middleware.js +52 -0
- package/dist/api-middleware.js.map +1 -0
- package/dist/api-middleware.test.js +86 -0
- package/dist/api-middleware.test.js.map +1 -0
- package/dist/commands/analyze.js +31 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/check.js +21 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/index-cmd.js +29 -0
- package/dist/commands/index-cmd.js.map +1 -0
- package/dist/commands/serve.js +121 -0
- package/dist/commands/serve.js.map +1 -0
- package/dist/core-resolution.test.js +20 -0
- package/dist/core-resolution.test.js.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/open-browser.js +30 -0
- package/dist/open-browser.js.map +1 -0
- package/dist/open-browser.test.js +16 -0
- package/dist/open-browser.test.js.map +1 -0
- package/dist/ui-server.js +91 -0
- package/dist/ui-server.js.map +1 -0
- package/dist/ui-server.test.js +105 -0
- package/dist/ui-server.test.js.map +1 -0
- package/dist/validation.js +80 -0
- package/dist/validation.js.map +1 -0
- package/dist/validation.test.js +141 -0
- package/dist/validation.test.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wil Asche
|
|
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/bin/weft.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data API for the Weft UI, served by the CLI's Vite server and closing
|
|
3
|
+
* over the single WeftService instance. The UI never constructs a service —
|
|
4
|
+
* it consumes these JSON endpoints (and the manifest file during SSR).
|
|
5
|
+
*/
|
|
6
|
+
export function weftApiPlugin(service) {
|
|
7
|
+
return {
|
|
8
|
+
name: "weft-api",
|
|
9
|
+
configureServer(server) {
|
|
10
|
+
server.middlewares.use("/api", (req, res, next) => {
|
|
11
|
+
handleApiRequest(service, req, res).catch((err) => next(err));
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Handle one /api request. The url is relative to the /api mount point. */
|
|
17
|
+
export async function handleApiRequest(service, req, res) {
|
|
18
|
+
const url = new URL(req.url ?? "/", "http://weft.local");
|
|
19
|
+
const path = url.pathname;
|
|
20
|
+
if (path === "/manifest") {
|
|
21
|
+
return json(res, 200, await service.getManifest());
|
|
22
|
+
}
|
|
23
|
+
if (path.startsWith("/doc/")) {
|
|
24
|
+
const nodeId = decodeURIComponent(path.slice("/doc/".length));
|
|
25
|
+
try {
|
|
26
|
+
return json(res, 200, { content: service.read(nodeId) });
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return json(res, 404, { error: `Document not found: ${nodeId}` });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (path === "/search") {
|
|
33
|
+
const query = url.searchParams.get("q");
|
|
34
|
+
if (!query)
|
|
35
|
+
return json(res, 400, { error: 'Missing query parameter "q"' });
|
|
36
|
+
return json(res, 200, await service.search(query));
|
|
37
|
+
}
|
|
38
|
+
if (path === "/traverse") {
|
|
39
|
+
const nodeId = url.searchParams.get("node");
|
|
40
|
+
if (!nodeId)
|
|
41
|
+
return json(res, 400, { error: 'Missing query parameter "node"' });
|
|
42
|
+
const direction = (url.searchParams.get("direction") ?? "both");
|
|
43
|
+
return json(res, 200, await service.traverse(nodeId, direction));
|
|
44
|
+
}
|
|
45
|
+
return json(res, 404, { error: `Unknown API route: ${path}` });
|
|
46
|
+
}
|
|
47
|
+
function json(res, status, body) {
|
|
48
|
+
res.statusCode = status;
|
|
49
|
+
res.setHeader("Content-Type", "application/json");
|
|
50
|
+
res.end(JSON.stringify(body));
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=api-middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-middleware.js","sourceRoot":"","sources":["../src/api-middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { WeftService } from "@lepid-labs/weft-core";
|
|
4
|
+
import { beforeAll, describe, expect, it } from "vitest";
|
|
5
|
+
import { handleApiRequest } from "./api-middleware.js";
|
|
6
|
+
const __dirname = resolve(fileURLToPath(import.meta.url), "..");
|
|
7
|
+
const FIXTURES_DIR = resolve(__dirname, "__fixtures__");
|
|
8
|
+
let service;
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
service = new WeftService({
|
|
11
|
+
rootDir: FIXTURES_DIR,
|
|
12
|
+
docsDir: "docs",
|
|
13
|
+
entryPoint: "docs/README.md",
|
|
14
|
+
ignore: [],
|
|
15
|
+
});
|
|
16
|
+
await service.rebuild();
|
|
17
|
+
});
|
|
18
|
+
/** Run one request through the handler and capture the JSON response. */
|
|
19
|
+
// biome-ignore lint/suspicious/noExplicitAny: test helper — each assertion narrows the shape it uses
|
|
20
|
+
async function request(url) {
|
|
21
|
+
let status = 0;
|
|
22
|
+
let payload = "";
|
|
23
|
+
const res = {
|
|
24
|
+
set statusCode(code) {
|
|
25
|
+
status = code;
|
|
26
|
+
},
|
|
27
|
+
setHeader() { },
|
|
28
|
+
end(chunk) {
|
|
29
|
+
payload = chunk;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
await handleApiRequest(service, { url }, res);
|
|
33
|
+
return { status, body: JSON.parse(payload) };
|
|
34
|
+
}
|
|
35
|
+
describe("handleApiRequest", () => {
|
|
36
|
+
it("serves the manifest", async () => {
|
|
37
|
+
const { status, body } = await request("/manifest");
|
|
38
|
+
expect(status).toBe(200);
|
|
39
|
+
expect(body.nodes.map((n) => n.id).sort()).toEqual([
|
|
40
|
+
"README.md",
|
|
41
|
+
"architecture.md",
|
|
42
|
+
]);
|
|
43
|
+
expect(body.edges.length).toBeGreaterThan(0);
|
|
44
|
+
});
|
|
45
|
+
it("serves document content", async () => {
|
|
46
|
+
const { status, body } = await request("/doc/architecture.md");
|
|
47
|
+
expect(status).toBe(200);
|
|
48
|
+
expect(body.content).toContain("# Architecture");
|
|
49
|
+
});
|
|
50
|
+
it("404s for a missing document", async () => {
|
|
51
|
+
const { status, body } = await request("/doc/nope.md");
|
|
52
|
+
expect(status).toBe(404);
|
|
53
|
+
expect(body.error).toContain("nope.md");
|
|
54
|
+
});
|
|
55
|
+
it("404s for a document path escaping the docs root", async () => {
|
|
56
|
+
const { status } = await request("/doc/..%2F..%2Fpackage.json");
|
|
57
|
+
expect(status).toBe(404);
|
|
58
|
+
});
|
|
59
|
+
it("serves search results", async () => {
|
|
60
|
+
const { status, body } = await request("/search?q=weaving");
|
|
61
|
+
expect(status).toBe(200);
|
|
62
|
+
expect(body.some((r) => r.id === "architecture.md")).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
it("400s on a missing search query", async () => {
|
|
65
|
+
const { status, body } = await request("/search");
|
|
66
|
+
expect(status).toBe(400);
|
|
67
|
+
expect(body.error).toContain('"q"');
|
|
68
|
+
});
|
|
69
|
+
it("serves traverse edges in both directions", async () => {
|
|
70
|
+
const outgoing = await request("/traverse?node=README.md&direction=outgoing");
|
|
71
|
+
expect(outgoing.status).toBe(200);
|
|
72
|
+
expect(outgoing.body.some((e) => e.to.node === "architecture.md")).toBe(true);
|
|
73
|
+
const incoming = await request("/traverse?node=architecture.md&direction=incoming");
|
|
74
|
+
expect(incoming.body.some((e) => e.from.node === "README.md")).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
it("400s on a missing traverse node", async () => {
|
|
77
|
+
const { status } = await request("/traverse");
|
|
78
|
+
expect(status).toBe(400);
|
|
79
|
+
});
|
|
80
|
+
it("404s on unknown routes", async () => {
|
|
81
|
+
const { status, body } = await request("/openapi/api.yaml");
|
|
82
|
+
expect(status).toBe(404);
|
|
83
|
+
expect(body.error).toContain("Unknown API route");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
//# sourceMappingURL=api-middleware.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-middleware.test.js","sourceRoot":"","sources":["../src/api-middleware.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { defaultRegistry } from "@lepid-labs/weft-core";
|
|
2
|
+
import { command } from "cleye";
|
|
3
|
+
import { formatReport, formatRules, runValidation } from "../validation.js";
|
|
4
|
+
export const analyzeCommand = command({
|
|
5
|
+
name: "analyze",
|
|
6
|
+
help: {
|
|
7
|
+
description: "Run every validation rule over the graph and report the results",
|
|
8
|
+
},
|
|
9
|
+
parameters: ["[root-dir]"],
|
|
10
|
+
flags: {
|
|
11
|
+
json: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
description: "Emit the result as JSON",
|
|
14
|
+
default: false,
|
|
15
|
+
},
|
|
16
|
+
listRules: {
|
|
17
|
+
type: Boolean,
|
|
18
|
+
description: "List the registered rules and their default severities, then exit",
|
|
19
|
+
default: false,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}, async (argv) => {
|
|
23
|
+
if (argv.flags.listRules) {
|
|
24
|
+
console.log(formatRules(defaultRegistry()));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const result = await runValidation(argv._.rootDir ?? process.cwd());
|
|
28
|
+
console.log(formatReport(result, { json: argv.flags.json }));
|
|
29
|
+
// Reporting only — `weft check` is the command that fails a build.
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/commands/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CACpC;IACC,IAAI,EAAE,SAAS;IACf,IAAI,EAAE;QACL,WAAW,EAAE,iEAAiE;KAC9E;IACD,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,KAAK,EAAE;QACN,IAAI,EAAE;YACL,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,yBAAyB;YACtC,OAAO,EAAE,KAAK;SACd;QACD,SAAS,EAAE;YACV,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,mEAAmE;YAChF,OAAO,EAAE,KAAK;SACd;KACD;CACD,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACd,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;QAC5C,OAAO;IACR,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,mEAAmE;AACpE,CAAC,CACD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { command } from "cleye";
|
|
2
|
+
import { exitCodeFor, formatReport, runValidation } from "../validation.js";
|
|
3
|
+
export const checkCommand = command({
|
|
4
|
+
name: "check",
|
|
5
|
+
help: {
|
|
6
|
+
description: "Validate the graph and exit non-zero if any rule reports an error",
|
|
7
|
+
},
|
|
8
|
+
parameters: ["[root-dir]"],
|
|
9
|
+
flags: {
|
|
10
|
+
json: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
description: "Emit the result as JSON",
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}, async (argv) => {
|
|
17
|
+
const result = await runValidation(argv._.rootDir ?? process.cwd());
|
|
18
|
+
console.log(formatReport(result, { json: argv.flags.json }));
|
|
19
|
+
process.exitCode = exitCodeFor(result);
|
|
20
|
+
});
|
|
21
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../src/commands/check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAE5E,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAClC;IACC,IAAI,EAAE,OAAO;IACb,IAAI,EAAE;QACL,WAAW,EAAE,mEAAmE;KAChF;IACD,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,KAAK,EAAE;QACN,IAAI,EAAE;YACL,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,yBAAyB;YACtC,OAAO,EAAE,KAAK;SACd;KACD;CACD,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACd,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC7D,OAAO,CAAC,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC,CACD,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { WeftService, loadConfig } from "@lepid-labs/weft-core";
|
|
2
|
+
import { command } from "cleye";
|
|
3
|
+
export const indexCommand = command({
|
|
4
|
+
name: "index",
|
|
5
|
+
help: {
|
|
6
|
+
description: "Rebuild the graph manifest from embedded links",
|
|
7
|
+
},
|
|
8
|
+
parameters: ["[root-dir]"],
|
|
9
|
+
flags: {
|
|
10
|
+
quiet: {
|
|
11
|
+
type: Boolean,
|
|
12
|
+
description: "Suppress output",
|
|
13
|
+
default: false,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
}, async (argv) => {
|
|
17
|
+
const rootDir = argv._.rootDir ?? process.cwd();
|
|
18
|
+
const config = await loadConfig(rootDir);
|
|
19
|
+
const service = new WeftService(config);
|
|
20
|
+
const manifest = await service.rebuild();
|
|
21
|
+
const outPaths = await service.writeManifest();
|
|
22
|
+
if (!argv.flags.quiet) {
|
|
23
|
+
console.log(`Indexed ${manifest.nodes.length} documents, ${manifest.edges.length} edges`);
|
|
24
|
+
for (const outPath of outPaths) {
|
|
25
|
+
console.log(`Manifest written to ${outPath}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=index-cmd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-cmd.js","sourceRoot":"","sources":["../../src/commands/index-cmd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAClC;IACC,IAAI,EAAE,OAAO;IACb,IAAI,EAAE;QACL,WAAW,EAAE,gDAAgD;KAC7D;IACD,UAAU,EAAE,CAAC,YAAY,CAAC;IAC1B,KAAK,EAAE;QACN,KAAK,EAAE;YACN,IAAI,EAAE,OAAO;YACb,WAAW,EAAE,iBAAiB;YAC9B,OAAO,EAAE,KAAK;SACd;KACD;CACD,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACd,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;IACzC,MAAM,OAAO,GAAG,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;IAExC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACzC,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,aAAa,EAAE,CAAC;IAE/C,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACvB,OAAO,CAAC,GAAG,CAAC,WAAW,QAAQ,CAAC,KAAK,CAAC,MAAM,eAAe,QAAQ,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;QAC1F,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;QAC/C,CAAC;IACF,CAAC;AACF,CAAC,CACD,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { WeftService, fetchRepo, loadConfig, resolveFetchedRepos } from "@lepid-labs/weft-core";
|
|
2
|
+
import { command } from "cleye";
|
|
3
|
+
import { openBrowser } from "../open-browser.js";
|
|
4
|
+
import { chooseUiMode, startBuiltServer, startDevServer } from "../ui-server.js";
|
|
5
|
+
export const serveCommand = command({
|
|
6
|
+
name: "serve",
|
|
7
|
+
help: {
|
|
8
|
+
description: "Start the Weft UI server",
|
|
9
|
+
},
|
|
10
|
+
parameters: ["[root-dir]"],
|
|
11
|
+
flags: {
|
|
12
|
+
port: {
|
|
13
|
+
type: Number,
|
|
14
|
+
description: "Port to serve on",
|
|
15
|
+
default: 7777,
|
|
16
|
+
},
|
|
17
|
+
open: {
|
|
18
|
+
type: Boolean,
|
|
19
|
+
description: "Open the browser once the server is up",
|
|
20
|
+
default: false,
|
|
21
|
+
},
|
|
22
|
+
dev: {
|
|
23
|
+
type: Boolean,
|
|
24
|
+
description: "Serve the UI from source through Vite with hot reload (needs a checkout of the weft repo)",
|
|
25
|
+
default: false,
|
|
26
|
+
},
|
|
27
|
+
repo: {
|
|
28
|
+
type: String,
|
|
29
|
+
description: "Serve a GitHub repo (org/repo) without a checkout, fetching into a cache",
|
|
30
|
+
},
|
|
31
|
+
gh: {
|
|
32
|
+
type: String,
|
|
33
|
+
description: "Alias of --repo",
|
|
34
|
+
},
|
|
35
|
+
ref: {
|
|
36
|
+
type: String,
|
|
37
|
+
description: "Branch, tag or commit sha to fetch (with --repo; default: remote HEAD)",
|
|
38
|
+
},
|
|
39
|
+
refresh: {
|
|
40
|
+
type: Boolean,
|
|
41
|
+
description: "Re-resolve fetched refs even when the cached resolution is fresh",
|
|
42
|
+
default: false,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}, async (argv) => {
|
|
46
|
+
const port = argv.flags.port;
|
|
47
|
+
const { createRequire } = await import("node:module");
|
|
48
|
+
const { dirname, resolve } = await import("node:path");
|
|
49
|
+
// Resolve @lepid-labs/weft-ui through node resolution rather than a path relative to
|
|
50
|
+
// this file: the relative depth differs between src/ and dist/, and
|
|
51
|
+
// `new URL(import.meta.url).pathname` keeps a leading slash before the
|
|
52
|
+
// drive letter on Windows (`/C:/...`), which resolve() then mangles.
|
|
53
|
+
const require = createRequire(import.meta.url);
|
|
54
|
+
const uiRoot = dirname(require.resolve("@lepid-labs/weft-ui/package.json"));
|
|
55
|
+
if (argv.flags.repo && argv.flags.gh && argv.flags.repo !== argv.flags.gh) {
|
|
56
|
+
console.error("serve: --repo and --gh name different repos; pass one of them");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const repo = argv.flags.repo ?? argv.flags.gh;
|
|
60
|
+
if (repo && argv._.rootDir) {
|
|
61
|
+
console.error("serve: pass either a root directory or --repo, not both");
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
// Decide how the UI is served before fetching anything, so a missing
|
|
66
|
+
// build fails fast rather than after a clone.
|
|
67
|
+
const mode = chooseUiMode(uiRoot, argv.flags.dev);
|
|
68
|
+
// With --repo the root is a fetched, cache-resident checkout; otherwise
|
|
69
|
+
// resolve the root dir before the dev-mode chdir, since it defaults to the cwd.
|
|
70
|
+
let rootDir;
|
|
71
|
+
if (repo) {
|
|
72
|
+
console.log(`Fetching ${repo}${argv.flags.ref ? `@${argv.flags.ref}` : ""}…`);
|
|
73
|
+
rootDir = await fetchRepo(repo, {
|
|
74
|
+
ref: argv.flags.ref,
|
|
75
|
+
refresh: argv.flags.refresh,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
rootDir = resolve(argv._.rootDir ?? process.cwd());
|
|
80
|
+
}
|
|
81
|
+
// The CLI owns the single WeftService. The UI never constructs one — it
|
|
82
|
+
// consumes /api JSON (client) and the manifest file (SSR).
|
|
83
|
+
let config = await loadConfig(rootDir);
|
|
84
|
+
if (repo) {
|
|
85
|
+
// Repos the fetched config references resolve the same way: a real
|
|
86
|
+
// local checkout wins, everything else is fetched at its HEAD.
|
|
87
|
+
config = await resolveFetchedRepos(config, { refresh: argv.flags.refresh });
|
|
88
|
+
}
|
|
89
|
+
const service = new WeftService(config);
|
|
90
|
+
await service.rebuild();
|
|
91
|
+
await service.writeManifest();
|
|
92
|
+
// SvelteKit's server loads read the manifest file from this path — SSR
|
|
93
|
+
// cannot reach the /api handler through SvelteKit's internal fetch.
|
|
94
|
+
process.env.WEFT_MANIFEST_PATH = service.manifestPath;
|
|
95
|
+
const server = mode === "built"
|
|
96
|
+
? await startBuiltServer(service, uiRoot, port)
|
|
97
|
+
: await startDevServer(service, uiRoot, port);
|
|
98
|
+
const url = `http://localhost:${port}`;
|
|
99
|
+
console.log(`Weft server running at ${url}${mode === "dev" ? " (vite dev)" : ""}`);
|
|
100
|
+
if (argv.flags.open)
|
|
101
|
+
openBrowser(url);
|
|
102
|
+
// Watch for doc changes
|
|
103
|
+
const unwatch = service.watch(async (manifest) => {
|
|
104
|
+
console.log(`Rebuilt: ${manifest.nodes.length} docs, ${manifest.edges.length} edges`);
|
|
105
|
+
await service.writeManifest();
|
|
106
|
+
});
|
|
107
|
+
// Graceful shutdown
|
|
108
|
+
const shutdown = () => {
|
|
109
|
+
unwatch();
|
|
110
|
+
server.close();
|
|
111
|
+
process.exit(0);
|
|
112
|
+
};
|
|
113
|
+
process.on("SIGINT", shutdown);
|
|
114
|
+
process.on("SIGTERM", shutdown);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
console.error("Failed to start server:", err);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
// The CLI runs under plain Node (no Vite), so @lepid-labs/weft-core must resolve to
|
|
7
|
+
// built JavaScript, not TypeScript source. See issue #10.
|
|
8
|
+
describe("@lepid-labs/weft-core resolution for plain Node", () => {
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
it("resolves to built output, not TypeScript source", () => {
|
|
11
|
+
const resolved = require.resolve("@lepid-labs/weft-core");
|
|
12
|
+
expect(resolved).toMatch(/dist[\\/]index\.js$/);
|
|
13
|
+
expect(existsSync(resolved)).toBe(true);
|
|
14
|
+
});
|
|
15
|
+
it("imports successfully under plain Node", () => {
|
|
16
|
+
const stdout = execFileSync(process.execPath, ["--input-type=module", "-e", "await import('@lepid-labs/weft-core'); console.log('ok');"], { cwd: fileURLToPath(new URL("..", import.meta.url)), encoding: "utf8" });
|
|
17
|
+
expect(stdout.trim()).toBe("ok");
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
//# sourceMappingURL=core-resolution.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core-resolution.test.js","sourceRoot":"","sources":["../src/core-resolution.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,oFAAoF;AACpF,0DAA0D;AAC1D,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAChE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAE/C,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC1D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;QAC1D,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC;QAChD,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,YAAY,CAC1B,OAAO,CAAC,QAAQ,EAChB,CAAC,qBAAqB,EAAE,IAAI,EAAE,2DAA2D,CAAC,EAC1F,EAAE,GAAG,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,CACxE,CAAC;QACF,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { cli } from "cleye";
|
|
4
|
+
import { analyzeCommand } from "./commands/analyze.js";
|
|
5
|
+
import { checkCommand } from "./commands/check.js";
|
|
6
|
+
import { indexCommand } from "./commands/index-cmd.js";
|
|
7
|
+
import { serveCommand } from "./commands/serve.js";
|
|
8
|
+
const { version } = createRequire(import.meta.url)("../package.json");
|
|
9
|
+
const argv = cli({
|
|
10
|
+
name: "weft",
|
|
11
|
+
version,
|
|
12
|
+
commands: [analyzeCommand, checkCommand, indexCommand, serveCommand],
|
|
13
|
+
});
|
|
14
|
+
// Show help if no command given
|
|
15
|
+
if (!argv.command) {
|
|
16
|
+
argv.showHelp();
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,EAAE,OAAO,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAwB,CAAC;AAE7F,MAAM,IAAI,GAAG,GAAG,CAAC;IAChB,IAAI,EAAE,MAAM;IACZ,OAAO;IACP,QAAQ,EAAE,CAAC,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,CAAC;CACpE,CAAC,CAAC;AAEH,gCAAgC;AAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;IACnB,IAAI,CAAC,QAAQ,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* The platform's own "open this in the default browser" command. Each OS
|
|
4
|
+
* ships one, so no dependency is needed to find a browser.
|
|
5
|
+
*/
|
|
6
|
+
export function openCommand(url, platform = process.platform) {
|
|
7
|
+
switch (platform) {
|
|
8
|
+
case "darwin":
|
|
9
|
+
return { command: "open", args: [url] };
|
|
10
|
+
case "win32":
|
|
11
|
+
// `start` is a cmd builtin; the empty string is the window title it
|
|
12
|
+
// would otherwise take the (quoted) url for.
|
|
13
|
+
return { command: "cmd", args: ["/c", "start", "", url] };
|
|
14
|
+
default:
|
|
15
|
+
return { command: "xdg-open", args: [url] };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open a url in the default browser without waiting on it. Failing to open is
|
|
20
|
+
* reported, never fatal: the server is up and the url is already printed.
|
|
21
|
+
*/
|
|
22
|
+
export function openBrowser(url, platform) {
|
|
23
|
+
const { command, args } = openCommand(url, platform);
|
|
24
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true });
|
|
25
|
+
child.on("error", (err) => {
|
|
26
|
+
console.error(`Could not open a browser (${command}: ${err.message}) — open ${url} yourself.`);
|
|
27
|
+
});
|
|
28
|
+
child.unref();
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=open-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../src/open-browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAO3C;;;GAGG;AACH,MAAM,UAAU,WAAW,CAC1B,GAAW,EACX,WAA4B,OAAO,CAAC,QAAQ;IAE5C,QAAQ,QAAQ,EAAE,CAAC;QAClB,KAAK,QAAQ;YACZ,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,KAAK,OAAO;YACX,oEAAoE;YACpE,6CAA6C;YAC7C,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;QAC3D;YACC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IAC9C,CAAC;AACF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,QAA0B;IAClE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3F,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;QACzB,OAAO,CAAC,KAAK,CAAC,6BAA6B,OAAO,KAAK,GAAG,CAAC,OAAO,YAAY,GAAG,YAAY,CAAC,CAAC;IAChG,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { openCommand } from "./open-browser.js";
|
|
3
|
+
const URL = "http://localhost:7777";
|
|
4
|
+
describe("openCommand", () => {
|
|
5
|
+
it("uses `open` on macOS", () => {
|
|
6
|
+
expect(openCommand(URL, "darwin")).toEqual({ command: "open", args: [URL] });
|
|
7
|
+
});
|
|
8
|
+
it("uses cmd's `start` on Windows, with an explicit empty title", () => {
|
|
9
|
+
expect(openCommand(URL, "win32")).toEqual({ command: "cmd", args: ["/c", "start", "", URL] });
|
|
10
|
+
});
|
|
11
|
+
it("uses xdg-open everywhere else", () => {
|
|
12
|
+
expect(openCommand(URL, "linux")).toEqual({ command: "xdg-open", args: [URL] });
|
|
13
|
+
expect(openCommand(URL, "freebsd")).toEqual({ command: "xdg-open", args: [URL] });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=open-browser.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.test.js","sourceRoot":"","sources":["../src/open-browser.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,GAAG,GAAG,uBAAuB,CAAC;AAEpC,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC5B,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC9E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6DAA6D,EAAE,GAAG,EAAE;QACtE,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/F,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChF,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { handleApiRequest, weftApiPlugin } from "./api-middleware.js";
|
|
6
|
+
/** The adapter-node entry point, relative to the `@lepid-labs/weft-ui` package root. */
|
|
7
|
+
export const BUILT_HANDLER = join("build", "handler.js");
|
|
8
|
+
/**
|
|
9
|
+
* Pick how to serve the UI. The build wins when it exists — it is what a
|
|
10
|
+
* published package ships and what `npx` runs — and the source tree is the
|
|
11
|
+
* fallback for a checkout of this repo that has not built it. `forceDev`
|
|
12
|
+
* (`--dev`) is for working on the UI itself: hot reload, at Vite's startup cost.
|
|
13
|
+
*/
|
|
14
|
+
export function chooseUiMode(uiRoot, forceDev) {
|
|
15
|
+
const hasBuild = existsSync(join(uiRoot, BUILT_HANDLER));
|
|
16
|
+
const hasSource = existsSync(join(uiRoot, "svelte.config.js"));
|
|
17
|
+
if (forceDev) {
|
|
18
|
+
if (!hasSource) {
|
|
19
|
+
throw new Error(`serve: --dev needs the @lepid-labs/weft-ui source tree, and ${uiRoot} has none`);
|
|
20
|
+
}
|
|
21
|
+
return "dev";
|
|
22
|
+
}
|
|
23
|
+
if (hasBuild)
|
|
24
|
+
return "built";
|
|
25
|
+
if (hasSource)
|
|
26
|
+
return "dev";
|
|
27
|
+
throw new Error(`serve: no UI found in ${uiRoot} — expected ${BUILT_HANDLER} (run \`pnpm --filter @lepid-labs/weft-ui build\`)`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Route one request: `/api…` to the data API with the mount point stripped —
|
|
31
|
+
* exactly as Vite's `use("/api", …)` strips it, so both modes share one
|
|
32
|
+
* handler — and everything else to the UI.
|
|
33
|
+
*/
|
|
34
|
+
export function routeRequest(service, ui) {
|
|
35
|
+
return (req, res) => {
|
|
36
|
+
const url = req.url ?? "/";
|
|
37
|
+
if (url === "/api" || url.startsWith("/api/") || url.startsWith("/api?")) {
|
|
38
|
+
const rest = url.slice("/api".length);
|
|
39
|
+
req.url = rest === "" || rest.startsWith("?") ? `/${rest}` : rest;
|
|
40
|
+
handleApiRequest(service, req, res).catch((err) => {
|
|
41
|
+
console.error("API error:", err);
|
|
42
|
+
if (!res.headersSent) {
|
|
43
|
+
res.statusCode = 500;
|
|
44
|
+
res.setHeader("Content-Type", "application/json");
|
|
45
|
+
}
|
|
46
|
+
res.end(JSON.stringify({ error: "Internal server error" }));
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
ui(req, res, () => {
|
|
51
|
+
res.statusCode = 404;
|
|
52
|
+
res.end("Not found");
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/** Serve the adapter-node build over plain `node:http`, the API in front of it. */
|
|
57
|
+
export async function startBuiltServer(service, uiRoot, port) {
|
|
58
|
+
const entry = pathToFileURL(join(uiRoot, BUILT_HANDLER)).href;
|
|
59
|
+
const { handler } = (await import(entry));
|
|
60
|
+
const server = createServer(routeRequest(service, handler));
|
|
61
|
+
await new Promise((resolve, reject) => {
|
|
62
|
+
server.once("error", reject);
|
|
63
|
+
server.listen(port, () => {
|
|
64
|
+
server.off("error", reject);
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
return { close: () => void server.close() };
|
|
69
|
+
}
|
|
70
|
+
/** Serve the UI source through Vite's dev server, with the API as a plugin. */
|
|
71
|
+
export async function startDevServer(service, uiRoot, port) {
|
|
72
|
+
let createViteServer;
|
|
73
|
+
try {
|
|
74
|
+
({ createServer: createViteServer } = await import("vite"));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
throw new Error("serve: --dev needs vite, which the published package does not install — run from a checkout of the weft repo");
|
|
78
|
+
}
|
|
79
|
+
// SvelteKit's Vite plugin overrides Vite's `root` option with process.cwd()
|
|
80
|
+
// and looks up svelte.config.js and src/app.html there, so passing `root`
|
|
81
|
+
// alone is not enough — the process has to run from the UI package.
|
|
82
|
+
process.chdir(uiRoot);
|
|
83
|
+
const server = await createViteServer({
|
|
84
|
+
root: uiRoot,
|
|
85
|
+
server: { port },
|
|
86
|
+
plugins: [weftApiPlugin(service)],
|
|
87
|
+
});
|
|
88
|
+
await server.listen();
|
|
89
|
+
return { close: () => void server.close() };
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=ui-server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-server.js","sourceRoot":"","sources":["../src/ui-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { WeftService } from "@lepid-labs/weft-core";
|
|
7
|
+
import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
|
|
8
|
+
import { BUILT_HANDLER, chooseUiMode, routeRequest } from "./ui-server.js";
|
|
9
|
+
const __dirname = resolve(fileURLToPath(import.meta.url), "..");
|
|
10
|
+
const FIXTURES_DIR = resolve(__dirname, "__fixtures__");
|
|
11
|
+
describe("chooseUiMode", () => {
|
|
12
|
+
const dirs = [];
|
|
13
|
+
function uiRoot(opts) {
|
|
14
|
+
const dir = mkdtempSync(join(tmpdir(), "weft-ui-"));
|
|
15
|
+
dirs.push(dir);
|
|
16
|
+
if (opts.build) {
|
|
17
|
+
mkdirSync(join(dir, "build"));
|
|
18
|
+
writeFileSync(join(dir, BUILT_HANDLER), "");
|
|
19
|
+
}
|
|
20
|
+
if (opts.source)
|
|
21
|
+
writeFileSync(join(dir, "svelte.config.js"), "");
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
for (const dir of dirs.splice(0))
|
|
26
|
+
rmSync(dir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
it("prefers the build when both build and source exist", () => {
|
|
29
|
+
expect(chooseUiMode(uiRoot({ build: true, source: true }), false)).toBe("built");
|
|
30
|
+
});
|
|
31
|
+
it("serves the build when only the build exists (a published package)", () => {
|
|
32
|
+
expect(chooseUiMode(uiRoot({ build: true }), false)).toBe("built");
|
|
33
|
+
});
|
|
34
|
+
it("falls back to the source tree when nothing is built", () => {
|
|
35
|
+
expect(chooseUiMode(uiRoot({ source: true }), false)).toBe("dev");
|
|
36
|
+
});
|
|
37
|
+
it("--dev forces the source tree over an existing build", () => {
|
|
38
|
+
expect(chooseUiMode(uiRoot({ build: true, source: true }), true)).toBe("dev");
|
|
39
|
+
});
|
|
40
|
+
it("--dev without a source tree is an error, not a silent fallback", () => {
|
|
41
|
+
expect(() => chooseUiMode(uiRoot({ build: true }), true)).toThrow(/--dev needs/);
|
|
42
|
+
});
|
|
43
|
+
it("names the missing build when there is no UI at all", () => {
|
|
44
|
+
expect(() => chooseUiMode(uiRoot({}), false)).toThrow(/build\/handler\.js/);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe("routeRequest", () => {
|
|
48
|
+
let server;
|
|
49
|
+
let base;
|
|
50
|
+
const uiSeen = [];
|
|
51
|
+
beforeAll(async () => {
|
|
52
|
+
const service = new WeftService({
|
|
53
|
+
rootDir: FIXTURES_DIR,
|
|
54
|
+
docsDir: "docs",
|
|
55
|
+
entryPoint: "docs/README.md",
|
|
56
|
+
ignore: [],
|
|
57
|
+
});
|
|
58
|
+
await service.rebuild();
|
|
59
|
+
// A stand-in for adapter-node's handler: records the url it was given and
|
|
60
|
+
// echoes it, so the tests can see exactly what reached the UI.
|
|
61
|
+
const ui = (req, res) => {
|
|
62
|
+
uiSeen.push(req.url ?? "");
|
|
63
|
+
res.setHeader("Content-Type", "text/plain");
|
|
64
|
+
res.end(`ui:${req.url}`);
|
|
65
|
+
};
|
|
66
|
+
server = createServer(routeRequest(service, ui));
|
|
67
|
+
await new Promise((done) => server.listen(0, done));
|
|
68
|
+
const address = server.address();
|
|
69
|
+
if (!address || typeof address === "string")
|
|
70
|
+
throw new Error("no port");
|
|
71
|
+
base = `http://127.0.0.1:${address.port}`;
|
|
72
|
+
});
|
|
73
|
+
afterAll(() => server.close());
|
|
74
|
+
afterEach(() => uiSeen.splice(0));
|
|
75
|
+
it("serves the API with the /api mount point stripped", async () => {
|
|
76
|
+
const res = await fetch(`${base}/api/doc/architecture.md`);
|
|
77
|
+
expect(res.status).toBe(200);
|
|
78
|
+
expect(res.headers.get("content-type")).toBe("application/json");
|
|
79
|
+
const body = (await res.json());
|
|
80
|
+
expect(body.content).toContain("# Architecture");
|
|
81
|
+
expect(uiSeen).toEqual([]);
|
|
82
|
+
});
|
|
83
|
+
it("passes the query string through to the API", async () => {
|
|
84
|
+
const res = await fetch(`${base}/api/search?q=architecture`);
|
|
85
|
+
expect(res.status).toBe(200);
|
|
86
|
+
expect(await res.json()).not.toEqual([]);
|
|
87
|
+
});
|
|
88
|
+
it("treats a bare /api as the API root, as Vite's mount does", async () => {
|
|
89
|
+
const res = await fetch(`${base}/api`);
|
|
90
|
+
expect(res.status).toBe(404);
|
|
91
|
+
expect((await res.json()).error).toMatch(/Unknown API route: \//);
|
|
92
|
+
expect(uiSeen).toEqual([]);
|
|
93
|
+
});
|
|
94
|
+
it("hands every other path to the UI untouched", async () => {
|
|
95
|
+
const res = await fetch(`${base}/docs/architecture?x=1`);
|
|
96
|
+
expect(res.status).toBe(200);
|
|
97
|
+
expect(await res.text()).toBe("ui:/docs/architecture?x=1");
|
|
98
|
+
expect(uiSeen).toEqual(["/docs/architecture?x=1"]);
|
|
99
|
+
});
|
|
100
|
+
it("does not mistake a path that merely starts with 'api' for the API", async () => {
|
|
101
|
+
const res = await fetch(`${base}/apiary`);
|
|
102
|
+
expect(await res.text()).toBe("ui:/apiary");
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=ui-server.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui-server.test.js","sourceRoot":"","sources":["../src/ui-server.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { WeftService, defaultRegistry, loadConfig, } from "@lepid-labs/weft-core";
|
|
2
|
+
/** Severity order for reporting — worst first. */
|
|
3
|
+
const SEVERITY_ORDER = ["error", "warn", "info"];
|
|
4
|
+
/** Build the graph for a project and run every registered check over it. */
|
|
5
|
+
export async function runValidation(rootDir, registry = defaultRegistry()) {
|
|
6
|
+
const config = await loadConfig(rootDir);
|
|
7
|
+
return new WeftService(config).validate(registry);
|
|
8
|
+
}
|
|
9
|
+
/** Render what a diagnostic concerns as a single location string. */
|
|
10
|
+
export function formatTarget(target) {
|
|
11
|
+
switch (target.kind) {
|
|
12
|
+
case "node":
|
|
13
|
+
return `${target.node}${target.anchor ?? ""}`;
|
|
14
|
+
case "edge": {
|
|
15
|
+
const { from, to } = target.edge;
|
|
16
|
+
return `${from.node}${from.anchor ?? ""} -> ${to.node}${to.anchor ?? ""}`;
|
|
17
|
+
}
|
|
18
|
+
default:
|
|
19
|
+
return "(graph)";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Pluralize a count for the summary line. */
|
|
23
|
+
function plural(count, noun) {
|
|
24
|
+
return `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
25
|
+
}
|
|
26
|
+
function formatDiagnostic(diagnostic, ruleWidth) {
|
|
27
|
+
const head = ` ${diagnostic.severity.padEnd(5)} ${diagnostic.rule.padEnd(ruleWidth)} ${formatTarget(diagnostic.target)}`;
|
|
28
|
+
const message = ` ${diagnostic.message}`;
|
|
29
|
+
const hint = diagnostic.hint ? `\n hint: ${diagnostic.hint}` : "";
|
|
30
|
+
return `${head}\n${message}${hint}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Render a validation result for the terminal.
|
|
34
|
+
*
|
|
35
|
+
* Output is deliberately ASCII-only: the Windows console is cp1252, where a
|
|
36
|
+
* decorative glyph raises an encoding error on write and crashes the command on
|
|
37
|
+
* its own output.
|
|
38
|
+
*/
|
|
39
|
+
export function formatReport(result, options = {}) {
|
|
40
|
+
if (options.json)
|
|
41
|
+
return JSON.stringify(result, null, 2);
|
|
42
|
+
const lines = [];
|
|
43
|
+
// Size the rule column to what is actually being reported, so one long rule
|
|
44
|
+
// id does not knock every other row out of alignment.
|
|
45
|
+
const ruleWidth = Math.max(0, ...result.diagnostics.map((d) => d.rule.length));
|
|
46
|
+
for (const severity of SEVERITY_ORDER) {
|
|
47
|
+
for (const diagnostic of result.diagnostics.filter((d) => d.severity === severity)) {
|
|
48
|
+
lines.push(formatDiagnostic(diagnostic, ruleWidth));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (lines.length)
|
|
52
|
+
lines.push("");
|
|
53
|
+
const { error, warn, info } = result.counts;
|
|
54
|
+
const total = error + warn + info;
|
|
55
|
+
lines.push(total === 0
|
|
56
|
+
? "No problems found."
|
|
57
|
+
: `${plural(total, "problem")} (${plural(error, "error")}, ${plural(warn, "warning")}, ${plural(info, "note")})`);
|
|
58
|
+
const ran = plural(result.rulesRun.length, "rule");
|
|
59
|
+
const off = result.rulesSkipped.length ? `, ${result.rulesSkipped.length} off` : "";
|
|
60
|
+
lines.push(`Ran ${ran}${off}.`);
|
|
61
|
+
if (result.unknownRules.length) {
|
|
62
|
+
lines.push(`! config sets a severity for unknown rules: ${result.unknownRules.join(", ")}`);
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
/** List every registered rule and its default severity, for discovering config ids. */
|
|
67
|
+
export function formatRules(registry) {
|
|
68
|
+
const rules = registry.rules;
|
|
69
|
+
if (!rules.length)
|
|
70
|
+
return "No rules are registered.";
|
|
71
|
+
const width = Math.max(...rules.map((rule) => rule.id.length));
|
|
72
|
+
return rules
|
|
73
|
+
.map((rule) => `${rule.id.padEnd(width)} ${rule.defaultSeverity.padEnd(5)} ${rule.description}`)
|
|
74
|
+
.join("\n");
|
|
75
|
+
}
|
|
76
|
+
/** `weft check` fails the build on error severity only. Warnings and notes report but pass. */
|
|
77
|
+
export function exitCodeFor(result) {
|
|
78
|
+
return result.counts.error > 0 ? 1 : 0;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { ValidatorRegistry } from "@lepid-labs/weft-core";
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { exitCodeFor, formatReport, formatRules, formatTarget, runValidation, } from "./validation.js";
|
|
6
|
+
const __dirname = resolve(fileURLToPath(import.meta.url), "..");
|
|
7
|
+
const FIXTURES_DIR = resolve(__dirname, "__fixtures__");
|
|
8
|
+
function result(diagnostics = [], overrides = {}) {
|
|
9
|
+
return {
|
|
10
|
+
diagnostics,
|
|
11
|
+
counts: {
|
|
12
|
+
error: diagnostics.filter((d) => d.severity === "error").length,
|
|
13
|
+
warn: diagnostics.filter((d) => d.severity === "warn").length,
|
|
14
|
+
info: diagnostics.filter((d) => d.severity === "info").length,
|
|
15
|
+
},
|
|
16
|
+
rulesRun: ["validator-error"],
|
|
17
|
+
rulesSkipped: [],
|
|
18
|
+
unknownRules: [],
|
|
19
|
+
...overrides,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const ERROR_DIAGNOSTIC = {
|
|
23
|
+
rule: "edge-target-missing",
|
|
24
|
+
severity: "error",
|
|
25
|
+
message: "Target document does not exist",
|
|
26
|
+
target: {
|
|
27
|
+
kind: "edge",
|
|
28
|
+
edge: { from: { node: "README.md" }, to: { node: "gone.md" }, type: "references" },
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const WARN_DIAGNOSTIC = {
|
|
32
|
+
rule: "doc-orphaned",
|
|
33
|
+
severity: "warn",
|
|
34
|
+
message: "Nothing links to this document",
|
|
35
|
+
target: { kind: "node", node: "stray.md" },
|
|
36
|
+
hint: "link it from README.md",
|
|
37
|
+
};
|
|
38
|
+
describe("formatTarget", () => {
|
|
39
|
+
it("renders a node, with its anchor when present", () => {
|
|
40
|
+
expect(formatTarget({ kind: "node", node: "api.md" })).toBe("api.md");
|
|
41
|
+
expect(formatTarget({ kind: "node", node: "api.md", anchor: "#users" })).toBe("api.md#users");
|
|
42
|
+
});
|
|
43
|
+
it("renders an edge as source to target, keeping both anchors", () => {
|
|
44
|
+
expect(formatTarget({
|
|
45
|
+
kind: "edge",
|
|
46
|
+
edge: {
|
|
47
|
+
from: { node: "a.md", anchor: "#sync" },
|
|
48
|
+
to: { node: "b.yaml", anchor: "#listUsers" },
|
|
49
|
+
type: "implements",
|
|
50
|
+
},
|
|
51
|
+
})).toBe("a.md#sync -> b.yaml#listUsers");
|
|
52
|
+
});
|
|
53
|
+
it("renders a graph-wide target", () => {
|
|
54
|
+
expect(formatTarget({ kind: "graph" })).toBe("(graph)");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("formatReport", () => {
|
|
58
|
+
it("reports a clean run", () => {
|
|
59
|
+
const text = formatReport(result());
|
|
60
|
+
expect(text).toContain("No problems found.");
|
|
61
|
+
expect(text).toContain("Ran 1 rule.");
|
|
62
|
+
});
|
|
63
|
+
it("lists each diagnostic with its severity, rule id, location and message", () => {
|
|
64
|
+
const text = formatReport(result([ERROR_DIAGNOSTIC]));
|
|
65
|
+
expect(text).toContain("error");
|
|
66
|
+
expect(text).toContain("edge-target-missing");
|
|
67
|
+
expect(text).toContain("README.md -> gone.md");
|
|
68
|
+
expect(text).toContain("Target document does not exist");
|
|
69
|
+
});
|
|
70
|
+
it("includes a hint when the diagnostic carries one", () => {
|
|
71
|
+
expect(formatReport(result([WARN_DIAGNOSTIC]))).toContain("hint: link it from README.md");
|
|
72
|
+
});
|
|
73
|
+
it("orders errors before warnings", () => {
|
|
74
|
+
const text = formatReport(result([WARN_DIAGNOSTIC, ERROR_DIAGNOSTIC]));
|
|
75
|
+
expect(text.indexOf("edge-target-missing")).toBeLessThan(text.indexOf("doc-orphaned"));
|
|
76
|
+
});
|
|
77
|
+
it("summarizes the counts, pluralized", () => {
|
|
78
|
+
expect(formatReport(result([ERROR_DIAGNOSTIC, WARN_DIAGNOSTIC]))).toContain("2 problems (1 error, 1 warning, 0 notes)");
|
|
79
|
+
});
|
|
80
|
+
it("notes how many rules were turned off", () => {
|
|
81
|
+
const text = formatReport(result([], { rulesRun: ["a"], rulesSkipped: ["b", "c"] }));
|
|
82
|
+
expect(text).toContain("Ran 1 rule, 2 off.");
|
|
83
|
+
});
|
|
84
|
+
it("flags config rule ids that no validator declares", () => {
|
|
85
|
+
const text = formatReport(result([], { unknownRules: ["typo-rule"] }));
|
|
86
|
+
expect(text).toContain("unknown rules: typo-rule");
|
|
87
|
+
});
|
|
88
|
+
it("stays ASCII so the cp1252 Windows console can print it", () => {
|
|
89
|
+
const text = formatReport(result([ERROR_DIAGNOSTIC, WARN_DIAGNOSTIC], { unknownRules: ["x"] }));
|
|
90
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting the whole ASCII range
|
|
91
|
+
expect(text).toMatch(/^[\x00-\x7F]*$/);
|
|
92
|
+
});
|
|
93
|
+
it("emits the full result as JSON when asked", () => {
|
|
94
|
+
const validation = result([ERROR_DIAGNOSTIC]);
|
|
95
|
+
const parsed = JSON.parse(formatReport(validation, { json: true }));
|
|
96
|
+
expect(parsed).toEqual(validation);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
describe("formatRules", () => {
|
|
100
|
+
it("lists each rule with its default severity and description", () => {
|
|
101
|
+
const registry = new ValidatorRegistry().register({
|
|
102
|
+
rules: [{ id: "some-check", description: "Checks something", defaultSeverity: "warn" }],
|
|
103
|
+
run: () => [],
|
|
104
|
+
});
|
|
105
|
+
const text = formatRules(registry);
|
|
106
|
+
expect(text).toContain("some-check");
|
|
107
|
+
expect(text).toContain("warn");
|
|
108
|
+
expect(text).toContain("Checks something");
|
|
109
|
+
expect(text).toContain("validator-error");
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe("exitCodeFor", () => {
|
|
113
|
+
it("fails on an error diagnostic", () => {
|
|
114
|
+
expect(exitCodeFor(result([ERROR_DIAGNOSTIC]))).toBe(1);
|
|
115
|
+
});
|
|
116
|
+
it("passes on warnings and notes alone", () => {
|
|
117
|
+
expect(exitCodeFor(result([WARN_DIAGNOSTIC]))).toBe(0);
|
|
118
|
+
});
|
|
119
|
+
it("passes on a clean run", () => {
|
|
120
|
+
expect(exitCodeFor(result())).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
describe("runValidation", () => {
|
|
124
|
+
it("builds the project's graph and runs the given checks over it", async () => {
|
|
125
|
+
const registry = new ValidatorRegistry().register({
|
|
126
|
+
rules: [{ id: "node-count", description: "Counts nodes", defaultSeverity: "info" }],
|
|
127
|
+
run: (context) => [
|
|
128
|
+
{ rule: "node-count", message: `${context.nodes.size} nodes`, target: { kind: "graph" } },
|
|
129
|
+
],
|
|
130
|
+
});
|
|
131
|
+
const validation = await runValidation(FIXTURES_DIR, registry);
|
|
132
|
+
expect(validation.diagnostics.map((d) => d.message)).toEqual(["2 nodes"]);
|
|
133
|
+
expect(validation.counts).toEqual({ error: 0, warn: 0, info: 1 });
|
|
134
|
+
});
|
|
135
|
+
it("finds nothing with the built-in checks, which are not written yet", async () => {
|
|
136
|
+
const validation = await runValidation(FIXTURES_DIR);
|
|
137
|
+
expect(validation.diagnostics).toEqual([]);
|
|
138
|
+
expect(exitCodeFor(validation)).toBe(0);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=validation.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.test.js","sourceRoot":"","sources":["../src/validation.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lepid-labs/weft",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Weft CLI: serve, index and check a documentation graph, from a checkout or straight from GitHub",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Lepid-Labs/weft.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Lepid-Labs/weft#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"bin": {
|
|
18
|
+
"weft": "bin/weft.js"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"cleye": "^1.3.0",
|
|
22
|
+
"@lepid-labs/weft-core": "0.1.0",
|
|
23
|
+
"@lepid-labs/weft-ui": "0.1.0"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/node": "^22.0.0",
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vite": "^6.4.3",
|
|
29
|
+
"vitest": "^3.2.6"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"vite": "^6.4.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"vite": {
|
|
36
|
+
"optional": true
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=24"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsc",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"test": "pnpm --filter @lepid-labs/weft-core build && vitest run",
|
|
49
|
+
"test:watch": "vitest"
|
|
50
|
+
}
|
|
51
|
+
}
|