@montytools/cli 0.2.6 → 0.2.8
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/bin/monty.mjs +20 -3
- package/package.json +1 -1
- package/template/AGENTS.md +13 -1
- package/template/package.json +1 -1
package/bin/monty.mjs
CHANGED
|
@@ -489,6 +489,7 @@ async function dev() {
|
|
|
489
489
|
let hbTimer = null;
|
|
490
490
|
let publishing = false;
|
|
491
491
|
let ended = false;
|
|
492
|
+
const sessionId = `dev_${randomBytes(16).toString("hex")}`;
|
|
492
493
|
const buildFile = join(appDir, ".monty", "build");
|
|
493
494
|
const buildId = existsSync(buildFile) ? readFileSync(buildFile, "utf8").trim() : undefined;
|
|
494
495
|
// The DEV schema channel: heartbeats carry the compiled schema, and edits
|
|
@@ -519,7 +520,7 @@ async function dev() {
|
|
|
519
520
|
await fetch(`${host}/api/dev-session`, {
|
|
520
521
|
method: "POST",
|
|
521
522
|
headers: { authorization: `Bearer ${cfg.key}`, "content-type": "application/json" },
|
|
522
|
-
body: JSON.stringify({ slug: meta.slug, end: true }),
|
|
523
|
+
body: JSON.stringify({ slug: meta.slug, sessionId, end: true }),
|
|
523
524
|
signal: AbortSignal.timeout(timeoutMs),
|
|
524
525
|
});
|
|
525
526
|
} catch { /* best effort */ }
|
|
@@ -534,7 +535,17 @@ async function dev() {
|
|
|
534
535
|
await clearDevSession();
|
|
535
536
|
}
|
|
536
537
|
|
|
537
|
-
|
|
538
|
+
function stopSuperseded(fix) {
|
|
539
|
+
if (ended) return;
|
|
540
|
+
ended = true;
|
|
541
|
+
if (hbTimer) clearInterval(hbTimer);
|
|
542
|
+
try { tunnelChild?.kill(); } catch { /* already gone */ }
|
|
543
|
+
try { child.kill(); } catch { /* already gone */ }
|
|
544
|
+
console.log(`dev-session: superseded — ${fix}`);
|
|
545
|
+
setTimeout(() => process.exit(0), 50);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function heartbeat(originUrl, { claim = false } = {}) {
|
|
538
549
|
await refreshSchemaIfChanged();
|
|
539
550
|
try {
|
|
540
551
|
const r = await fetch(`${host}/api/dev-session`, {
|
|
@@ -543,6 +554,8 @@ async function dev() {
|
|
|
543
554
|
body: JSON.stringify({
|
|
544
555
|
slug: meta.slug,
|
|
545
556
|
tunnelUrl: originUrl,
|
|
557
|
+
sessionId,
|
|
558
|
+
claim,
|
|
546
559
|
name: currentMeta.name,
|
|
547
560
|
icon: currentMeta.icon,
|
|
548
561
|
buildId,
|
|
@@ -552,6 +565,10 @@ async function dev() {
|
|
|
552
565
|
});
|
|
553
566
|
const data = await r.json().catch(() => null);
|
|
554
567
|
if (!r.ok) {
|
|
568
|
+
if (data?.code === "DEV_SESSION_SUPERSEDED") {
|
|
569
|
+
stopSuperseded(data.fix ?? "A newer `monty dev` session is active for this app.");
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
555
572
|
console.log(`dev-session: ${data?.code ?? r.status}${data?.fix ? ` — ${data.fix}` : ""}`);
|
|
556
573
|
return false;
|
|
557
574
|
}
|
|
@@ -633,7 +650,7 @@ async function dev() {
|
|
|
633
650
|
console.log("tunnel: unavailable — dev mode registered on localhost (visible on this machine's browser only)");
|
|
634
651
|
}
|
|
635
652
|
}
|
|
636
|
-
const registered = await heartbeat(originUrl);
|
|
653
|
+
const registered = await heartbeat(originUrl, { claim: true });
|
|
637
654
|
console.log(
|
|
638
655
|
registered
|
|
639
656
|
? `studio: ${host}/studio/${meta.slug} — your app is live there while this runs; click Publish to ship`
|
package/package.json
CHANGED
package/template/AGENTS.md
CHANGED
|
@@ -39,6 +39,9 @@ Folders are managed for you: this app lives in `~/Monty/<slug>`. `monty current`
|
|
|
39
39
|
|
|
40
40
|
```ts
|
|
41
41
|
// monty.config.ts
|
|
42
|
+
import { defineApp, montyFileSchema } from "@montytools/sdk";
|
|
43
|
+
import { z } from "zod";
|
|
44
|
+
|
|
42
45
|
export const app = defineApp({
|
|
43
46
|
slug: "expenses",
|
|
44
47
|
tables: {
|
|
@@ -47,13 +50,14 @@ export const app = defineApp({
|
|
|
47
50
|
amount: z.number().positive(),
|
|
48
51
|
status: z.enum(["draft", "submitted", "approved"]).default("draft"),
|
|
49
52
|
assigneeId: z.string().optional(), // userId from useMembers()
|
|
53
|
+
receipt: montyFileSchema.optional(),
|
|
50
54
|
}),
|
|
51
55
|
},
|
|
52
56
|
});
|
|
53
57
|
```
|
|
54
58
|
|
|
55
59
|
```tsx
|
|
56
|
-
import { useList, useRecord, useInsert, useUpdate, useRemove, useMembers } from "@montytools/sdk/react";
|
|
60
|
+
import { useList, useRecord, useInsert, useUpdate, useRemove, useMembers, useUploadFile, useFileUrl } from "@montytools/sdk/react";
|
|
57
61
|
import { app } from "../../monty.config";
|
|
58
62
|
|
|
59
63
|
const { data, status, loadMore } = useList(app, "expenses", {
|
|
@@ -81,6 +85,11 @@ await remove(row._id);
|
|
|
81
85
|
|
|
82
86
|
const one = useRecord(app, "expenses", idOrNull); // row | null(missing or no selection) | undefined(loading)
|
|
83
87
|
const members = useMembers(); // [{ userId, name, email, imageUrl, role }]
|
|
88
|
+
|
|
89
|
+
const uploadFile = useUploadFile(app); // File/Blob -> MontyFile descriptor
|
|
90
|
+
const receipt = await uploadFile(input.files[0]);
|
|
91
|
+
await update(row._id, { receipt }); // store descriptor, not bytes
|
|
92
|
+
const { url } = useFileUrl(app, row.receipt); // authenticated blob: URL for previews/downloads
|
|
84
93
|
```
|
|
85
94
|
|
|
86
95
|
## Errors are instructions
|
|
@@ -97,6 +106,7 @@ Every platform error is one line shaped like:
|
|
|
97
106
|
| `NOT_FOUND` | Stale, foreign, or WRONG-TABLE record id — ids come from `useList`/`useRecord` on the same table; never hard-code or mix them. |
|
|
98
107
|
| `INVALID_SCHEMA` | A table field uses a reserved name (`_*`, `updatedAt`, `createdBy`) — rename it. |
|
|
99
108
|
| `SCHEMA_DRIFT` (warning) | Stored rows predate your latest schema change; nothing crashes, but make changed fields `.optional()`/`.default(...)`. |
|
|
109
|
+
| `FILE_TOO_LARGE` | Platform file uploads are currently capped at 10 MiB per file. |
|
|
100
110
|
| `UNAUTHENTICATED` / `NO_ACTIVE_WORKSPACE` | App isn't running through the Monty host/dev shell. |
|
|
101
111
|
| `MISSING_ENV` / `NO_PROVIDER` | `.env.local` or the `<MontyProvider>` in `main.tsx` was removed. |
|
|
102
112
|
|
|
@@ -136,6 +146,8 @@ confirms it.
|
|
|
136
146
|
|
|
137
147
|
- Rows, not embedded arrays: a comments thread is a `comments` table with a
|
|
138
148
|
`parentId` field, not an array inside a record (records cap at 1 MiB).
|
|
149
|
+
- Files are `montyFileSchema` descriptors in records; bytes live in platform
|
|
150
|
+
storage and are previewed with `useFileUrl(app, file)`.
|
|
139
151
|
- People are `z.string()` userIds from `useMembers()`; render names/avatars
|
|
140
152
|
from the member list.
|
|
141
153
|
- Timestamps are `z.number()` epochs or `z.iso.datetime()` strings — never
|
package/template/package.json
CHANGED