@sourcepress/cli 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/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-test.log +46 -0
- package/bin/sourcepress.js +2 -0
- package/dist/__tests__/config.test.d.ts +2 -0
- package/dist/__tests__/config.test.d.ts.map +1 -0
- package/dist/__tests__/config.test.js +70 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/commands/build.d.ts +3 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +27 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +3 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +21 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/eval.d.ts +3 -0
- package/dist/commands/eval.d.ts.map +1 -0
- package/dist/commands/eval.js +40 -0
- package/dist/commands/eval.js.map +1 -0
- package/dist/commands/gaps.d.ts +3 -0
- package/dist/commands/gaps.d.ts.map +1 -0
- package/dist/commands/gaps.js +31 -0
- package/dist/commands/gaps.js.map +1 -0
- package/dist/commands/graph.d.ts +3 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +33 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +117 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/stale.d.ts +3 -0
- package/dist/commands/stale.d.ts.map +1 -0
- package/dist/commands/stale.js +35 -0
- package/dist/commands/stale.js.map +1 -0
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +26 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/sync.d.ts +3 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +22 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/engine-boot.d.ts +4 -0
- package/dist/engine-boot.d.ts.map +1 -0
- package/dist/engine-boot.js +15 -0
- package/dist/engine-boot.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/dist/index.js.map +1 -0
- package/package.json +28 -0
- package/src/__tests__/config.test.ts +80 -0
- package/src/commands/build.ts +29 -0
- package/src/commands/dev.ts +27 -0
- package/src/commands/eval.ts +47 -0
- package/src/commands/gaps.ts +38 -0
- package/src/commands/graph.ts +41 -0
- package/src/commands/init.ts +123 -0
- package/src/commands/stale.ts +40 -0
- package/src/commands/status.ts +37 -0
- package/src/commands/sync.ts +28 -0
- package/src/config.ts +47 -0
- package/src/engine-boot.ts +17 -0
- package/src/index.ts +146 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ContentFile } from "@sourcepress/core";
|
|
2
|
+
import type { EngineContext } from "@sourcepress/server";
|
|
3
|
+
|
|
4
|
+
export async function stale(engine: EngineContext): Promise<void> {
|
|
5
|
+
console.log("Checking for stale content...\n");
|
|
6
|
+
|
|
7
|
+
const collections = engine.listCollections();
|
|
8
|
+
let staleCount = 0;
|
|
9
|
+
|
|
10
|
+
for (const name of collections) {
|
|
11
|
+
let files: ContentFile[];
|
|
12
|
+
try {
|
|
13
|
+
files = await engine.listContent(name);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
console.error(` ${name}: error — ${err instanceof Error ? err.message : String(err)}`);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const file of files) {
|
|
20
|
+
const generatedAt = file.frontmatter?.generated_at as string | undefined;
|
|
21
|
+
const staleSources = file.frontmatter?.stale_sources as string[] | undefined;
|
|
22
|
+
|
|
23
|
+
if (staleSources && staleSources.length > 0) {
|
|
24
|
+
console.log(` [stale] ${file.path}`);
|
|
25
|
+
console.log(` Stale sources: ${staleSources.join(", ")}`);
|
|
26
|
+
if (generatedAt) {
|
|
27
|
+
console.log(` Generated at: ${generatedAt}`);
|
|
28
|
+
}
|
|
29
|
+
staleCount++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (staleCount === 0) {
|
|
35
|
+
console.log("All content is up to date.");
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`\n${staleCount} stale file${staleCount !== 1 ? "s" : ""} found.`);
|
|
38
|
+
console.log("Run `sourcepress eval --run` to regenerate stale content.");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { SourcePressConfig } from "@sourcepress/core";
|
|
2
|
+
import type { EngineContext } from "@sourcepress/server";
|
|
3
|
+
|
|
4
|
+
export async function status(engine: EngineContext, config: SourcePressConfig): Promise<void> {
|
|
5
|
+
console.log("SourcePress status\n");
|
|
6
|
+
|
|
7
|
+
console.log(` Repository: ${config.repository.owner}/${config.repository.repo}`);
|
|
8
|
+
console.log(` Branch: ${config.repository.branch}`);
|
|
9
|
+
console.log(` AI provider: ${config.ai.provider} (${config.ai.model})`);
|
|
10
|
+
console.log(` GitHub: ${process.env.GITHUB_TOKEN ? "configured" : "MISSING"}`);
|
|
11
|
+
console.log(` AI key: ${process.env.AI_API_KEY ? "configured" : "not set"}`);
|
|
12
|
+
|
|
13
|
+
const collections = engine.listCollections();
|
|
14
|
+
console.log(
|
|
15
|
+
` Collections: ${collections.length}${collections.length > 0 ? ` (${collections.join(", ")})` : ""}`,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
// Knowledge graph summary
|
|
19
|
+
try {
|
|
20
|
+
const knowledgeFiles = await engine.knowledgeStore.list();
|
|
21
|
+
console.log(
|
|
22
|
+
` Knowledge: ${knowledgeFiles.length} file${knowledgeFiles.length !== 1 ? "s" : ""}`,
|
|
23
|
+
);
|
|
24
|
+
} catch {
|
|
25
|
+
console.log(" Knowledge: unavailable");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Cache info
|
|
29
|
+
const cacheBackend = config.cache?.backend ?? "memory";
|
|
30
|
+
console.log(` Cache: ${cacheBackend}`);
|
|
31
|
+
|
|
32
|
+
// Jobs
|
|
33
|
+
const jobsBackend = config.jobs?.backend ?? "in-process";
|
|
34
|
+
console.log(` Jobs: ${jobsBackend}`);
|
|
35
|
+
|
|
36
|
+
console.log("");
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { EngineContext } from "@sourcepress/server";
|
|
2
|
+
|
|
3
|
+
export async function sync(engine: EngineContext): Promise<void> {
|
|
4
|
+
console.log("Syncing content from GitHub...\n");
|
|
5
|
+
|
|
6
|
+
const collections = engine.listCollections();
|
|
7
|
+
|
|
8
|
+
if (collections.length === 0) {
|
|
9
|
+
console.log(" No collections configured.");
|
|
10
|
+
console.log("\nSync complete.");
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let total = 0;
|
|
15
|
+
for (const name of collections) {
|
|
16
|
+
try {
|
|
17
|
+
const files = await engine.listContent(name);
|
|
18
|
+
console.log(` ${name}: ${files.length} file${files.length !== 1 ? "s" : ""}`);
|
|
19
|
+
total += files.length;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(` ${name}: error — ${err instanceof Error ? err.message : String(err)}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(
|
|
26
|
+
`\nSync complete. ${total} file${total !== 1 ? "s" : ""} across ${collections.length} collection${collections.length !== 1 ? "s" : ""}.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import { validateConfig } from "@sourcepress/core";
|
|
5
|
+
import type { SourcePressConfig } from "@sourcepress/core";
|
|
6
|
+
|
|
7
|
+
async function importTsFile(fullPath: string): Promise<unknown> {
|
|
8
|
+
const { tsImport } = await import("tsx/esm/api");
|
|
9
|
+
return tsImport(pathToFileURL(fullPath).href, import.meta.url);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function loadConfig(cwd: string): Promise<SourcePressConfig | null> {
|
|
13
|
+
const candidates = ["sourcepress.config.ts", "sourcepress.config.js"];
|
|
14
|
+
for (const name of candidates) {
|
|
15
|
+
const fullPath = join(cwd, name);
|
|
16
|
+
if (existsSync(fullPath)) {
|
|
17
|
+
let mod: unknown;
|
|
18
|
+
try {
|
|
19
|
+
if (name.endsWith(".ts")) {
|
|
20
|
+
mod = await importTsFile(fullPath);
|
|
21
|
+
} else {
|
|
22
|
+
mod = await import(pathToFileURL(fullPath).href);
|
|
23
|
+
}
|
|
24
|
+
} catch (err) {
|
|
25
|
+
throw new Error(
|
|
26
|
+
`Failed to load ${name}: ${err instanceof Error ? err.message : String(err)}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
let raw =
|
|
30
|
+
mod != null && typeof mod === "object" && "default" in mod
|
|
31
|
+
? (mod as { default: unknown }).default
|
|
32
|
+
: mod;
|
|
33
|
+
// tsx wraps modules with an extra layer — unwrap if needed
|
|
34
|
+
if (raw != null && typeof raw === "object" && "default" in raw) {
|
|
35
|
+
raw = (raw as { default: unknown }).default;
|
|
36
|
+
}
|
|
37
|
+
const result = validateConfig(raw);
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid sourcepress config in ${name}:\n${result.errors.map((e) => ` - ${e}`).join("\n")}`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return result.data as SourcePressConfig;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SourcePressConfig } from "@sourcepress/core";
|
|
2
|
+
import { createEngine } from "@sourcepress/server";
|
|
3
|
+
import type { EngineContext } from "@sourcepress/server";
|
|
4
|
+
|
|
5
|
+
export async function bootEngine(config: SourcePressConfig): Promise<EngineContext> {
|
|
6
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
7
|
+
if (!githubToken) {
|
|
8
|
+
console.error("Error: GITHUB_TOKEN environment variable is required.");
|
|
9
|
+
console.error("Set it: export GITHUB_TOKEN=ghp_...");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
return createEngine({
|
|
13
|
+
config,
|
|
14
|
+
githubToken,
|
|
15
|
+
aiApiKey: process.env.AI_API_KEY,
|
|
16
|
+
});
|
|
17
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import type { SourcePressConfig } from "@sourcepress/core";
|
|
4
|
+
import { build } from "./commands/build.js";
|
|
5
|
+
import { dev } from "./commands/dev.js";
|
|
6
|
+
import { evalCmd } from "./commands/eval.js";
|
|
7
|
+
import { gaps } from "./commands/gaps.js";
|
|
8
|
+
import { graph } from "./commands/graph.js";
|
|
9
|
+
import { init } from "./commands/init.js";
|
|
10
|
+
import { stale } from "./commands/stale.js";
|
|
11
|
+
import { status } from "./commands/status.js";
|
|
12
|
+
import { sync } from "./commands/sync.js";
|
|
13
|
+
import { loadConfig } from "./config.js";
|
|
14
|
+
import { bootEngine } from "./engine-boot.js";
|
|
15
|
+
|
|
16
|
+
function loadEnvFile(dir: string): void {
|
|
17
|
+
const envPath = join(dir, ".env");
|
|
18
|
+
if (!existsSync(envPath)) return;
|
|
19
|
+
|
|
20
|
+
const content = readFileSync(envPath, "utf-8");
|
|
21
|
+
for (const line of content.split("\n")) {
|
|
22
|
+
const trimmed = line.trim();
|
|
23
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
24
|
+
const eqIndex = trimmed.indexOf("=");
|
|
25
|
+
if (eqIndex === -1) continue;
|
|
26
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
27
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
28
|
+
if (
|
|
29
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
30
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
31
|
+
) {
|
|
32
|
+
value = value.slice(1, -1);
|
|
33
|
+
}
|
|
34
|
+
if (!(key in process.env)) {
|
|
35
|
+
process.env[key] = value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const COMMANDS_NEEDING_CONFIG = [
|
|
41
|
+
"dev",
|
|
42
|
+
"build",
|
|
43
|
+
"sync",
|
|
44
|
+
"graph",
|
|
45
|
+
"stale",
|
|
46
|
+
"eval",
|
|
47
|
+
"status",
|
|
48
|
+
"gaps",
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
function printHelp(): void {
|
|
52
|
+
console.log("sourcepress — Open source Content Engine with knowledge graph\n");
|
|
53
|
+
console.log("Commands:");
|
|
54
|
+
console.log(" init Initialize a new SourcePress project");
|
|
55
|
+
console.log(" dev Start the Content Engine server");
|
|
56
|
+
console.log(" build Sync content + build site");
|
|
57
|
+
console.log(" sync Sync content from GitHub");
|
|
58
|
+
console.log(" graph Show/rebuild knowledge graph");
|
|
59
|
+
console.log(" stale List stale content");
|
|
60
|
+
console.log(" eval Run evals");
|
|
61
|
+
console.log(" status Health check");
|
|
62
|
+
console.log(" gaps List knowledge gaps");
|
|
63
|
+
console.log("\nOptions:");
|
|
64
|
+
console.log(" --help Show this help");
|
|
65
|
+
console.log(" --version Show version");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printVersion(): void {
|
|
69
|
+
console.log("0.1.0");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function main(): Promise<void> {
|
|
73
|
+
loadEnvFile(process.cwd());
|
|
74
|
+
const args = process.argv.slice(2);
|
|
75
|
+
const command = args[0];
|
|
76
|
+
|
|
77
|
+
if (command === "init") {
|
|
78
|
+
init(process.cwd());
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!command || command === "--help") {
|
|
83
|
+
printHelp();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (command === "--version") {
|
|
88
|
+
printVersion();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (COMMANDS_NEEDING_CONFIG.includes(command)) {
|
|
93
|
+
let config: SourcePressConfig | null;
|
|
94
|
+
try {
|
|
95
|
+
config = await loadConfig(process.cwd());
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!config) {
|
|
102
|
+
console.error("No sourcepress.config.ts found. Run `sourcepress init` first.");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const engine = await bootEngine(config);
|
|
107
|
+
|
|
108
|
+
// Command dispatch
|
|
109
|
+
switch (command) {
|
|
110
|
+
case "dev":
|
|
111
|
+
await dev(engine, args.slice(1));
|
|
112
|
+
break;
|
|
113
|
+
case "build":
|
|
114
|
+
await build(engine, process.cwd());
|
|
115
|
+
break;
|
|
116
|
+
case "sync":
|
|
117
|
+
await sync(engine);
|
|
118
|
+
break;
|
|
119
|
+
case "graph":
|
|
120
|
+
await graph(engine, args.slice(1));
|
|
121
|
+
break;
|
|
122
|
+
case "stale":
|
|
123
|
+
await stale(engine);
|
|
124
|
+
break;
|
|
125
|
+
case "eval":
|
|
126
|
+
await evalCmd(engine, args.slice(1));
|
|
127
|
+
break;
|
|
128
|
+
case "status":
|
|
129
|
+
await status(engine, config);
|
|
130
|
+
break;
|
|
131
|
+
case "gaps":
|
|
132
|
+
await gaps(engine);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.error(`Unknown command: ${command}`);
|
|
139
|
+
console.error("Run `sourcepress --help` for usage.");
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main().catch((err: unknown) => {
|
|
144
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
});
|