@usegraft/cli 0.1.1 → 0.2.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/README.md +60 -0
- package/dist/{add-UBAZMSP7.js → add-ASXTMYCX.js} +1 -1
- package/dist/{approvals-Z3EOK5AM.js → approvals-MN3IS3FO.js} +5 -7
- package/dist/{asset-FPEL463J.js → asset-DMNX5JBZ.js} +1 -1
- package/dist/{branch-HRG46YIL.js → branch-IDY42TAV.js} +1 -1
- package/dist/{chunk-BUPFXTUG.js → chunk-2NH6KYTL.js} +16 -5
- package/dist/{chunk-LTFTDG6Z.js → chunk-6Z27SGVO.js} +2 -1
- package/dist/{chunk-KZ3FDWUK.js → chunk-GDTOV3DU.js} +3 -1
- package/dist/{chunk-TUWL7WFD.js → chunk-U25NVB6W.js} +54 -14
- package/dist/{compilations-6P45MPAE.js → compilations-72DJ2VEP.js} +1 -1
- package/dist/{compile-AAUC4G7H.js → compile-TCBGZZAU.js} +2 -2
- package/dist/{content-DY2YI3LT.js → content-VLKNYIHL.js} +1 -1
- package/dist/{db-VGDWG4QA.js → db-WACSH3HN.js} +1 -1
- package/dist/{dev-MX7TQOJ2.js → dev-4YSMWIAS.js} +14 -3
- package/dist/{harden-HQHONZPH.js → harden-QASY2KMI.js} +1 -1
- package/dist/index.js +17 -17
- package/dist/{init-3H2IP5US.js → init-TRPSGUYW.js} +1 -1
- package/dist/{mcp-US3QFLOR.js → mcp-LUJMTAQO.js} +18 -10
- package/dist/{merge-CZUNMMB5.js → merge-XMTFPLHO.js} +3 -2
- package/dist/{migrate-RRW4Z6RB.js → migrate-QB44VL6R.js} +2 -2
- package/dist/{serve-Q63UTTER.js → serve-ZGXLEAEB.js} +4 -2
- package/dist/{studio-TNPOB665.js → studio-HXVSO4WJ.js} +17 -11
- package/package.json +30 -14
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @usegraft/cli
|
|
2
|
+
|
|
3
|
+
> The `graft` command. Scaffold a project, compile authored content, serve the runtime, split your database credentials.
|
|
4
|
+
|
|
5
|
+
Part of [Graft](https://github.com/AndersonDesign1/graft), a CMS built so an AI agent is the primary operator.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -D @usegraft/cli
|
|
11
|
+
# or run it without installing
|
|
12
|
+
npx graft init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart, with no database
|
|
16
|
+
|
|
17
|
+
A content project needs no services at all. `graft compile` writes the index to a SQLite artifact your app reads embedded.
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx graft init # scaffolds graft.config.ts, content/, llms.txt
|
|
21
|
+
npx graft compile # → .graft/index.db
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Move to Postgres by setting `DATABASE_URL` and changing one line in `graft.config.ts`:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
export const index = "postgres";
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Commands
|
|
31
|
+
|
|
32
|
+
| Command | Does |
|
|
33
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `graft init` | Scaffold a project |
|
|
35
|
+
| `graft compile` | Validate authored MDX and project it into the index |
|
|
36
|
+
| `graft dev` | Recompile on save |
|
|
37
|
+
| `graft serve` | Run the headless runtime: typed functions, MCP over HTTP, `/healthz` |
|
|
38
|
+
| `graft studio` | Open the optional editing UI |
|
|
39
|
+
| `graft mcp` | Serve MCP on stdio, for `.mcp.json` |
|
|
40
|
+
| `graft add <item>` | Copy a registry primitive into your repo, shadcn-style |
|
|
41
|
+
| `graft approve <id>` | Decide a pending approval. Operator credential only |
|
|
42
|
+
| `graft harden <role>` | Grant a Postgres role exactly what a deployment needs, and nothing that can decide an approval |
|
|
43
|
+
| `graft branch` / `graft merge` | Copy-on-write content branches |
|
|
44
|
+
| `graft migrate` | Run content codemods when the schema changes |
|
|
45
|
+
|
|
46
|
+
Run `graft <command> --help` for flags.
|
|
47
|
+
|
|
48
|
+
## Two credentials
|
|
49
|
+
|
|
50
|
+
`graft harden` splits the operator credential from the runtime one. The runtime can serve, project content, and request approvals. It can never decide one, and Postgres enforces that rather than the application.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
graft harden graft_runtime
|
|
54
|
+
# deploy with the runtime role's URL as DATABASE_URL
|
|
55
|
+
# keep the operator URL for compile, migrate, merge and approve
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
MIT. [Repository](https://github.com/AndersonDesign1/graft) · [Changelog](https://github.com/AndersonDesign1/graft/blob/feat/core/packages/cli/CHANGELOG.md) · [Security policy](https://github.com/AndersonDesign1/graft/blob/feat/core/SECURITY.md)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
loadProjectEnv,
|
|
4
4
|
requireDatabaseUrl
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2NH6KYTL.js";
|
|
6
6
|
|
|
7
7
|
// src/commands/approvals.ts
|
|
8
8
|
import { userInfo } from "os";
|
|
@@ -33,12 +33,10 @@ async function decideCommand(options) {
|
|
|
33
33
|
]);
|
|
34
34
|
const handle = createDb(url);
|
|
35
35
|
try {
|
|
36
|
-
const row = await decideApproval(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
options.decidedBy ?? operatorName()
|
|
41
|
-
);
|
|
36
|
+
const row = await decideApproval(handle.db, options.id, options.decision, {
|
|
37
|
+
kind: "human",
|
|
38
|
+
id: operatorName()
|
|
39
|
+
});
|
|
42
40
|
if (!row) {
|
|
43
41
|
throw new GraftError({
|
|
44
42
|
code: "APPROVAL_INVALID",
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/config.ts
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { dirname, join, resolve } from "path";
|
|
6
|
-
import { GraftError } from "@usegraft/contracts";
|
|
6
|
+
import { GraftError, STATIC_INDEX_DEFAULT_PATH } from "@usegraft/contracts";
|
|
7
7
|
import { createJiti } from "jiti";
|
|
8
8
|
var CONFIG_FILENAMES = [
|
|
9
9
|
"graft.config.ts",
|
|
@@ -97,6 +97,7 @@ async function loadConfig(configPath) {
|
|
|
97
97
|
const projectDir = dirname(configPath);
|
|
98
98
|
const index = parseIndexConfig(mod.index, projectDir, configPath);
|
|
99
99
|
assertStaticSupports(index, collections, functions, configPath);
|
|
100
|
+
const mdxTrust = parseMdxTrust(mod.mdxTrust, configPath);
|
|
100
101
|
const contentDirSetting = typeof mod.contentDir === "string" ? mod.contentDir : "content";
|
|
101
102
|
const migrationsDirSetting = typeof mod.migrationsDir === "string" ? mod.migrationsDir : "migrations";
|
|
102
103
|
return {
|
|
@@ -106,9 +107,20 @@ async function loadConfig(configPath) {
|
|
|
106
107
|
migrationsDir: resolve(projectDir, migrationsDirSetting),
|
|
107
108
|
index,
|
|
108
109
|
collections,
|
|
109
|
-
functions
|
|
110
|
+
functions,
|
|
111
|
+
mdxTrust
|
|
110
112
|
};
|
|
111
113
|
}
|
|
114
|
+
function parseMdxTrust(value, configPath) {
|
|
115
|
+
if (value === void 0) return "restricted";
|
|
116
|
+
if (value === "restricted" || value === "full") return value;
|
|
117
|
+
throw new GraftError({
|
|
118
|
+
code: "CONFIG_INVALID",
|
|
119
|
+
message: `${configPath} exports \`mdxTrust\` as ${JSON.stringify(value)}, which is not "restricted" or "full".`,
|
|
120
|
+
fix: 'Export `mdxTrust = "restricted"` (the default: `{\u2026}` expressions and `import` are refused in authored bodies) or `mdxTrust = "full"` (every author has commit access, so code review is the control). Omit the export to keep the default.',
|
|
121
|
+
details: { mdxTrust: value }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
112
124
|
function assertStaticSupports(index, collections, functions, configPath) {
|
|
113
125
|
if (index.driver !== "static") return;
|
|
114
126
|
const dbCollections = Object.values(collections).filter((collection) => collection.authority === "db-authoritative").map((collection) => collection.name);
|
|
@@ -125,18 +137,17 @@ function assertStaticSupports(index, collections, functions, configPath) {
|
|
|
125
137
|
details: { dbCollections, functions: functionNames }
|
|
126
138
|
});
|
|
127
139
|
}
|
|
128
|
-
var STATIC_INDEX_DEFAULT = ".graft/index.db";
|
|
129
140
|
function parseIndexConfig(value, projectDir, configPath) {
|
|
130
141
|
if (value === void 0 || value === "postgres") return { driver: "postgres" };
|
|
131
142
|
if (value === "static") {
|
|
132
|
-
return { driver: "static", path: resolve(projectDir,
|
|
143
|
+
return { driver: "static", path: resolve(projectDir, STATIC_INDEX_DEFAULT_PATH) };
|
|
133
144
|
}
|
|
134
145
|
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
135
146
|
const driver = value.driver;
|
|
136
147
|
const path = value.path;
|
|
137
148
|
if (driver === "postgres") return { driver: "postgres" };
|
|
138
149
|
if (driver === "static" && (path === void 0 || typeof path === "string")) {
|
|
139
|
-
return { driver: "static", path: resolve(projectDir, path ??
|
|
150
|
+
return { driver: "static", path: resolve(projectDir, path ?? STATIC_INDEX_DEFAULT_PATH) };
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
153
|
throw new GraftError({
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
loadConfig,
|
|
5
5
|
loadProjectEnv,
|
|
6
6
|
requireDatabaseUrl
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2NH6KYTL.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/migrate.ts
|
|
10
10
|
import { existsSync, readdirSync, statSync } from "fs";
|
|
@@ -99,6 +99,7 @@ Author one as migrations/0001-<name>.ts default-exporting defineContentMigration
|
|
|
99
99
|
db: branch.db,
|
|
100
100
|
contentDir: config.contentDir,
|
|
101
101
|
collections: config.collections,
|
|
102
|
+
mdxTrust: config.mdxTrust,
|
|
102
103
|
branchId: writeBranch,
|
|
103
104
|
gitSha
|
|
104
105
|
});
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
loadConfig,
|
|
8
8
|
loadProjectEnv,
|
|
9
9
|
requireDatabaseUrl
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2NH6KYTL.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/compile.ts
|
|
13
13
|
import { GraftError } from "@usegraft/contracts";
|
|
@@ -29,6 +29,7 @@ async function compileCommand(options) {
|
|
|
29
29
|
const result = await compileStatic({
|
|
30
30
|
contentDir: config.contentDir,
|
|
31
31
|
collections: config.collections,
|
|
32
|
+
mdxTrust: config.mdxTrust,
|
|
32
33
|
indexPath: config.index.path
|
|
33
34
|
});
|
|
34
35
|
console.log(formatCompileResult(result));
|
|
@@ -52,6 +53,7 @@ async function compileCommand(options) {
|
|
|
52
53
|
db: branch.db,
|
|
53
54
|
contentDir: config.contentDir,
|
|
54
55
|
collections: config.collections,
|
|
56
|
+
mdxTrust: config.mdxTrust,
|
|
55
57
|
branchId: scopeWriteBranch(branch.scope),
|
|
56
58
|
pruneUnknown: options.pruneUnknown
|
|
57
59
|
});
|
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
loadConfig,
|
|
5
5
|
loadProjectEnv,
|
|
6
6
|
requireDatabaseUrl
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2NH6KYTL.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/serve.ts
|
|
10
10
|
import { execFileSync } from "child_process";
|
|
11
11
|
import { createServer } from "http";
|
|
12
12
|
import { createActorResolver } from "@usegraft/auth";
|
|
13
13
|
import { GraftError } from "@usegraft/contracts";
|
|
14
|
+
import { setRequestPeer } from "@usegraft/core";
|
|
14
15
|
function createServeRouter(routes) {
|
|
15
16
|
return async (request) => {
|
|
16
17
|
const { pathname } = new URL(request.url);
|
|
@@ -41,9 +42,34 @@ var HOP_BY_HOP = /* @__PURE__ */ new Set([
|
|
|
41
42
|
"upgrade",
|
|
42
43
|
"proxy-connection"
|
|
43
44
|
]);
|
|
44
|
-
function
|
|
45
|
+
function allowedHostsFor(host) {
|
|
46
|
+
if (host === "0.0.0.0" || host === "::" || host === "") return void 0;
|
|
47
|
+
if (host === "127.0.0.1" || host === "localhost" || host === "::1") {
|
|
48
|
+
return ["127.0.0.1", "localhost", "[::1]"];
|
|
49
|
+
}
|
|
50
|
+
return [host];
|
|
51
|
+
}
|
|
52
|
+
function hostname(host) {
|
|
53
|
+
const trimmed = host.trim().toLowerCase();
|
|
54
|
+
if (trimmed.startsWith("[")) return trimmed.slice(0, trimmed.indexOf("]") + 1);
|
|
55
|
+
const colon = trimmed.lastIndexOf(":");
|
|
56
|
+
return colon === -1 ? trimmed : trimmed.slice(0, colon);
|
|
57
|
+
}
|
|
58
|
+
function createNodeListener(handler, options = {}) {
|
|
59
|
+
const allowed = options.allowedHosts?.map(hostname);
|
|
45
60
|
return async (req, res) => {
|
|
46
61
|
try {
|
|
62
|
+
if (allowed && !allowed.includes(hostname(req.headers.host ?? ""))) {
|
|
63
|
+
res.writeHead(400, { "content-type": "application/json" });
|
|
64
|
+
res.end(
|
|
65
|
+
JSON.stringify({
|
|
66
|
+
error: "INPUT_VALIDATION_FAILED",
|
|
67
|
+
message: `This server does not answer to Host "${req.headers.host ?? ""}".`,
|
|
68
|
+
fix: `Reach it at one of: ${allowed.join(", ")}. A mismatched Host is either a misconfigured proxy or a DNS-rebinding attempt.`
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
47
73
|
const chunks = [];
|
|
48
74
|
for await (const chunk of req) chunks.push(chunk);
|
|
49
75
|
const body = Buffer.concat(chunks);
|
|
@@ -59,6 +85,8 @@ function createNodeListener(handler) {
|
|
|
59
85
|
headers,
|
|
60
86
|
body: method === "GET" || method === "HEAD" || body.length === 0 ? void 0 : body
|
|
61
87
|
});
|
|
88
|
+
const peer = req.socket.remoteAddress;
|
|
89
|
+
if (peer) setRequestPeer(request, peer);
|
|
62
90
|
const response = await handler(request);
|
|
63
91
|
const outHeaders = {};
|
|
64
92
|
response.headers.forEach((value, key) => {
|
|
@@ -120,7 +148,10 @@ async function startServe(options) {
|
|
|
120
148
|
devTokens: devToken ? { [devToken]: { kind: "agent", id: "graft-serve", scopes } } : void 0
|
|
121
149
|
});
|
|
122
150
|
const approvalPolicy = process.env.GRAFT_APPROVAL_POLICY === "human" ? "human" : "none";
|
|
123
|
-
const
|
|
151
|
+
const host = options.host ?? process.env.HOST ?? "127.0.0.1";
|
|
152
|
+
const loopback = host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
153
|
+
const anonymousOptIn = process.env.GRAFT_MCP_ALLOW_ANONYMOUS === "1";
|
|
154
|
+
const allowAnonymousMcp = loopback || anonymousOptIn;
|
|
124
155
|
const functions = config.functions ?? {};
|
|
125
156
|
const gitSha = localGitSha(options.cwd);
|
|
126
157
|
const fnHandler = createFunctionsHandler({
|
|
@@ -138,11 +169,12 @@ async function startServe(options) {
|
|
|
138
169
|
contentDir: config.contentDir,
|
|
139
170
|
collections: config.collections,
|
|
140
171
|
functions,
|
|
172
|
+
mdxTrust: config.mdxTrust,
|
|
141
173
|
db: branch.db,
|
|
142
174
|
branchId: writeBranch,
|
|
143
175
|
scope: branch.scope,
|
|
144
176
|
actor: resolveActor,
|
|
145
|
-
|
|
177
|
+
allowAnonymous: allowAnonymousMcp
|
|
146
178
|
});
|
|
147
179
|
const health = async () => {
|
|
148
180
|
try {
|
|
@@ -167,7 +199,6 @@ async function startServe(options) {
|
|
|
167
199
|
{ status: 200, headers: { "content-type": "application/json" } }
|
|
168
200
|
);
|
|
169
201
|
};
|
|
170
|
-
const host = options.host ?? process.env.HOST ?? "127.0.0.1";
|
|
171
202
|
const requestedPort = options.port ?? Number(process.env.PORT ?? 3903);
|
|
172
203
|
if (!Number.isInteger(requestedPort) || requestedPort < 0 || requestedPort > 65535) {
|
|
173
204
|
await branch.close();
|
|
@@ -178,31 +209,39 @@ async function startServe(options) {
|
|
|
178
209
|
fix: "Pass --port <0-65535> (0 picks a free port) or set PORT."
|
|
179
210
|
});
|
|
180
211
|
}
|
|
181
|
-
|
|
182
|
-
|
|
212
|
+
if (!loopback && anonymousOptIn) {
|
|
213
|
+
console.warn(
|
|
214
|
+
"[graft serve] WARNING: GRAFT_MCP_ALLOW_ANONYMOUS=1 while bound beyond loopback \u2014 unauthenticated callers can write content, upload assets and decide approvals. Only do this behind an auth proxy you control."
|
|
215
|
+
);
|
|
216
|
+
} else if (!loopback && issuers.length === 0 && !devToken) {
|
|
183
217
|
console.warn(
|
|
184
|
-
"[graft serve] WARNING: binding beyond loopback with no identity configured \u2014
|
|
218
|
+
"[graft serve] WARNING: binding beyond loopback with no identity configured \u2014 MCP will refuse every caller, because there is nothing to authenticate them against. Set GRAFT_DEV_TOKEN or GRAFT_TRUSTED_ISSUERS."
|
|
185
219
|
);
|
|
186
220
|
}
|
|
187
221
|
let studioHandler;
|
|
188
222
|
if (studioMod) {
|
|
189
|
-
const
|
|
223
|
+
const authenticate = !loopback && (devToken || issuers.length > 0) ? async (request) => {
|
|
190
224
|
const actor = await resolveActor(request);
|
|
191
|
-
|
|
192
|
-
|
|
225
|
+
if (actor.kind === "anonymous" || !actor.id) return null;
|
|
226
|
+
return { kind: actor.kind, id: actor.id, scopes: actor.scopes ?? [] };
|
|
227
|
+
} : !loopback ? () => null : void 0;
|
|
193
228
|
studioHandler = studioMod.createStudioHandler({
|
|
194
229
|
db: branch.db,
|
|
195
230
|
collections: config.collections,
|
|
196
231
|
contentDir: config.contentDir,
|
|
232
|
+
mdxTrust: config.mdxTrust,
|
|
197
233
|
defaultBranch: writeBranch,
|
|
198
|
-
|
|
234
|
+
// Only reached on a loopback mount, where there is no caller identity to
|
|
235
|
+
// attribute to. Off loopback the authenticated principal is used instead.
|
|
236
|
+
decider: { kind: "agent", id: "studio-serve" },
|
|
199
237
|
uiBasePath: "/studio",
|
|
200
|
-
|
|
238
|
+
authenticate
|
|
201
239
|
});
|
|
202
240
|
}
|
|
203
241
|
const server = createServer(
|
|
204
242
|
createNodeListener(
|
|
205
|
-
createServeRouter({ fn: fnHandler, mcp: mcpHandler, health, studio: studioHandler })
|
|
243
|
+
createServeRouter({ fn: fnHandler, mcp: mcpHandler, health, studio: studioHandler }),
|
|
244
|
+
{ allowedHosts: allowedHostsFor(host) }
|
|
206
245
|
)
|
|
207
246
|
);
|
|
208
247
|
await new Promise((resolve, reject) => {
|
|
@@ -250,6 +289,7 @@ async function serveCommand(options) {
|
|
|
250
289
|
|
|
251
290
|
export {
|
|
252
291
|
createServeRouter,
|
|
292
|
+
allowedHostsFor,
|
|
253
293
|
createNodeListener,
|
|
254
294
|
startServe,
|
|
255
295
|
serveCommand
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import {
|
|
3
3
|
assertNoStaticBranch,
|
|
4
4
|
compileCommand
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GDTOV3DU.js";
|
|
6
6
|
import "./chunk-GPA66AP5.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-2NH6KYTL.js";
|
|
8
8
|
export {
|
|
9
9
|
assertNoStaticBranch,
|
|
10
10
|
compileCommand
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
assertNoStaticBranch
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GDTOV3DU.js";
|
|
5
5
|
import {
|
|
6
6
|
formatCompileResult,
|
|
7
7
|
printGraftError
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
loadConfig,
|
|
13
13
|
loadProjectEnv,
|
|
14
14
|
requireDatabaseUrl
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-2NH6KYTL.js";
|
|
16
16
|
|
|
17
17
|
// src/commands/dev.ts
|
|
18
18
|
import { existsSync, watch } from "fs";
|
|
@@ -42,6 +42,7 @@ async function devCommand(options) {
|
|
|
42
42
|
const branch = control !== void 0 && url !== void 0 ? await resolveBranchHandle(control.db, options.branchId ?? "main", { databaseUrl: url }) : void 0;
|
|
43
43
|
const writeBranch = branch !== void 0 ? scopeWriteBranch(branch.scope) : "main";
|
|
44
44
|
let timer;
|
|
45
|
+
let rewatchContent;
|
|
45
46
|
let compiling = false;
|
|
46
47
|
let dirty = false;
|
|
47
48
|
let configDirty = false;
|
|
@@ -54,17 +55,21 @@ async function devCommand(options) {
|
|
|
54
55
|
try {
|
|
55
56
|
if (configDirty) {
|
|
56
57
|
configDirty = false;
|
|
58
|
+
const previousContentDir = config.contentDir;
|
|
57
59
|
config = await loadConfig(configPath);
|
|
58
60
|
console.log(`reloaded ${basename(configPath)}`);
|
|
61
|
+
if (config.contentDir !== previousContentDir) rewatchContent?.(config.contentDir);
|
|
59
62
|
}
|
|
60
63
|
const result = branch !== void 0 ? await compile({
|
|
61
64
|
db: branch.db,
|
|
62
65
|
contentDir: config.contentDir,
|
|
63
66
|
collections: config.collections,
|
|
67
|
+
mdxTrust: config.mdxTrust,
|
|
64
68
|
branchId: writeBranch
|
|
65
69
|
}) : await compileStatic({
|
|
66
70
|
contentDir: config.contentDir,
|
|
67
71
|
collections: config.collections,
|
|
72
|
+
mdxTrust: config.mdxTrust,
|
|
68
73
|
indexPath: config.index.path
|
|
69
74
|
});
|
|
70
75
|
console.log(formatCompileResult(result));
|
|
@@ -85,12 +90,17 @@ async function devCommand(options) {
|
|
|
85
90
|
timer = setTimeout(() => void runCompile(), DEBOUNCE_MS);
|
|
86
91
|
};
|
|
87
92
|
await runCompile();
|
|
93
|
+
let contentWatcher = watch(config.contentDir, { recursive: true }, () => schedule());
|
|
88
94
|
const watchers = [
|
|
89
|
-
watch(config.contentDir, { recursive: true }, () => schedule()),
|
|
90
95
|
watch(config.projectDir, (_event, filename) => {
|
|
91
96
|
if (filename && CONFIG_FILENAMES.includes(filename)) schedule(true);
|
|
92
97
|
})
|
|
93
98
|
];
|
|
99
|
+
rewatchContent = (dir) => {
|
|
100
|
+
contentWatcher.close();
|
|
101
|
+
contentWatcher = watch(dir, { recursive: true }, () => schedule());
|
|
102
|
+
console.log(`now watching ${relative(options.cwd, dir) || "."}`);
|
|
103
|
+
};
|
|
94
104
|
const watchedContent = relative(options.cwd, config.contentDir) || ".";
|
|
95
105
|
const branchLabel = branch === void 0 ? "static index" : branch.scope.kind === "physical" ? `${branch.name} (neon)` : options.branchId ?? "main";
|
|
96
106
|
console.log(
|
|
@@ -99,6 +109,7 @@ async function devCommand(options) {
|
|
|
99
109
|
await new Promise((resolveStopped) => {
|
|
100
110
|
const stop = () => {
|
|
101
111
|
clearTimeout(timer);
|
|
112
|
+
contentWatcher.close();
|
|
102
113
|
for (const watcher of watchers) watcher.close();
|
|
103
114
|
resolveStopped();
|
|
104
115
|
};
|
package/dist/index.js
CHANGED
|
@@ -156,7 +156,7 @@ async function run(argv, options = {}) {
|
|
|
156
156
|
const args = parseArgs(rest);
|
|
157
157
|
switch (command) {
|
|
158
158
|
case "init": {
|
|
159
|
-
const { initCommand } = await import("./init-
|
|
159
|
+
const { initCommand } = await import("./init-TRPSGUYW.js");
|
|
160
160
|
const result = initCommand({
|
|
161
161
|
targetDir: args.positionals[0] ?? cwd,
|
|
162
162
|
driver: args.initDriver
|
|
@@ -187,12 +187,12 @@ async function run(argv, options = {}) {
|
|
|
187
187
|
return 0;
|
|
188
188
|
}
|
|
189
189
|
case "compile": {
|
|
190
|
-
const { compileCommand } = await import("./compile-
|
|
190
|
+
const { compileCommand } = await import("./compile-TCBGZZAU.js");
|
|
191
191
|
await compileCommand({ cwd, branchId: args.branchId, pruneUnknown: args.pruneUnknown });
|
|
192
192
|
return 0;
|
|
193
193
|
}
|
|
194
194
|
case "dev": {
|
|
195
|
-
const { devCommand } = await import("./dev-
|
|
195
|
+
const { devCommand } = await import("./dev-4YSMWIAS.js");
|
|
196
196
|
await devCommand({ cwd, branchId: args.branchId });
|
|
197
197
|
return 0;
|
|
198
198
|
}
|
|
@@ -203,7 +203,7 @@ async function run(argv, options = {}) {
|
|
|
203
203
|
subcommand ? `unknown db subcommand "${subcommand}" \u2014 the only one is \`graft db migrate\`` : "usage: graft db migrate [--dry-run]"
|
|
204
204
|
);
|
|
205
205
|
}
|
|
206
|
-
const { dbMigrateCommand, formatMigrateResult } = await import("./db-
|
|
206
|
+
const { dbMigrateCommand, formatMigrateResult } = await import("./db-WACSH3HN.js");
|
|
207
207
|
const result = await dbMigrateCommand({ cwd, dryRun: args.dryRun });
|
|
208
208
|
console.log(formatMigrateResult(result));
|
|
209
209
|
return 0;
|
|
@@ -213,7 +213,7 @@ async function run(argv, options = {}) {
|
|
|
213
213
|
if (subcommand !== "put" || !file) {
|
|
214
214
|
throw new UsageError("usage: graft asset put <file> [key]");
|
|
215
215
|
}
|
|
216
|
-
const { assetPutCommand } = await import("./asset-
|
|
216
|
+
const { assetPutCommand } = await import("./asset-DMNX5JBZ.js");
|
|
217
217
|
const result = await assetPutCommand({ cwd, file, key });
|
|
218
218
|
console.log(`Uploaded ${result.key} (${result.contentType}, ${result.bytes} bytes)`);
|
|
219
219
|
console.log(
|
|
@@ -228,7 +228,7 @@ async function run(argv, options = {}) {
|
|
|
228
228
|
return 0;
|
|
229
229
|
}
|
|
230
230
|
case "approvals": {
|
|
231
|
-
const { approvalsListCommand, formatApproval } = await import("./approvals-
|
|
231
|
+
const { approvalsListCommand, formatApproval } = await import("./approvals-MN3IS3FO.js");
|
|
232
232
|
const pending = await approvalsListCommand({ cwd });
|
|
233
233
|
if (pending.length === 0) {
|
|
234
234
|
console.log("No pending approvals.");
|
|
@@ -245,7 +245,7 @@ async function run(argv, options = {}) {
|
|
|
245
245
|
case "deny": {
|
|
246
246
|
const id = args.positionals[0];
|
|
247
247
|
if (!id) throw new UsageError(`usage: graft ${command} <approval-id>`);
|
|
248
|
-
const { decideCommand } = await import("./approvals-
|
|
248
|
+
const { decideCommand } = await import("./approvals-MN3IS3FO.js");
|
|
249
249
|
const decision = command === "approve" ? "approved" : "denied";
|
|
250
250
|
const row = await decideCommand({ cwd, id, decision });
|
|
251
251
|
console.log(
|
|
@@ -259,7 +259,7 @@ async function run(argv, options = {}) {
|
|
|
259
259
|
return 0;
|
|
260
260
|
}
|
|
261
261
|
case "migrate": {
|
|
262
|
-
const { migrateCommand } = await import("./migrate-
|
|
262
|
+
const { migrateCommand } = await import("./migrate-QB44VL6R.js");
|
|
263
263
|
await migrateCommand({ cwd, branchId: args.branchId, apply: args.apply });
|
|
264
264
|
return 0;
|
|
265
265
|
}
|
|
@@ -273,7 +273,7 @@ async function run(argv, options = {}) {
|
|
|
273
273
|
if (subcommand && !name) {
|
|
274
274
|
throw new UsageError(`usage: graft branch ${subcommand} <name>`);
|
|
275
275
|
}
|
|
276
|
-
const mod = await import("./branch-
|
|
276
|
+
const mod = await import("./branch-IDY42TAV.js");
|
|
277
277
|
if (!subcommand) {
|
|
278
278
|
const rows = await mod.branchListCommand({ cwd });
|
|
279
279
|
if (rows.length === 0) {
|
|
@@ -316,12 +316,12 @@ async function run(argv, options = {}) {
|
|
|
316
316
|
const branch = args.positionals[0];
|
|
317
317
|
if (!branch)
|
|
318
318
|
throw new UsageError("usage: graft merge <branch> [--into <target>] [--apply]");
|
|
319
|
-
const { mergeCommand } = await import("./merge-
|
|
319
|
+
const { mergeCommand } = await import("./merge-XMTFPLHO.js");
|
|
320
320
|
await mergeCommand({ cwd, branch, into: args.into, apply: args.apply });
|
|
321
321
|
return 0;
|
|
322
322
|
}
|
|
323
323
|
case "add": {
|
|
324
|
-
const { addCommand } = await import("./add-
|
|
324
|
+
const { addCommand } = await import("./add-ASXTMYCX.js");
|
|
325
325
|
await addCommand({
|
|
326
326
|
cwd,
|
|
327
327
|
names: args.positionals,
|
|
@@ -331,12 +331,12 @@ async function run(argv, options = {}) {
|
|
|
331
331
|
return 0;
|
|
332
332
|
}
|
|
333
333
|
case "mcp": {
|
|
334
|
-
const { mcpCommand } = await import("./mcp-
|
|
334
|
+
const { mcpCommand } = await import("./mcp-LUJMTAQO.js");
|
|
335
335
|
await mcpCommand({ cwd, branchId: args.branchId });
|
|
336
336
|
return 0;
|
|
337
337
|
}
|
|
338
338
|
case "serve": {
|
|
339
|
-
const { serveCommand } = await import("./serve-
|
|
339
|
+
const { serveCommand } = await import("./serve-ZGXLEAEB.js");
|
|
340
340
|
await serveCommand({
|
|
341
341
|
cwd,
|
|
342
342
|
branchId: args.branchId,
|
|
@@ -347,7 +347,7 @@ async function run(argv, options = {}) {
|
|
|
347
347
|
return 0;
|
|
348
348
|
}
|
|
349
349
|
case "studio": {
|
|
350
|
-
const { studioCommand } = await import("./studio-
|
|
350
|
+
const { studioCommand } = await import("./studio-HXVSO4WJ.js");
|
|
351
351
|
await studioCommand({
|
|
352
352
|
cwd,
|
|
353
353
|
branchId: args.branchId,
|
|
@@ -357,7 +357,7 @@ async function run(argv, options = {}) {
|
|
|
357
357
|
return 0;
|
|
358
358
|
}
|
|
359
359
|
case "content": {
|
|
360
|
-
const { contentListCommand, formatContentLine } = await import("./content-
|
|
360
|
+
const { contentListCommand, formatContentLine } = await import("./content-VLKNYIHL.js");
|
|
361
361
|
const { branch, lines } = await contentListCommand({ cwd, branchId: args.branchId });
|
|
362
362
|
if (lines.length === 0) {
|
|
363
363
|
console.log(`No content on branch "${branch}" (run graft compile).`);
|
|
@@ -369,7 +369,7 @@ async function run(argv, options = {}) {
|
|
|
369
369
|
return 0;
|
|
370
370
|
}
|
|
371
371
|
case "compilations": {
|
|
372
|
-
const { compilationsListCommand, formatCompilation } = await import("./compilations-
|
|
372
|
+
const { compilationsListCommand, formatCompilation } = await import("./compilations-72DJ2VEP.js");
|
|
373
373
|
const rows = await compilationsListCommand({ cwd, branchId: args.branchId });
|
|
374
374
|
if (rows.length === 0) {
|
|
375
375
|
console.log("No compilations recorded yet (run graft compile).");
|
|
@@ -381,7 +381,7 @@ async function run(argv, options = {}) {
|
|
|
381
381
|
case "harden": {
|
|
382
382
|
const role = args.positionals[0];
|
|
383
383
|
if (!role) throw new UsageError("usage: graft harden <role>");
|
|
384
|
-
const { hardenCommand } = await import("./harden-
|
|
384
|
+
const { hardenCommand } = await import("./harden-QASY2KMI.js");
|
|
385
385
|
const result = await hardenCommand({ cwd, role });
|
|
386
386
|
console.log(`Hardened runtime role "${result.role}" (${result.statements.length} grants):`);
|
|
387
387
|
for (const statement of result.statements) console.log(` ${statement}`);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
assertNoStaticBranch
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GDTOV3DU.js";
|
|
5
5
|
import "./chunk-GPA66AP5.js";
|
|
6
6
|
import {
|
|
7
7
|
findConfig,
|
|
8
8
|
loadConfig,
|
|
9
9
|
loadProjectEnv,
|
|
10
10
|
requireDatabaseUrl
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2NH6KYTL.js";
|
|
12
12
|
|
|
13
13
|
// src/commands/mcp.ts
|
|
14
14
|
import { createActorResolver } from "@usegraft/auth";
|
|
@@ -25,21 +25,25 @@ async function mcpCommand(options) {
|
|
|
25
25
|
try {
|
|
26
26
|
const devToken = process.env.GRAFT_DEV_TOKEN;
|
|
27
27
|
const scopes = (process.env.GRAFT_DEV_SCOPES ?? "").split(/[,\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
28
|
+
const cliActor = {
|
|
29
|
+
kind: "agent",
|
|
30
|
+
id: "graft-cli",
|
|
31
|
+
scopes: [.../* @__PURE__ */ new Set(["content:write", ...scopes])]
|
|
32
|
+
};
|
|
28
33
|
const resolveActor = createActorResolver({
|
|
29
34
|
issuers: [],
|
|
30
|
-
devTokens: devToken ? {
|
|
31
|
-
[devToken]: {
|
|
32
|
-
kind: "agent",
|
|
33
|
-
id: "graft-cli",
|
|
34
|
-
scopes
|
|
35
|
-
}
|
|
36
|
-
} : void 0
|
|
35
|
+
devTokens: devToken ? { [devToken]: cliActor } : void 0
|
|
37
36
|
});
|
|
37
|
+
const resolveStdioActor = async (request) => {
|
|
38
|
+
const resolved = await resolveActor(request);
|
|
39
|
+
return resolved.kind === "anonymous" ? cliActor : resolved;
|
|
40
|
+
};
|
|
38
41
|
const server = createGraftMcp({
|
|
39
42
|
name: "graft",
|
|
40
43
|
contentDir: config.contentDir,
|
|
41
44
|
collections: config.collections,
|
|
42
45
|
functions: config.functions,
|
|
46
|
+
mdxTrust: config.mdxTrust,
|
|
43
47
|
...branch !== void 0 ? {
|
|
44
48
|
db: branch.db,
|
|
45
49
|
branchId: scopeWriteBranch(branch.scope),
|
|
@@ -47,7 +51,11 @@ async function mcpCommand(options) {
|
|
|
47
51
|
// overlay branch searches its full parent chain, not just its own rows.
|
|
48
52
|
scope: branch.scope
|
|
49
53
|
} : { staticIndexPath: config.index.path },
|
|
50
|
-
actor:
|
|
54
|
+
actor: resolveStdioActor,
|
|
55
|
+
connectionActor: cliActor,
|
|
56
|
+
// Local agents may upload a file from the project; remote mounts get no
|
|
57
|
+
// `path` argument at all.
|
|
58
|
+
localUploadRoot: config.projectDir,
|
|
51
59
|
defaultAuthorization: devToken
|
|
52
60
|
});
|
|
53
61
|
await serveStdio(server);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
discoverMigrations
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-6Z27SGVO.js";
|
|
5
5
|
import {
|
|
6
6
|
findConfig,
|
|
7
7
|
loadConfig,
|
|
8
8
|
loadProjectEnv,
|
|
9
9
|
requireDatabaseUrl
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2NH6KYTL.js";
|
|
11
11
|
|
|
12
12
|
// src/commands/merge.ts
|
|
13
13
|
import { basename } from "path";
|
|
@@ -144,6 +144,7 @@ async function mergeCommand(options) {
|
|
|
144
144
|
db: target.db,
|
|
145
145
|
contentDir: config.contentDir,
|
|
146
146
|
collections: config.collections,
|
|
147
|
+
mdxTrust: config.mdxTrust,
|
|
147
148
|
branchId: targetWrite,
|
|
148
149
|
gitSha
|
|
149
150
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
allowedHostsFor,
|
|
3
4
|
createNodeListener,
|
|
4
5
|
createServeRouter,
|
|
5
6
|
serveCommand,
|
|
6
7
|
startServe
|
|
7
|
-
} from "./chunk-
|
|
8
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-U25NVB6W.js";
|
|
9
|
+
import "./chunk-2NH6KYTL.js";
|
|
9
10
|
export {
|
|
11
|
+
allowedHostsFor,
|
|
10
12
|
createNodeListener,
|
|
11
13
|
createServeRouter,
|
|
12
14
|
serveCommand,
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
allowedHostsFor,
|
|
3
4
|
createNodeListener
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-U25NVB6W.js";
|
|
5
6
|
import {
|
|
6
7
|
findConfig,
|
|
7
8
|
loadConfig,
|
|
8
9
|
loadProjectEnv,
|
|
9
10
|
requireDatabaseUrl
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-2NH6KYTL.js";
|
|
11
12
|
|
|
12
13
|
// src/commands/studio.ts
|
|
13
14
|
import { execFile } from "child_process";
|
|
@@ -17,11 +18,11 @@ import { GraftError } from "@usegraft/contracts";
|
|
|
17
18
|
function isLoopback(host) {
|
|
18
19
|
return host === "127.0.0.1" || host === "localhost" || host === "::1";
|
|
19
20
|
}
|
|
20
|
-
function
|
|
21
|
+
function operatorIdentity() {
|
|
21
22
|
try {
|
|
22
|
-
return userInfo().username;
|
|
23
|
+
return { kind: "human", id: userInfo().username };
|
|
23
24
|
} catch {
|
|
24
|
-
return process.env.USERNAME ?? process.env.USER ?? "operator";
|
|
25
|
+
return { kind: "human", id: process.env.USERNAME ?? process.env.USER ?? "operator" };
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
function openBrowser(url) {
|
|
@@ -58,19 +59,24 @@ async function studioCommand(options) {
|
|
|
58
59
|
"[graft studio] WARNING: binding beyond loopback with no GRAFT_DEV_TOKEN \u2014 set a bearer token before exposing Studio to a network."
|
|
59
60
|
);
|
|
60
61
|
}
|
|
61
|
-
const
|
|
62
|
+
const authenticate = !loopback && devToken ? (request) => {
|
|
62
63
|
const header = request.headers.get("authorization") ?? "";
|
|
63
|
-
|
|
64
|
-
|
|
64
|
+
if (header !== `Bearer ${devToken}`) return null;
|
|
65
|
+
return {
|
|
66
|
+
...operatorIdentity(),
|
|
67
|
+
scopes: ["studio:read", "studio:write", "approvals:decide"]
|
|
68
|
+
};
|
|
69
|
+
} : !loopback ? () => null : void 0;
|
|
65
70
|
const handler = createStudioHandler({
|
|
66
71
|
db: branch.db,
|
|
67
72
|
collections: config.collections,
|
|
68
73
|
contentDir: config.contentDir,
|
|
74
|
+
mdxTrust: config.mdxTrust,
|
|
69
75
|
defaultBranch: writeBranch,
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
decider: operatorIdentity,
|
|
77
|
+
authenticate
|
|
72
78
|
});
|
|
73
|
-
const server = createServer(createNodeListener(handler));
|
|
79
|
+
const server = createServer(createNodeListener(handler, { allowedHosts: allowedHostsFor(host) }));
|
|
74
80
|
await new Promise((resolve, reject) => {
|
|
75
81
|
server.once("error", reject);
|
|
76
82
|
server.listen(requestedPort, host, () => resolve());
|
package/package.json
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usegraft/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "The graft CLI: scaffold, compile authored content, serve the runtime, and split your database credentials.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agent",
|
|
7
|
+
"ai",
|
|
8
|
+
"cli",
|
|
9
|
+
"cms",
|
|
10
|
+
"content",
|
|
11
|
+
"graft",
|
|
12
|
+
"headless-cms",
|
|
13
|
+
"mcp",
|
|
14
|
+
"postgres",
|
|
15
|
+
"scaffolding",
|
|
16
|
+
"static-site",
|
|
17
|
+
"typescript"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://github.com/AndersonDesign1/graft#readme",
|
|
4
20
|
"license": "MIT",
|
|
5
21
|
"repository": {
|
|
6
22
|
"type": "git",
|
|
@@ -18,20 +34,21 @@
|
|
|
18
34
|
"access": "public"
|
|
19
35
|
},
|
|
20
36
|
"dependencies": {
|
|
21
|
-
"@usegraft/assets": "0.
|
|
22
|
-
"@usegraft/auth": "0.
|
|
23
|
-
"@usegraft/compiler": "0.
|
|
24
|
-
"@usegraft/content-migrations": "0.
|
|
25
|
-
"@usegraft/contracts": "0.
|
|
26
|
-
"@usegraft/core": "0.
|
|
27
|
-
"@usegraft/db": "0.
|
|
28
|
-
"@usegraft/mcp": "0.
|
|
29
|
-
"@usegraft/
|
|
30
|
-
"@usegraft/
|
|
37
|
+
"@usegraft/assets": "0.2.0",
|
|
38
|
+
"@usegraft/auth": "0.2.0",
|
|
39
|
+
"@usegraft/compiler": "0.2.0",
|
|
40
|
+
"@usegraft/content-migrations": "0.2.0",
|
|
41
|
+
"@usegraft/contracts": "0.2.0",
|
|
42
|
+
"@usegraft/core": "0.2.0",
|
|
43
|
+
"@usegraft/db": "0.2.0",
|
|
44
|
+
"@usegraft/mcp": "0.2.0",
|
|
45
|
+
"@usegraft/mdx-safety": "0.2.0",
|
|
46
|
+
"@usegraft/registry": "0.2.0",
|
|
47
|
+
"@usegraft/studio": "0.2.0",
|
|
31
48
|
"jiti": "^2.4.0"
|
|
32
49
|
},
|
|
33
50
|
"devDependencies": {
|
|
34
|
-
"@usegraft/sdk-core": "0.
|
|
51
|
+
"@usegraft/sdk-core": "0.2.0"
|
|
35
52
|
},
|
|
36
53
|
"engines": {
|
|
37
54
|
"node": ">=22.16"
|
|
@@ -40,7 +57,6 @@
|
|
|
40
57
|
"build": "tsup",
|
|
41
58
|
"dev": "tsup --watch",
|
|
42
59
|
"typecheck": "tsc --noEmit",
|
|
43
|
-
"test": "vitest run --passWithNoTests"
|
|
44
|
-
"lint": "oxlint ."
|
|
60
|
+
"test": "vitest run --passWithNoTests"
|
|
45
61
|
}
|
|
46
62
|
}
|