@usegraft/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/LICENSE +21 -0
- package/dist/add-UBAZMSP7.js +61 -0
- package/dist/approvals-Z3EOK5AM.js +68 -0
- package/dist/asset-FPEL463J.js +49 -0
- package/dist/branch-HRG46YIL.js +75 -0
- package/dist/chunk-BUPFXTUG.js +182 -0
- package/dist/chunk-GPA66AP5.js +22 -0
- package/dist/chunk-KZ3FDWUK.js +71 -0
- package/dist/chunk-LTFTDG6Z.js +172 -0
- package/dist/chunk-TUWL7WFD.js +256 -0
- package/dist/compilations-6P45MPAE.js +29 -0
- package/dist/compile-AAUC4G7H.js +11 -0
- package/dist/content-DY2YI3LT.js +44 -0
- package/dist/db-VGDWG4QA.js +48 -0
- package/dist/dev-MX7TQOJ2.js +113 -0
- package/dist/harden-HQHONZPH.js +43 -0
- package/dist/index.js +427 -0
- package/dist/init-3H2IP5US.js +117 -0
- package/dist/mcp-US3QFLOR.js +61 -0
- package/dist/merge-CZUNMMB5.js +175 -0
- package/dist/migrate-RRW4Z6RB.js +10 -0
- package/dist/serve-Q63UTTER.js +14 -0
- package/dist/studio-TNPOB665.js +103 -0
- package/package.json +46 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadProjectEnv,
|
|
4
|
+
requireDatabaseUrl
|
|
5
|
+
} from "./chunk-BUPFXTUG.js";
|
|
6
|
+
|
|
7
|
+
// src/commands/harden.ts
|
|
8
|
+
async function hardenCommand(options) {
|
|
9
|
+
loadProjectEnv(options.cwd);
|
|
10
|
+
const url = requireDatabaseUrl();
|
|
11
|
+
const [{ createDb, hardenRuntimeRole }, { GraftError }] = await Promise.all([
|
|
12
|
+
import("@usegraft/db"),
|
|
13
|
+
import("@usegraft/contracts")
|
|
14
|
+
]);
|
|
15
|
+
const handle = createDb(url);
|
|
16
|
+
try {
|
|
17
|
+
const statements = await hardenRuntimeRole(handle.db, options.role);
|
|
18
|
+
return { role: options.role, statements };
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if (error instanceof GraftError) throw error;
|
|
21
|
+
let code;
|
|
22
|
+
for (let e = error; e; e = e.cause) {
|
|
23
|
+
if (typeof e.code === "string") {
|
|
24
|
+
code = e.code;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (code === "42704") {
|
|
29
|
+
throw new GraftError({
|
|
30
|
+
code: "INPUT_VALIDATION_FAILED",
|
|
31
|
+
message: `Role "${options.role}" does not exist in this database.`,
|
|
32
|
+
fix: `Create it first (as the operator): CREATE ROLE ${options.role} LOGIN PASSWORD '\u2026' \u2014 or via your provider's console \u2014 then rerun \`graft harden ${options.role}\`.`,
|
|
33
|
+
details: { role: options.role }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
} finally {
|
|
38
|
+
await handle.close();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
hardenCommand
|
|
43
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
printGraftError
|
|
4
|
+
} from "./chunk-GPA66AP5.js";
|
|
5
|
+
|
|
6
|
+
// src/cli.ts
|
|
7
|
+
var VERSION = "0.0.0";
|
|
8
|
+
var PLANNED = [];
|
|
9
|
+
function printHelp() {
|
|
10
|
+
const lines = [
|
|
11
|
+
"graft \u2014 the agent-native CMS",
|
|
12
|
+
"",
|
|
13
|
+
"Usage: graft <command> [options]",
|
|
14
|
+
"",
|
|
15
|
+
"Commands:",
|
|
16
|
+
" init [dir] Scaffold a Graft project (graft.config.ts, content/, llms.txt).",
|
|
17
|
+
" Static index by default \u2014 no database, no env. --postgres for the",
|
|
18
|
+
" Postgres tier (operational data, functions, DB branching)",
|
|
19
|
+
" compile Project the content tree into the content index once",
|
|
20
|
+
" dev Watch content/ + graft.config.ts and recompile on change",
|
|
21
|
+
" db migrate Apply the Postgres schema migrations that ship with @usegraft/db",
|
|
22
|
+
" (the first command a Postgres-tier project runs; --dry-run lists them)",
|
|
23
|
+
" asset put <file> [key] Upload a binary to the asset store (S3_* env)",
|
|
24
|
+
" approvals List pending approvals for human-gated (destructive) function calls",
|
|
25
|
+
" approve <id> Approve a pending approval (the caller retries with x-graft-approval)",
|
|
26
|
+
" deny <id> Deny a pending approval",
|
|
27
|
+
" migrate Show pending content/data migrations (dry-run); --apply runs them",
|
|
28
|
+
" branch List branches (name, parent, backend)",
|
|
29
|
+
" branch create <name> Register a preview branch (instant; --from <parent>, default main;",
|
|
30
|
+
" --backend neon forks a physical Neon branch)",
|
|
31
|
+
" branch drop <name> Drop a branch (overlay: purge rows; neon: delete the fork)",
|
|
32
|
+
" merge <name> Merge a branch into --into (default main): replay ledger,",
|
|
33
|
+
" move data rows, recompile. Dry-run; --apply executes",
|
|
34
|
+
" add <item> Copy an owned primitive from the registry into graft/",
|
|
35
|
+
" (+ its deps; regenerates the graft/ barrel \u2014 no config edit)",
|
|
36
|
+
" mcp Serve the project MCP over stdio (content + function tools;",
|
|
37
|
+
" for .mcp.json / local agents). Requires DATABASE_URL.",
|
|
38
|
+
" serve Run the headless Graft runtime over HTTP: POST /api/fn/<name>,",
|
|
39
|
+
" POST /api/mcp, GET /healthz (what a self-host container runs)",
|
|
40
|
+
" studio Opt-in Studio UI (edit content, approve/deny, OpenAPI)",
|
|
41
|
+
" content List the content tree from the compiled index (Studio parity)",
|
|
42
|
+
" compilations List recent content projection trail rows (Studio parity)",
|
|
43
|
+
" harden <role> Grant an existing Postgres role the runtime privilege set",
|
|
44
|
+
" (can request + consume approvals but never decide them)",
|
|
45
|
+
...PLANNED.map((cmd) => ` ${cmd.name.padEnd(12)} ${cmd.summary} (${cmd.phase})`),
|
|
46
|
+
"",
|
|
47
|
+
"Options:",
|
|
48
|
+
" --branch <id> Content branch (compile/dev/migrate/mcp/serve/studio/content/compilations; default: main)",
|
|
49
|
+
" --port <n> Port for `graft serve` / `graft studio` (serve default 3903; studio 4983)",
|
|
50
|
+
" --host <h> Host for `graft serve` / `graft studio` (default: 127.0.0.1, or HOST)",
|
|
51
|
+
" --studio Mount opt-in Studio on `graft serve` at /studio (or GRAFT_STUDIO=1)",
|
|
52
|
+
" --from <name> Parent to fork from (branch create; default: main)",
|
|
53
|
+
" --into <name> Merge target (merge; default: main)",
|
|
54
|
+
" --backend <kind> Branch backend: overlay (default) or neon (branch create)",
|
|
55
|
+
" --apply Execute pending migrations / the merge (default is a dry-run report)",
|
|
56
|
+
" --postgres Scaffold `graft init` for the Postgres index (default: static)",
|
|
57
|
+
" --static Scaffold `graft init` for the static index (the default)",
|
|
58
|
+
" --dry-run Preview `graft add` without writing; list pending `graft db migrate`",
|
|
59
|
+
" --overwrite Let `graft add` replace files that differ",
|
|
60
|
+
" --prune-unknown Let `graft compile` remove index rows in collections this schema",
|
|
61
|
+
" doesn't know (default: refuse \u2014 the shared-DATABASE_URL guard)",
|
|
62
|
+
" -h, --help Show this help",
|
|
63
|
+
" -v, --version Show version"
|
|
64
|
+
];
|
|
65
|
+
console.log(lines.join("\n"));
|
|
66
|
+
}
|
|
67
|
+
var UsageError = class extends Error {
|
|
68
|
+
};
|
|
69
|
+
function parseArgs(rest) {
|
|
70
|
+
const positionals = [];
|
|
71
|
+
let branchId;
|
|
72
|
+
let from;
|
|
73
|
+
let into;
|
|
74
|
+
let backend;
|
|
75
|
+
let port;
|
|
76
|
+
let host;
|
|
77
|
+
let apply = false;
|
|
78
|
+
let dryRun = false;
|
|
79
|
+
let overwrite = false;
|
|
80
|
+
let pruneUnknown = false;
|
|
81
|
+
let studio = false;
|
|
82
|
+
let initDriver;
|
|
83
|
+
const value = (flag, raw) => {
|
|
84
|
+
if (!raw || raw.startsWith("-")) {
|
|
85
|
+
throw new UsageError(`${flag} requires a value, e.g. ${flag} main`);
|
|
86
|
+
}
|
|
87
|
+
return raw;
|
|
88
|
+
};
|
|
89
|
+
for (let i = 0; i < rest.length; i++) {
|
|
90
|
+
const arg = rest[i];
|
|
91
|
+
if (arg === "--branch" || arg === "-b") {
|
|
92
|
+
branchId = value("--branch", rest[++i]);
|
|
93
|
+
} else if (arg === "--from") {
|
|
94
|
+
from = value("--from", rest[++i]);
|
|
95
|
+
} else if (arg === "--into") {
|
|
96
|
+
into = value("--into", rest[++i]);
|
|
97
|
+
} else if (arg === "--backend") {
|
|
98
|
+
backend = value("--backend", rest[++i]);
|
|
99
|
+
} else if (arg === "--port") {
|
|
100
|
+
const raw = rest[++i];
|
|
101
|
+
if (raw === void 0 || !/^\d+$/.test(raw)) {
|
|
102
|
+
throw new UsageError("--port requires a number, e.g. --port 3903");
|
|
103
|
+
}
|
|
104
|
+
port = Number(raw);
|
|
105
|
+
} else if (arg === "--host") {
|
|
106
|
+
host = value("--host", rest[++i]);
|
|
107
|
+
} else if (arg === "--apply") {
|
|
108
|
+
apply = true;
|
|
109
|
+
} else if (arg === "--dry-run") {
|
|
110
|
+
dryRun = true;
|
|
111
|
+
} else if (arg === "--overwrite") {
|
|
112
|
+
overwrite = true;
|
|
113
|
+
} else if (arg === "--prune-unknown") {
|
|
114
|
+
pruneUnknown = true;
|
|
115
|
+
} else if (arg === "--studio") {
|
|
116
|
+
studio = true;
|
|
117
|
+
} else if (arg === "--postgres") {
|
|
118
|
+
initDriver = "postgres";
|
|
119
|
+
} else if (arg === "--static") {
|
|
120
|
+
initDriver = "static";
|
|
121
|
+
} else if (arg.startsWith("-")) {
|
|
122
|
+
throw new UsageError(`unknown option "${arg}"`);
|
|
123
|
+
} else {
|
|
124
|
+
positionals.push(arg);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
positionals,
|
|
129
|
+
branchId,
|
|
130
|
+
from,
|
|
131
|
+
into,
|
|
132
|
+
backend,
|
|
133
|
+
port,
|
|
134
|
+
host,
|
|
135
|
+
apply,
|
|
136
|
+
dryRun,
|
|
137
|
+
overwrite,
|
|
138
|
+
pruneUnknown,
|
|
139
|
+
studio,
|
|
140
|
+
initDriver
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function run(argv, options = {}) {
|
|
144
|
+
const cwd = options.cwd ?? process.cwd();
|
|
145
|
+
const [command, ...rest] = argv;
|
|
146
|
+
if (command === "-v" || command === "--version") {
|
|
147
|
+
console.log(VERSION);
|
|
148
|
+
return 0;
|
|
149
|
+
}
|
|
150
|
+
if (!command || command === "-h" || command === "--help") {
|
|
151
|
+
printHelp();
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const args = parseArgs(rest);
|
|
156
|
+
switch (command) {
|
|
157
|
+
case "init": {
|
|
158
|
+
const { initCommand } = await import("./init-3H2IP5US.js");
|
|
159
|
+
const result = initCommand({
|
|
160
|
+
targetDir: args.positionals[0] ?? cwd,
|
|
161
|
+
driver: args.initDriver
|
|
162
|
+
});
|
|
163
|
+
console.log(
|
|
164
|
+
`Initialized a Graft project in ${result.projectDir} (${result.driver} index, ${result.created.length} file(s)):`
|
|
165
|
+
);
|
|
166
|
+
for (const file of result.created) console.log(` created ${file}`);
|
|
167
|
+
console.log(
|
|
168
|
+
result.driver === "static" ? [
|
|
169
|
+
"",
|
|
170
|
+
"Next steps \u2014 no database, no environment variables:",
|
|
171
|
+
" 1. Install the runtime: add @usegraft/core and zod (plus @usegraft/cli as a dev dep)",
|
|
172
|
+
" 2. Run `graft compile` once, or keep `graft dev` running while you edit",
|
|
173
|
+
"",
|
|
174
|
+
"The index compiles to .graft/index.db (git-ignored \u2014 it is derived from your",
|
|
175
|
+
"files). Run `graft compile` before your app builds so it ships with the deploy.",
|
|
176
|
+
"Need operational data, typed functions, or DB branching? Switch `index` to",
|
|
177
|
+
'"postgres" in graft.config.ts and set DATABASE_URL.'
|
|
178
|
+
].join("\n") : [
|
|
179
|
+
"",
|
|
180
|
+
"Next steps:",
|
|
181
|
+
" 1. Install the runtime: add @usegraft/core and zod (plus @usegraft/cli as a dev dep)",
|
|
182
|
+
" 2. Put DATABASE_URL=postgres://\u2026 in .env",
|
|
183
|
+
" 3. Run `graft compile` once, or keep `graft dev` running while you edit"
|
|
184
|
+
].join("\n")
|
|
185
|
+
);
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
188
|
+
case "compile": {
|
|
189
|
+
const { compileCommand } = await import("./compile-AAUC4G7H.js");
|
|
190
|
+
await compileCommand({ cwd, branchId: args.branchId, pruneUnknown: args.pruneUnknown });
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
case "dev": {
|
|
194
|
+
const { devCommand } = await import("./dev-MX7TQOJ2.js");
|
|
195
|
+
await devCommand({ cwd, branchId: args.branchId });
|
|
196
|
+
return 0;
|
|
197
|
+
}
|
|
198
|
+
case "db": {
|
|
199
|
+
const [subcommand] = args.positionals;
|
|
200
|
+
if (subcommand !== "migrate") {
|
|
201
|
+
throw new UsageError(
|
|
202
|
+
subcommand ? `unknown db subcommand "${subcommand}" \u2014 the only one is \`graft db migrate\`` : "usage: graft db migrate [--dry-run]"
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
const { dbMigrateCommand, formatMigrateResult } = await import("./db-VGDWG4QA.js");
|
|
206
|
+
const result = await dbMigrateCommand({ cwd, dryRun: args.dryRun });
|
|
207
|
+
console.log(formatMigrateResult(result));
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
case "asset": {
|
|
211
|
+
const [subcommand, file, key] = args.positionals;
|
|
212
|
+
if (subcommand !== "put" || !file) {
|
|
213
|
+
throw new UsageError("usage: graft asset put <file> [key]");
|
|
214
|
+
}
|
|
215
|
+
const { assetPutCommand } = await import("./asset-FPEL463J.js");
|
|
216
|
+
const result = await assetPutCommand({ cwd, file, key });
|
|
217
|
+
console.log(`Uploaded ${result.key} (${result.contentType}, ${result.bytes} bytes)`);
|
|
218
|
+
console.log(
|
|
219
|
+
[
|
|
220
|
+
"",
|
|
221
|
+
"Reference it from an `asset` field in frontmatter:",
|
|
222
|
+
` image:`,
|
|
223
|
+
` key: ${result.key}`,
|
|
224
|
+
" alt: describe the image for screen readers"
|
|
225
|
+
].join("\n")
|
|
226
|
+
);
|
|
227
|
+
return 0;
|
|
228
|
+
}
|
|
229
|
+
case "approvals": {
|
|
230
|
+
const { approvalsListCommand, formatApproval } = await import("./approvals-Z3EOK5AM.js");
|
|
231
|
+
const pending = await approvalsListCommand({ cwd });
|
|
232
|
+
if (pending.length === 0) {
|
|
233
|
+
console.log("No pending approvals.");
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
console.log(`${pending.length} pending approval(s):
|
|
237
|
+
`);
|
|
238
|
+
for (const row of pending) console.log(`${formatApproval(row)}
|
|
239
|
+
`);
|
|
240
|
+
console.log("Decide with `graft approve <id>` or `graft deny <id>`.");
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
case "approve":
|
|
244
|
+
case "deny": {
|
|
245
|
+
const id = args.positionals[0];
|
|
246
|
+
if (!id) throw new UsageError(`usage: graft ${command} <approval-id>`);
|
|
247
|
+
const { decideCommand } = await import("./approvals-Z3EOK5AM.js");
|
|
248
|
+
const decision = command === "approve" ? "approved" : "denied";
|
|
249
|
+
const row = await decideCommand({ cwd, id, decision });
|
|
250
|
+
console.log(
|
|
251
|
+
`${decision}: ${row.functionName} ${JSON.stringify(row.input)} (by ${row.decidedBy})`
|
|
252
|
+
);
|
|
253
|
+
if (decision === "approved") {
|
|
254
|
+
console.log(
|
|
255
|
+
`The caller can now retry the exact same request with the header \`x-graft-approval: ${row.id}\` (one-shot).`
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
return 0;
|
|
259
|
+
}
|
|
260
|
+
case "migrate": {
|
|
261
|
+
const { migrateCommand } = await import("./migrate-RRW4Z6RB.js");
|
|
262
|
+
await migrateCommand({ cwd, branchId: args.branchId, apply: args.apply });
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
case "branch": {
|
|
266
|
+
const [subcommand, name] = args.positionals;
|
|
267
|
+
if (subcommand && subcommand !== "create" && subcommand !== "drop") {
|
|
268
|
+
throw new UsageError(
|
|
269
|
+
`unknown branch subcommand "${subcommand}" \u2014 use \`graft branch\`, \`graft branch create <name>\`, or \`graft branch drop <name>\``
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if (subcommand && !name) {
|
|
273
|
+
throw new UsageError(`usage: graft branch ${subcommand} <name>`);
|
|
274
|
+
}
|
|
275
|
+
const mod = await import("./branch-HRG46YIL.js");
|
|
276
|
+
if (!subcommand) {
|
|
277
|
+
const rows = await mod.branchListCommand({ cwd });
|
|
278
|
+
if (rows.length === 0) {
|
|
279
|
+
console.log("No branches registered (main is seeded by migration 0006).");
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
for (const row of rows) console.log(mod.formatBranch(row));
|
|
283
|
+
return 0;
|
|
284
|
+
}
|
|
285
|
+
if (subcommand === "create" && name) {
|
|
286
|
+
const meta = await mod.branchCreateCommand({
|
|
287
|
+
cwd,
|
|
288
|
+
name,
|
|
289
|
+
from: args.from,
|
|
290
|
+
backend: args.backend
|
|
291
|
+
});
|
|
292
|
+
console.log(
|
|
293
|
+
meta.backend === "neon" ? [
|
|
294
|
+
`Created neon branch "${meta.name}" from "${meta.parent}" \u2014 a physical fork at ${meta.endpointHost}.`,
|
|
295
|
+
`Content is inherited; operational data and approvals start empty on the fork.`,
|
|
296
|
+
` graft compile --branch ${meta.name} (routes to the fork automatically)`,
|
|
297
|
+
`Merge it back with \`graft merge ${meta.name}\` when ready.`
|
|
298
|
+
].join("\n") : [
|
|
299
|
+
`Created branch "${meta.name}" from "${meta.parent}" (${meta.backend} \u2014 zero rows copied).`,
|
|
300
|
+
`Reads overlay the parent until the branch writes its own rows:`,
|
|
301
|
+
` graft compile --branch ${meta.name}`,
|
|
302
|
+
`Merge it back with \`graft merge ${meta.name}\` when ready.`
|
|
303
|
+
].join("\n")
|
|
304
|
+
);
|
|
305
|
+
return 0;
|
|
306
|
+
}
|
|
307
|
+
const result = await mod.branchDropCommand({ cwd, name });
|
|
308
|
+
const p = result.purged;
|
|
309
|
+
console.log(
|
|
310
|
+
result.backend === "neon" ? `Dropped neon branch "${name}" (the fork and its endpoint were deleted).` : `Dropped branch "${name}"` + (p ? ` (purged ${p.content} content, ${p.data} data, ${p.ledger} ledger row(s)).` : ".")
|
|
311
|
+
);
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
case "merge": {
|
|
315
|
+
const branch = args.positionals[0];
|
|
316
|
+
if (!branch)
|
|
317
|
+
throw new UsageError("usage: graft merge <branch> [--into <target>] [--apply]");
|
|
318
|
+
const { mergeCommand } = await import("./merge-CZUNMMB5.js");
|
|
319
|
+
await mergeCommand({ cwd, branch, into: args.into, apply: args.apply });
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
case "add": {
|
|
323
|
+
const { addCommand } = await import("./add-UBAZMSP7.js");
|
|
324
|
+
await addCommand({
|
|
325
|
+
cwd,
|
|
326
|
+
names: args.positionals,
|
|
327
|
+
dryRun: args.dryRun,
|
|
328
|
+
overwrite: args.overwrite
|
|
329
|
+
});
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
case "mcp": {
|
|
333
|
+
const { mcpCommand } = await import("./mcp-US3QFLOR.js");
|
|
334
|
+
await mcpCommand({ cwd, branchId: args.branchId });
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
case "serve": {
|
|
338
|
+
const { serveCommand } = await import("./serve-Q63UTTER.js");
|
|
339
|
+
await serveCommand({
|
|
340
|
+
cwd,
|
|
341
|
+
branchId: args.branchId,
|
|
342
|
+
port: args.port,
|
|
343
|
+
host: args.host,
|
|
344
|
+
studio: args.studio
|
|
345
|
+
});
|
|
346
|
+
return 0;
|
|
347
|
+
}
|
|
348
|
+
case "studio": {
|
|
349
|
+
const { studioCommand } = await import("./studio-TNPOB665.js");
|
|
350
|
+
await studioCommand({
|
|
351
|
+
cwd,
|
|
352
|
+
branchId: args.branchId,
|
|
353
|
+
port: args.port,
|
|
354
|
+
host: args.host
|
|
355
|
+
});
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
case "content": {
|
|
359
|
+
const { contentListCommand, formatContentLine } = await import("./content-DY2YI3LT.js");
|
|
360
|
+
const { branch, lines } = await contentListCommand({ cwd, branchId: args.branchId });
|
|
361
|
+
if (lines.length === 0) {
|
|
362
|
+
console.log(`No content on branch "${branch}" (run graft compile).`);
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
console.log(`${lines.length} document(s) on branch "${branch}":
|
|
366
|
+
`);
|
|
367
|
+
for (const line of lines) console.log(formatContentLine(line));
|
|
368
|
+
return 0;
|
|
369
|
+
}
|
|
370
|
+
case "compilations": {
|
|
371
|
+
const { compilationsListCommand, formatCompilation } = await import("./compilations-6P45MPAE.js");
|
|
372
|
+
const rows = await compilationsListCommand({ cwd, branchId: args.branchId });
|
|
373
|
+
if (rows.length === 0) {
|
|
374
|
+
console.log("No compilations recorded yet (run graft compile).");
|
|
375
|
+
return 0;
|
|
376
|
+
}
|
|
377
|
+
for (const row of rows) console.log(formatCompilation(row));
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
case "harden": {
|
|
381
|
+
const role = args.positionals[0];
|
|
382
|
+
if (!role) throw new UsageError("usage: graft harden <role>");
|
|
383
|
+
const { hardenCommand } = await import("./harden-HQHONZPH.js");
|
|
384
|
+
const result = await hardenCommand({ cwd, role });
|
|
385
|
+
console.log(`Hardened runtime role "${result.role}" (${result.statements.length} grants):`);
|
|
386
|
+
for (const statement of result.statements) console.log(` ${statement}`);
|
|
387
|
+
console.log(
|
|
388
|
+
[
|
|
389
|
+
"",
|
|
390
|
+
"This role can request + consume approvals but can NEVER decide them",
|
|
391
|
+
"(no UPDATE on approvals) or rewrite content projections. Hand its",
|
|
392
|
+
"connection URL to the deployment as DATABASE_URL; keep the operator",
|
|
393
|
+
"URL for graft compile/migrate/branch/merge/approve."
|
|
394
|
+
].join("\n")
|
|
395
|
+
);
|
|
396
|
+
return 0;
|
|
397
|
+
}
|
|
398
|
+
default: {
|
|
399
|
+
const planned = PLANNED.find((cmd) => cmd.name === command);
|
|
400
|
+
if (planned) {
|
|
401
|
+
console.error(
|
|
402
|
+
`graft: "${planned.name}" is planned for ${planned.phase} and is not implemented yet.`
|
|
403
|
+
);
|
|
404
|
+
return 1;
|
|
405
|
+
}
|
|
406
|
+
console.error(`graft: unknown command "${command}"
|
|
407
|
+
`);
|
|
408
|
+
printHelp();
|
|
409
|
+
return 1;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (error instanceof UsageError) {
|
|
414
|
+
console.error(`graft: ${error.message}`);
|
|
415
|
+
return 1;
|
|
416
|
+
}
|
|
417
|
+
const { GraftError } = await import("@usegraft/contracts");
|
|
418
|
+
if (error instanceof GraftError) {
|
|
419
|
+
printGraftError(error);
|
|
420
|
+
return 1;
|
|
421
|
+
}
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/index.ts
|
|
427
|
+
process.exit(await run(process.argv.slice(2)));
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_FILENAMES
|
|
4
|
+
} from "./chunk-BUPFXTUG.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/init.ts
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
import { GraftError } from "@usegraft/contracts";
|
|
10
|
+
import { barrelSource } from "@usegraft/registry";
|
|
11
|
+
var configTemplate = (driver) => `/**
|
|
12
|
+
* The schema for this project \u2014 collections defined as owned code.
|
|
13
|
+
* Agents: this is the single source of truth for what content exists.
|
|
14
|
+
* Add fields here, then author documents in content/<collection>/<slug>.mdx.
|
|
15
|
+
*
|
|
16
|
+
* Primitives you add with \`graft add\` live under graft/ and are merged in via
|
|
17
|
+
* the generated graft/index.ts barrel \u2014 you never edit the import below.
|
|
18
|
+
*/
|
|
19
|
+
import { defineCollection, field, mergePrimitives } from "@usegraft/core";
|
|
20
|
+
import * as primitives from "./graft";
|
|
21
|
+
|
|
22
|
+
${driver === "static" ? `// Where the content index lives. "static" compiles to .graft/index.db \u2014
|
|
23
|
+
// no database, no env vars. Switch to "postgres" (with DATABASE_URL) when you
|
|
24
|
+
// need operational data, typed functions, or database branching.
|
|
25
|
+
export const index = "static";` : `// The content index lives in Postgres (DATABASE_URL). Content-only projects
|
|
26
|
+
// can use \`export const index = "static"\` instead and drop the database.
|
|
27
|
+
export const index = "postgres";`}
|
|
28
|
+
|
|
29
|
+
export const pages = defineCollection({
|
|
30
|
+
name: "pages",
|
|
31
|
+
description: "Pages rendered by your site.",
|
|
32
|
+
fields: {
|
|
33
|
+
title: field.string({ description: "Page headline and <title>." }),
|
|
34
|
+
tagline: field.string({ optional: true, description: "Short line under the headline." }),
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Your own collections/functions + everything under graft/ (added via \`graft add\`).
|
|
39
|
+
// mergePrimitives throws CONFIG_INVALID on a duplicate key \u2014 never a silent override.
|
|
40
|
+
export const { collections, functions } = mergePrimitives([{ collections: { pages } }, primitives]);
|
|
41
|
+
`;
|
|
42
|
+
var homeTemplate = (driver) => `---
|
|
43
|
+
title: Hello, Graft
|
|
44
|
+
tagline: ${driver === "static" ? "Content lives in git; the index is a file." : "Content lives in git; Postgres is the index."}
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
Edit this file, then run \`graft compile\` (or keep \`graft dev\` running) to
|
|
48
|
+
project it into the content index.
|
|
49
|
+
`;
|
|
50
|
+
var GITIGNORE_TEMPLATE = `# The compiled content index \u2014 derived from the files in git.
|
|
51
|
+
# Rebuild it any time with \`graft compile\`; build it in CI before your app builds.
|
|
52
|
+
.graft/
|
|
53
|
+
`;
|
|
54
|
+
var llmsTemplate = (driver) => `# Graft project \u2014 agent guide
|
|
55
|
+
|
|
56
|
+
Everything is code. Content lives in MDX files; git is authoritative; the
|
|
57
|
+
content index is derived. If they disagree, git wins \u2014 recompile.
|
|
58
|
+
|
|
59
|
+
- Schema: graft.config.ts (defineCollection/field over Zod). The single source of truth.
|
|
60
|
+
- Documents: content/<collection>/<slug>.mdx \u2014 frontmatter must satisfy the collection schema.
|
|
61
|
+
- Slugs are kebab-case; a frontmatter \`slug:\` overrides the filename.
|
|
62
|
+
- Project into the index: \`graft compile\` (one-shot) or \`graft dev\` (watch mode).
|
|
63
|
+
${driver === "static" ? `- This project uses the **static index** (\`index = "static"\` in graft.config):
|
|
64
|
+
compile writes .graft/index.db \u2014 a SQLite artifact. No database, no env vars.
|
|
65
|
+
- The artifact is derived, so it is git-ignored. Run \`graft compile\` before your
|
|
66
|
+
app builds (e.g. build command: \`graft compile && next build\`) so it ships with
|
|
67
|
+
the deploy.
|
|
68
|
+
- Read it from your app with \`openStaticIndex(".graft/index.db")\` from
|
|
69
|
+
"@usegraft/db", passed to \`createClient({ index, collections })\`.
|
|
70
|
+
- Branches here are just **git branches**: each checkout compiles its own artifact.
|
|
71
|
+
- Operational data (form submissions, orders, comments), typed functions, and
|
|
72
|
+
database branching need the Postgres tier. Adding one fails with NEEDS_DATABASE,
|
|
73
|
+
whose \`fix\` is the upgrade: set DATABASE_URL and \`export const index = "postgres"\`.` : `- Both need DATABASE_URL in .env (any parent directory works).
|
|
74
|
+
- One project = one database. Never point DATABASE_URL at another project's
|
|
75
|
+
index \u2014 compile refuses with INDEX_OWNERSHIP rather than purge its documents.`}
|
|
76
|
+
- Primitives: add owned building blocks with \`graft add <item>\` \u2014 they land under
|
|
77
|
+
graft/ and wire in automatically (the generated graft/index.ts barrel; no config
|
|
78
|
+
edit). Run \`graft add\` with no name to list what's available.
|
|
79
|
+
- Every Graft error carries a \`fix\` \u2014 do what it says, then retry.
|
|
80
|
+
- Ship changes as git commits; every projection records the git SHA it compiled from.
|
|
81
|
+
`;
|
|
82
|
+
function scaffoldFor(driver) {
|
|
83
|
+
const files = {
|
|
84
|
+
"graft.config.ts": configTemplate(driver),
|
|
85
|
+
// The generated barrel graft add regenerates; empty until the first `graft add`.
|
|
86
|
+
[join("graft", "index.ts")]: barrelSource([]),
|
|
87
|
+
[join("content", "pages", "home.mdx")]: homeTemplate(driver),
|
|
88
|
+
"llms.txt": llmsTemplate(driver)
|
|
89
|
+
};
|
|
90
|
+
if (driver === "static") files[".gitignore"] = GITIGNORE_TEMPLATE;
|
|
91
|
+
return files;
|
|
92
|
+
}
|
|
93
|
+
function initCommand(options) {
|
|
94
|
+
const projectDir = resolve(options.targetDir);
|
|
95
|
+
const driver = options.driver ?? "static";
|
|
96
|
+
for (const name of CONFIG_FILENAMES) {
|
|
97
|
+
if (existsSync(join(projectDir, name))) {
|
|
98
|
+
throw new GraftError({
|
|
99
|
+
code: "ALREADY_INITIALIZED",
|
|
100
|
+
message: `${name} already exists in ${projectDir}.`,
|
|
101
|
+
fix: "This is already a Graft project \u2014 evolve it by editing the existing graft.config.ts, or run `graft init <dir>` against an empty directory."
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const created = [];
|
|
106
|
+
for (const [relPath, body] of Object.entries(scaffoldFor(driver))) {
|
|
107
|
+
const absPath = join(projectDir, relPath);
|
|
108
|
+
if (existsSync(absPath)) continue;
|
|
109
|
+
mkdirSync(dirname(absPath), { recursive: true });
|
|
110
|
+
writeFileSync(absPath, body, "utf8");
|
|
111
|
+
created.push(relPath);
|
|
112
|
+
}
|
|
113
|
+
return { projectDir, driver, created };
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
initCommand
|
|
117
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
assertNoStaticBranch
|
|
4
|
+
} from "./chunk-KZ3FDWUK.js";
|
|
5
|
+
import "./chunk-GPA66AP5.js";
|
|
6
|
+
import {
|
|
7
|
+
findConfig,
|
|
8
|
+
loadConfig,
|
|
9
|
+
loadProjectEnv,
|
|
10
|
+
requireDatabaseUrl
|
|
11
|
+
} from "./chunk-BUPFXTUG.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/mcp.ts
|
|
14
|
+
import { createActorResolver } from "@usegraft/auth";
|
|
15
|
+
async function mcpCommand(options) {
|
|
16
|
+
loadProjectEnv(options.cwd);
|
|
17
|
+
const config = await loadConfig(findConfig(options.cwd));
|
|
18
|
+
const isStatic = config.index.driver === "static";
|
|
19
|
+
if (isStatic) assertNoStaticBranch(options.branchId);
|
|
20
|
+
const url = isStatic ? void 0 : requireDatabaseUrl();
|
|
21
|
+
const [{ createGraftMcp, serveStdio }, { createDb, resolveBranchHandle, scopeWriteBranch }] = await Promise.all([import("@usegraft/mcp"), import("@usegraft/db")]);
|
|
22
|
+
const control = url !== void 0 ? createDb(url) : void 0;
|
|
23
|
+
const branchName = options.branchId ?? "main";
|
|
24
|
+
const branch = control !== void 0 && url !== void 0 ? await resolveBranchHandle(control.db, branchName, { databaseUrl: url }) : void 0;
|
|
25
|
+
try {
|
|
26
|
+
const devToken = process.env.GRAFT_DEV_TOKEN;
|
|
27
|
+
const scopes = (process.env.GRAFT_DEV_SCOPES ?? "").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
28
|
+
const resolveActor = createActorResolver({
|
|
29
|
+
issuers: [],
|
|
30
|
+
devTokens: devToken ? {
|
|
31
|
+
[devToken]: {
|
|
32
|
+
kind: "agent",
|
|
33
|
+
id: "graft-cli",
|
|
34
|
+
scopes
|
|
35
|
+
}
|
|
36
|
+
} : void 0
|
|
37
|
+
});
|
|
38
|
+
const server = createGraftMcp({
|
|
39
|
+
name: "graft",
|
|
40
|
+
contentDir: config.contentDir,
|
|
41
|
+
collections: config.collections,
|
|
42
|
+
functions: config.functions,
|
|
43
|
+
...branch !== void 0 ? {
|
|
44
|
+
db: branch.db,
|
|
45
|
+
branchId: scopeWriteBranch(branch.scope),
|
|
46
|
+
// The handle's resolved scope makes search_content overlay-aware: an
|
|
47
|
+
// overlay branch searches its full parent chain, not just its own rows.
|
|
48
|
+
scope: branch.scope
|
|
49
|
+
} : { staticIndexPath: config.index.path },
|
|
50
|
+
actor: resolveActor,
|
|
51
|
+
defaultAuthorization: devToken
|
|
52
|
+
});
|
|
53
|
+
await serveStdio(server);
|
|
54
|
+
} finally {
|
|
55
|
+
await branch?.close();
|
|
56
|
+
await control?.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
mcpCommand
|
|
61
|
+
};
|