makersclaw 0.7.0 → 0.8.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 +8 -1
- package/package.json +1 -1
- package/r/annotate.json +4 -4
- package/r/approval-request.json +2 -2
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +5 -5
- package/r/chat.json +3 -3
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/contact-section.json +2 -2
- package/r/core.json +7 -1
- package/r/csv-import.json +3 -3
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/flow-canvas.json +3 -3
- package/r/footer.json +3 -3
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/map.json +2 -2
- package/r/menubar.json +2 -2
- package/r/mind-map.json +4 -4
- package/r/navigation-menu.json +2 -2
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +4 -4
- package/r/page-builder.json +3 -3
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +3 -3
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-notes.json +4 -4
- package/r/registry.json +126 -121
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +3 -3
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +3 -3
- package/r/spreadsheet.json +2 -2
- package/r/stat-cards.json +3 -3
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/transaction-list.json +2 -2
- package/r/whiteboard.json +4 -4
- package/registry/blocks/annotate/README.md +14 -11
- package/registry/blocks/chart/cartesian.jsx +1 -1
- package/registry/blocks/chart/core.jsx +33 -23
- package/registry/blocks/chart/pie.jsx +1 -1
- package/registry/blocks/chat/README.md +4 -1
- package/registry/blocks/chat/demo.jsx +2 -2
- package/registry/blocks/flow-canvas/README.md +15 -8
- package/registry/blocks/footer/README.md +3 -2
- package/registry/blocks/mind-map/README.md +4 -2
- package/registry/blocks/org-chart/README.md +4 -2
- package/registry/blocks/page-builder/README.md +1 -1
- package/registry/blocks/page-builder/demo.jsx +3 -3
- package/registry/blocks/pdf-viewer/README.md +4 -1
- package/registry/blocks/sidebar/README.md +23 -13
- package/registry/blocks/sketch-canvas/README.md +15 -9
- package/registry/blocks/whiteboard/README.md +20 -14
- package/src/cli.mjs +2 -2
- package/src/install.mjs +23 -6
- package/src/registry.mjs +34 -3
- package/templates/next/files/README.md +291 -11
- package/templates/next/files/app/shell.jsx +54 -37
- package/templates/next/files/check/run.mjs +76 -4
- package/templates/next/files/components/ui/index.js +1 -0
- package/templates/next/files/components/ui/layout.jsx +307 -0
- package/templates/next/files/lib/claw.js +4 -1
- package/templates/next/files/lib/hooks.js +71 -2
- package/templates/next/files/lib/platform.js +93 -4
- package/templates/next/template.json +10 -1
|
@@ -127,7 +127,10 @@ not run.
|
|
|
127
127
|
has no `author`.
|
|
128
128
|
- `emptyTitle` · `emptyBody` · `emptyState` — what an empty thread says;
|
|
129
129
|
`emptyState` replaces the whole thing.
|
|
130
|
-
- `height` — default `"min(40rem, 80dvh)"
|
|
130
|
+
- `height` — default `"min(40rem, 80dvh)"` (the block's own); the list
|
|
131
|
+
scrolls inside it. Yours is a length (`"40rem"`) or `"100%"` in a sized
|
|
132
|
+
box, such as the kit's `AppLayout contentMode="fill"`: a `vh` length in
|
|
133
|
+
your code is refused by `npm run check`.
|
|
131
134
|
- `label` — what the thread is called to a screen reader.
|
|
132
135
|
- `className` — layout around it (width, margin). Never colour.
|
|
133
136
|
|
|
@@ -233,7 +233,7 @@ function SupportDemo() {
|
|
|
233
233
|
emptyTitle="No messages yet"
|
|
234
234
|
emptyBody="Ask about billing, your workspace or an invoice — Ada answers in a few seconds."
|
|
235
235
|
label="Support chat"
|
|
236
|
-
height="
|
|
236
|
+
height="44rem"
|
|
237
237
|
/>
|
|
238
238
|
</div>
|
|
239
239
|
);
|
|
@@ -412,7 +412,7 @@ function AssistantDemo() {
|
|
|
412
412
|
emptyTitle="What should we look at?"
|
|
413
413
|
emptyBody="Pip reads your orders, stock and returns. Pick a question or ask your own."
|
|
414
414
|
label="Planning assistant"
|
|
415
|
-
height="
|
|
415
|
+
height="40rem"
|
|
416
416
|
/>
|
|
417
417
|
</div>
|
|
418
418
|
);
|
|
@@ -111,7 +111,10 @@ re-route as cards move; handle ids are not stored.
|
|
|
111
111
|
screen). `onExitPresentation()` — Esc and the bar's Exit call it; turn
|
|
112
112
|
`presentation` off there.
|
|
113
113
|
- `height` — the canvas's height, default `560` (px; any CSS length works:
|
|
114
|
-
`"
|
|
114
|
+
`"100%"` inside a sized box, such as the kit's `AppLayout
|
|
115
|
+
contentMode="fill"` for a board that is the whole surface, the
|
|
116
|
+
Example). Never a `vh` length: `npm run check` refuses a size taken
|
|
117
|
+
from the window.
|
|
115
118
|
- `bare` — `false` (default: rounded, bordered). `true`: no border or
|
|
116
119
|
radius, for a full-page board.
|
|
117
120
|
- `label` — the board's accessible name, default `"Board"`; also the list
|
|
@@ -370,6 +373,7 @@ import { useState } from "react";
|
|
|
370
373
|
import { useSearchParams } from "next/navigation";
|
|
371
374
|
import { useRow } from "../lib/data";
|
|
372
375
|
import { updateRow } from "../lib/actions";
|
|
376
|
+
import { AppLayout } from "../components/ui";
|
|
373
377
|
import { FlowCanvas } from "../components/blocks/flow-canvas";
|
|
374
378
|
|
|
375
379
|
export default function LaunchSurface() {
|
|
@@ -382,13 +386,16 @@ export default function LaunchSurface() {
|
|
|
382
386
|
function LaunchBoard({ project }) {
|
|
383
387
|
const [board, setBoard] = useState(project.board); // jsonb, may be null; read once per project
|
|
384
388
|
return (
|
|
385
|
-
<
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
389
|
+
<AppLayout contentMode="fill">
|
|
390
|
+
<FlowCanvas
|
|
391
|
+
value={board ?? undefined}
|
|
392
|
+
onChange={setBoard}
|
|
393
|
+
onSave={(b) => updateRow({ source: "projects", id: project.id, patch: { board: b } })}
|
|
394
|
+
label={`${project.name} board`}
|
|
395
|
+
height="100%"
|
|
396
|
+
bare
|
|
397
|
+
/>
|
|
398
|
+
</AppLayout>
|
|
392
399
|
);
|
|
393
400
|
}
|
|
394
401
|
```
|
|
@@ -117,8 +117,9 @@ In a server-mode app the same calls are `loadRows` in a server surface and the S
|
|
|
117
117
|
|
|
118
118
|
- Change the language or currency by itself: `locales[].onChange` is
|
|
119
119
|
yours.
|
|
120
|
-
- Stick to the bottom of a short page: that is your layout's
|
|
121
|
-
`
|
|
120
|
+
- Stick to the bottom of a short page: that is your layout's. The kit's
|
|
121
|
+
`AppLayout` content is a flex column, so a `flex-1` above the footer
|
|
122
|
+
pushes it down on a short page and lets it follow a long one.
|
|
122
123
|
- Paint itself in the primary colour: the look is the workspace
|
|
123
124
|
theme's.
|
|
124
125
|
- Draw store badges: "Get the app" is two buttons with the Apple and
|
|
@@ -96,7 +96,9 @@ string, or nothing (a fresh map). The component runs it on `value`.
|
|
|
96
96
|
- `placeholder` — what an empty topic shows while it is typed (default
|
|
97
97
|
"New topic").
|
|
98
98
|
- `height` — px or any CSS length, default `600`. A height class in
|
|
99
|
-
`className` (`h-
|
|
99
|
+
`className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`
|
|
100
|
+
fills what the surface has left (the Example). Never `h-dvh` or a `vh`
|
|
101
|
+
height, which `npm run check` refuses.
|
|
100
102
|
- `label` — the map's accessible name (default "Mind map").
|
|
101
103
|
- `onSelectionChange(topic | null)` — the selected topic changed (a side
|
|
102
104
|
panel with its notes).
|
|
@@ -186,7 +188,7 @@ function PlanMap({ plan }) {
|
|
|
186
188
|
onChange={setMap}
|
|
187
189
|
onSave={(m) => updateRow({ source: "plans", id: plan.id, patch: { map: m, map_text: mindMapToText(m) } })}
|
|
188
190
|
label={`${plan.name} map`}
|
|
189
|
-
className="h-
|
|
191
|
+
className="min-h-0 flex-1"
|
|
190
192
|
/>
|
|
191
193
|
);
|
|
192
194
|
}
|
|
@@ -73,7 +73,9 @@ One row per person, as a table holds them:
|
|
|
73
73
|
reports: kit parts (their open deals, a "Message" button).
|
|
74
74
|
- `onSelectionChange(person | null)` — the selected card changed.
|
|
75
75
|
- `height` — px or any CSS length, default `640`. A height class in
|
|
76
|
-
`className` (`h-
|
|
76
|
+
`className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`
|
|
77
|
+
fills what the surface has left (the Example). Never `h-dvh` or a `vh`
|
|
78
|
+
height, which `npm run check` refuses.
|
|
77
79
|
- `label` — the chart's accessible name (default "Org chart").
|
|
78
80
|
- `className` — layout around it (margin, width, height). Never colour.
|
|
79
81
|
|
|
@@ -124,7 +126,7 @@ export default function TeamChart() {
|
|
|
124
126
|
if (res.ok) await whenLanded(); // the people are read again: the chart draws the new manager
|
|
125
127
|
return res;
|
|
126
128
|
}}
|
|
127
|
-
className="h-
|
|
129
|
+
className="min-h-0 flex-1"
|
|
128
130
|
/>
|
|
129
131
|
);
|
|
130
132
|
}
|
|
@@ -105,7 +105,7 @@ variables the app's Tailwind utilities read. The built-in blocks' CSS
|
|
|
105
105
|
| `actions` | — | Extra top-bar controls before Save, such as landing-builder's Publish. Use kit `Button`s at `size="sm"`. |
|
|
106
106
|
| `saveLabel` | `"Save"` | The Save button's words. |
|
|
107
107
|
| `builderRef` | — | A ref from `usePageBuilder()`. It receives the handle. |
|
|
108
|
-
| `height` | `720` | The builder's height: a number (px) or any CSS length (`"
|
|
108
|
+
| `height` | `720` | The builder's height: a number (px) or any CSS length (`"100%"` in a sized box, such as the kit's `AppLayout contentMode="fill"`; never a `vh` length, which `npm run check` refuses). It fills its width. |
|
|
109
109
|
| `className` | — | Added to the frame. |
|
|
110
110
|
|
|
111
111
|
### The handle
|
|
@@ -78,7 +78,7 @@ export default function PageBuilderDemo() {
|
|
|
78
78
|
<Landing />
|
|
79
79
|
</TabPanel>
|
|
80
80
|
<TabPanel tab="fresh" value={tab}>
|
|
81
|
-
<PageBuilder title="New page" height=
|
|
81
|
+
<PageBuilder title="New page" height={720} />
|
|
82
82
|
</TabPanel>
|
|
83
83
|
<TabPanel tab="own" value={tab}>
|
|
84
84
|
<PageBuilder
|
|
@@ -86,7 +86,7 @@ export default function PageBuilderDemo() {
|
|
|
86
86
|
builtIns="basics"
|
|
87
87
|
blocks={OWN_BLOCKS}
|
|
88
88
|
templates={OWN_TEMPLATES}
|
|
89
|
-
height=
|
|
89
|
+
height={720}
|
|
90
90
|
onSave={async () => {
|
|
91
91
|
await new Promise((r) => setTimeout(r, 400));
|
|
92
92
|
toast.success("Page saved");
|
|
@@ -122,7 +122,7 @@ function Landing() {
|
|
|
122
122
|
page={{ html: LAUNCH.html }}
|
|
123
123
|
onSave={onSave}
|
|
124
124
|
builderRef={builder}
|
|
125
|
-
height=
|
|
125
|
+
height={760}
|
|
126
126
|
onUploadImage={async (file) => {
|
|
127
127
|
await new Promise((r) => setTimeout(r, 500));
|
|
128
128
|
// In an app: upload to storage and return its public address.
|
|
@@ -108,7 +108,10 @@ Store a highlight as a row in its own table (`document_id`, `page`,
|
|
|
108
108
|
- `notes` — open the Highlights list on a wide viewer at first. Default:
|
|
109
109
|
open when there are highlights.
|
|
110
110
|
- `height` — the viewer's height, any CSS length. Default
|
|
111
|
-
`"min(80vh, 860px)"
|
|
111
|
+
`"min(80vh, 860px)"` (the block's own); it never goes under 384px.
|
|
112
|
+
Yours is a length, or `"100%"` in a sized box such as the kit's
|
|
113
|
+
`AppLayout contentMode="fill"`: a `vh` length in your code is refused
|
|
114
|
+
by `npm run check`.
|
|
112
115
|
- `loading` — `true` shows the skeleton (while you fetch or make the
|
|
113
116
|
file) instead of the empty state.
|
|
114
117
|
- `emptyTitle` · `emptyBody` — the words when `file` is empty. Default
|
|
@@ -57,8 +57,11 @@ true) is where it starts. The icon rail shows every group unfolded.
|
|
|
57
57
|
- `shortcut` — Cmd/Ctrl + this key toggles it; default `"b"`, `null` for none.
|
|
58
58
|
- `variant` — `"inset"` draws the page as a card on the frame's ground.
|
|
59
59
|
Default: the Sidebar's own `variant`, so you rarely pass it here.
|
|
60
|
-
- `className` — the frame's
|
|
61
|
-
|
|
60
|
+
- `className` — the frame's edges, and its height from its parent: in a
|
|
61
|
+
shell it sits in the kit's `AppLayout contentMode="fill"` with
|
|
62
|
+
`min-h-0 flex-1` (the Example). The kit's `AppLayout` owns the
|
|
63
|
+
window's height; `min-h-dvh` or `h-dvh` here is refused by `npm run
|
|
64
|
+
check`. Never colour.
|
|
62
65
|
|
|
63
66
|
### Sidebar
|
|
64
67
|
|
|
@@ -138,6 +141,7 @@ The header button: opens the sheet on a phone, collapses to icons from
|
|
|
138
141
|
// app/shell.jsx — the app's shell, the surfaces as the sidebar's items
|
|
139
142
|
"use client";
|
|
140
143
|
import { usePathname } from "next/navigation";
|
|
144
|
+
import { AppLayout } from "../components/ui";
|
|
141
145
|
import { Sidebar, SidebarLayout, SidebarTrigger } from "../components/blocks/sidebar";
|
|
142
146
|
|
|
143
147
|
const ICONS = { inbox: "inbox", deals: "dollar", contacts: "users", reports: "chart" };
|
|
@@ -153,18 +157,24 @@ export default function Shell({ name, surfaces, children }) {
|
|
|
153
157
|
active: s.key === activeKey,
|
|
154
158
|
badge: s.built ? undefined : "planned",
|
|
155
159
|
}));
|
|
160
|
+
// The outer AppLayout owns the window; the inner one (it fills its
|
|
161
|
+
// slot) is the content that scrolls, back to its top on a new route.
|
|
156
162
|
return (
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
<AppLayout contentMode="fill">
|
|
164
|
+
<SidebarLayout
|
|
165
|
+
storageKey={`${name}.sidebar`}
|
|
166
|
+
className="min-h-0 flex-1"
|
|
167
|
+
sidebar={<Sidebar title={name} items={items} label={`${name} surfaces`} />}
|
|
168
|
+
>
|
|
169
|
+
<header className="flex min-h-14 items-center gap-2 border-b border-border px-2 md:hidden">
|
|
170
|
+
<SidebarTrigger />
|
|
171
|
+
<span className="font-semibold">{name}</span>
|
|
172
|
+
</header>
|
|
173
|
+
<AppLayout contentClassName="p-4 sm:p-6" scrollKey={pathname}>
|
|
174
|
+
{children}
|
|
175
|
+
</AppLayout>
|
|
176
|
+
</SidebarLayout>
|
|
177
|
+
</AppLayout>
|
|
168
178
|
);
|
|
169
179
|
}
|
|
170
180
|
```
|
|
@@ -130,8 +130,11 @@ A sketch is plain JSON. Store it as it comes out of `onChange` / `onSave`
|
|
|
130
130
|
- `onReady(handle)` — once, when the canvas has mounted.
|
|
131
131
|
- `label` — the accessible name of the canvas region (default "Sketch").
|
|
132
132
|
- `className` — layout: width, margin, height. The default height is
|
|
133
|
-
`h-[32rem]`; a height class of yours (`h-
|
|
134
|
-
|
|
133
|
+
`h-[32rem]`; a height class of yours (`h-full`, `min-h-…`, `min-h-0
|
|
134
|
+
flex-1`) replaces it. A canvas that is the whole surface sits in an
|
|
135
|
+
`AppLayout contentMode="fill"` from the kit with `min-h-0 flex-1` (the
|
|
136
|
+
Example): never `h-dvh` or a `vh` height, which `npm run check`
|
|
137
|
+
refuses. Never colour.
|
|
135
138
|
|
|
136
139
|
### The handle
|
|
137
140
|
|
|
@@ -220,6 +223,7 @@ A whiteboard that saves itself:
|
|
|
220
223
|
import { useSearchParams } from "next/navigation";
|
|
221
224
|
import { useRow } from "../lib/data";
|
|
222
225
|
import { updateRow } from "../lib/actions";
|
|
226
|
+
import { AppLayout } from "../components/ui";
|
|
223
227
|
import { SketchCanvas } from "../components/blocks/sketch-canvas";
|
|
224
228
|
|
|
225
229
|
export default function Board() {
|
|
@@ -227,13 +231,15 @@ export default function Board() {
|
|
|
227
231
|
const { row: board, loading } = useRow({ source: "boards", id });
|
|
228
232
|
if (loading || !board) return null;
|
|
229
233
|
return (
|
|
230
|
-
<
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
234
|
+
<AppLayout contentMode="fill">
|
|
235
|
+
<SketchCanvas
|
|
236
|
+
key={board.id}
|
|
237
|
+
label={board.title}
|
|
238
|
+
defaultValue={board.sketch} // jsonb, may be null; read once per board
|
|
239
|
+
onSave={(sketch) => updateRow({ source: "boards", id: board.id, patch: { sketch } })}
|
|
240
|
+
className="min-h-0 flex-1 rounded-none border-0"
|
|
241
|
+
/>
|
|
242
|
+
</AppLayout>
|
|
237
243
|
);
|
|
238
244
|
}
|
|
239
245
|
```
|
|
@@ -93,8 +93,11 @@ for agents.
|
|
|
93
93
|
receives the canvas handle: `exportImage()`, `getScene()`,
|
|
94
94
|
`insertElements()`… (sketch-canvas README, "The handle").
|
|
95
95
|
- `className` — layout: width, margin, height. The default height is
|
|
96
|
-
`h-[40rem]`; a height class of yours (`h-
|
|
97
|
-
|
|
96
|
+
`h-[40rem]`; a height class of yours (`h-full`, `min-h-0 flex-1`)
|
|
97
|
+
replaces it. A board that is the whole surface sits in an `AppLayout
|
|
98
|
+
contentMode="fill"` from the kit with `min-h-0 flex-1` (the Example):
|
|
99
|
+
never `h-dvh` or a `vh` height, which `npm run check` refuses. Never
|
|
100
|
+
colour.
|
|
98
101
|
|
|
99
102
|
### Templates
|
|
100
103
|
|
|
@@ -118,6 +121,7 @@ A board page that saves itself:
|
|
|
118
121
|
import { useSearchParams } from "next/navigation";
|
|
119
122
|
import { useRow, whenLanded } from "../lib/data";
|
|
120
123
|
import { updateRow } from "../lib/actions";
|
|
124
|
+
import { AppLayout } from "../components/ui";
|
|
121
125
|
import { Whiteboard } from "../components/blocks/whiteboard";
|
|
122
126
|
import { sketchToText } from "../components/blocks/sketch-canvas";
|
|
123
127
|
|
|
@@ -126,18 +130,20 @@ export default function BoardPage() {
|
|
|
126
130
|
const { row: board, loading } = useRow({ source: "boards", id });
|
|
127
131
|
if (loading || !board) return null;
|
|
128
132
|
return (
|
|
129
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
133
|
+
<AppLayout contentMode="fill">
|
|
134
|
+
<Whiteboard
|
|
135
|
+
key={board.id}
|
|
136
|
+
title={board.title}
|
|
137
|
+
onRename={async (next) => {
|
|
138
|
+
const res = await updateRow({ source: "boards", id: board.id, patch: { title: next } });
|
|
139
|
+
if (res.ok) await whenLanded(); // the board is read again: the title is the new one
|
|
140
|
+
return res;
|
|
141
|
+
}}
|
|
142
|
+
defaultValue={board.sketch} // jsonb, may be null; read once per board
|
|
143
|
+
onSave={(sketch) => updateRow({ source: "boards", id: board.id, patch: { sketch, sketch_text: sketchToText(sketch) } })}
|
|
144
|
+
className="min-h-0 flex-1 rounded-none border-0"
|
|
145
|
+
/>
|
|
146
|
+
</AppLayout>
|
|
141
147
|
);
|
|
142
148
|
}
|
|
143
149
|
```
|
package/src/cli.mjs
CHANGED
|
@@ -107,7 +107,7 @@ const listLine = (paths, n = 6) => (paths.length > n ? `${paths.slice(0, n).join
|
|
|
107
107
|
|
|
108
108
|
function packageLines(changes) {
|
|
109
109
|
if (!changes.length) return [];
|
|
110
|
-
return [` package.json: ${changes.map((c) => `${c.name} ${c.from ?? "(new)"} → ${c.to}`).join(" · ")}`];
|
|
110
|
+
return [` package.json: ${changes.map((c) => `${c.name} ${c.from ?? "(new)"} → ${c.to ?? "(removed)"}`).join(" · ")}`];
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
/** The lock's line, when it was written. What it left out is a note. */
|
|
@@ -230,7 +230,7 @@ function diffText(r) {
|
|
|
230
230
|
return lines.join("\n");
|
|
231
231
|
}
|
|
232
232
|
for (const e of r.entries) lines.push(` ${e.item.padEnd(14)} ${e.status.padEnd(8)} ${e.path}${e.detail ? ` — ${e.detail}` : ""}`);
|
|
233
|
-
for (const c of r.package) lines.push(` ${"package.json".padEnd(14)} ${c.key}.${c.name}: ${c.from ?? "(new)"} → ${c.to}`);
|
|
233
|
+
for (const c of r.package) lines.push(` ${"package.json".padEnd(14)} ${c.key}.${c.name}: ${c.from ?? "(new)"} → ${c.to ?? "(removed)"}`);
|
|
234
234
|
lines.push(`next: npx makersclaw update${r.compatible ? "" : " --major"}`);
|
|
235
235
|
return lines.join("\n");
|
|
236
236
|
}
|
package/src/install.mjs
CHANGED
|
@@ -28,7 +28,7 @@ import { join } from "node:path";
|
|
|
28
28
|
import { hashFile, readJson, removeFile, sha256, walk, writeFile, writeJson } from "./fsutil.mjs";
|
|
29
29
|
import { LOCKFILE, LockError, deriveLock, parseLock } from "./lockfile.mjs";
|
|
30
30
|
import { NPM_LOCKFILE, PNPM_VERSION, installDeps } from "./pm.mjs";
|
|
31
|
-
import { BASE_MODE, BLOCK_DIR, loadBlocks, loadItems, loadTemplate, modeFiles, packageInfo, readmeOf, suggest, templateNames } from "./registry.mjs";
|
|
31
|
+
import { BASE_MODE, BLOCK_DIR, loadBlocks, loadItems, loadTemplate, modeDropScripts, modeFiles, modePackage, packageInfo, readmeOf, suggest, templateNames } from "./registry.mjs";
|
|
32
32
|
|
|
33
33
|
export const MANIFEST = "makersclaw.json";
|
|
34
34
|
export const PINS = "check/pins.json";
|
|
@@ -231,8 +231,11 @@ function loadTemplateOrFail(name) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/** package.json: the template's scripts and dependencies win for the keys
|
|
234
|
-
* it names; every other key is the app's.
|
|
235
|
-
|
|
234
|
+
* it names; every other key is the app's. `dropScripts` are the scripts
|
|
235
|
+
* the app's mode has none of (registry modeDropScripts): removed whatever
|
|
236
|
+
* they say, so a server app switched to browser loses its `start`; a
|
|
237
|
+
* removal is a change `to: null`. */
|
|
238
|
+
function mergePackage(app, tpl, dropScripts = []) {
|
|
236
239
|
const out = { ...app };
|
|
237
240
|
const changes = [];
|
|
238
241
|
for (const key of ["scripts", "dependencies", "devDependencies"]) {
|
|
@@ -244,6 +247,12 @@ function mergePackage(app, tpl) {
|
|
|
244
247
|
out[key][name] = value;
|
|
245
248
|
}
|
|
246
249
|
}
|
|
250
|
+
for (const name of dropScripts) {
|
|
251
|
+
if (out.scripts?.[name] === undefined) continue;
|
|
252
|
+
out.scripts = { ...out.scripts };
|
|
253
|
+
changes.push({ key: "scripts", name, from: out.scripts[name], to: null });
|
|
254
|
+
delete out.scripts[name];
|
|
255
|
+
}
|
|
247
256
|
return { out, changes };
|
|
248
257
|
}
|
|
249
258
|
|
|
@@ -346,8 +355,10 @@ function lay(dir, tpl, before, { tables, mode } = {}) {
|
|
|
346
355
|
kept.push(f.path);
|
|
347
356
|
continue;
|
|
348
357
|
}
|
|
358
|
+
// package.json is the mode's (registry modePackage): a browser app's
|
|
359
|
+
// has no start script, whether it is laid new or merged.
|
|
349
360
|
if (f.class === "merged" && existsSync(to)) {
|
|
350
|
-
const { out, changes } = mergePackage(readJson(to),
|
|
361
|
+
const { out, changes } = mergePackage(readJson(to), modePackage(tpl, appMode), modeDropScripts(tpl, appMode));
|
|
351
362
|
if (changes.length) {
|
|
352
363
|
writeJson(to, out);
|
|
353
364
|
written.push(f.path);
|
|
@@ -355,6 +366,11 @@ function lay(dir, tpl, before, { tables, mode } = {}) {
|
|
|
355
366
|
pkg = changes;
|
|
356
367
|
continue;
|
|
357
368
|
}
|
|
369
|
+
if (f.class === "merged") {
|
|
370
|
+
writeJson(to, modePackage(tpl, appMode));
|
|
371
|
+
written.push(f.path);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
358
374
|
const bytes = readFileSync(f.from);
|
|
359
375
|
const hash = sha256(bytes);
|
|
360
376
|
if (hashFile(to) !== hash) {
|
|
@@ -768,13 +784,14 @@ export function diff(dir) {
|
|
|
768
784
|
|
|
769
785
|
const pkgPath = join(dir, "package.json");
|
|
770
786
|
const tplPkg = tpl.files.find((f) => f.class === "merged");
|
|
771
|
-
const
|
|
787
|
+
const drop = modeDropScripts(tpl, appMode);
|
|
788
|
+
const pkg = existsSync(pkgPath) && tplPkg ? mergePackage(readJson(pkgPath), modePackage(tpl, appMode), drop).changes : [];
|
|
772
789
|
// …and the installed blocks' npm packages, which update lays with them.
|
|
773
790
|
const blockDeps = Object.assign({}, ...Object.keys(manifest.blocks ?? {}).map((n) => known.find((b) => b.name === n)?.dependencies ?? {}));
|
|
774
791
|
if (existsSync(pkgPath) && Object.keys(blockDeps).length) pkg.push(...mergePackage(readJson(pkgPath), { dependencies: blockDeps }).changes);
|
|
775
792
|
// …and the lock update would cut for the package.json it would leave.
|
|
776
793
|
if (existsSync(pkgPath) && tplPkg) {
|
|
777
|
-
const after = mergePackage(mergePackage(readJson(pkgPath),
|
|
794
|
+
const after = mergePackage(mergePackage(readJson(pkgPath), modePackage(tpl, appMode), drop).out, { dependencies: blockDeps }).out;
|
|
778
795
|
const cut = cutLock(dir, tpl, after);
|
|
779
796
|
if (cut && cut.text !== cut.before) entries.push({ item: "template", path: LOCKFILE, status: cut.before === null ? "new" : "changed" });
|
|
780
797
|
}
|
package/src/registry.mjs
CHANGED
|
@@ -81,8 +81,10 @@ export const BASE_MODE = "server";
|
|
|
81
81
|
* - Each dropped file is a fixed file of the base: a drop the base does
|
|
82
82
|
* not ship drops nothing, without a word, and only a pinned file is
|
|
83
83
|
* the template's to remove from an app.
|
|
84
|
+
* - Each dropped script (`dropScripts`, 0.8.0) is a script of the base's
|
|
85
|
+
* package.json (`scripts`, its names), for the same reason.
|
|
84
86
|
*/
|
|
85
|
-
export function checkMode(template, mode, { base, overlay, drop }) {
|
|
87
|
+
export function checkMode(template, mode, { base, overlay, drop, scripts = [], dropScripts = [] }) {
|
|
86
88
|
const fixed = new Set(base.filter((f) => f.class === "fixed").map((f) => f.path));
|
|
87
89
|
const dropped = new Set(drop);
|
|
88
90
|
for (const path of overlay) {
|
|
@@ -94,6 +96,9 @@ export function checkMode(template, mode, { base, overlay, drop }) {
|
|
|
94
96
|
for (const path of drop) {
|
|
95
97
|
if (!fixed.has(path)) throw new Error(`template ${template}: mode ${mode} drops ${path}, which is not a fixed file of files/ — it would drop nothing`);
|
|
96
98
|
}
|
|
99
|
+
for (const name of dropScripts) {
|
|
100
|
+
if (!scripts.includes(name)) throw new Error(`template ${template}: mode ${mode} drops the script ${name}, which files/package.json has not — it would drop nothing`);
|
|
101
|
+
}
|
|
97
102
|
}
|
|
98
103
|
|
|
99
104
|
/** A mode's overlay folder: beside files/, a plain name. */
|
|
@@ -112,8 +117,11 @@ function loadModes(name, dir, meta, base) {
|
|
|
112
117
|
// files out. Browser mode's own files come after its drop list does.
|
|
113
118
|
const overlay = existsSync(root) ? walk(root) : [];
|
|
114
119
|
const drop = [...(spec.drop ?? [])];
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
const dropScripts = [...(spec.dropScripts ?? [])];
|
|
121
|
+
const merged = base.find((f) => f.class === "merged");
|
|
122
|
+
const scripts = Object.keys((merged && readJson(merged.from)?.scripts) ?? {});
|
|
123
|
+
checkMode(name, mode, { base, overlay, drop, scripts, dropScripts });
|
|
124
|
+
modes[mode] = { root, drop, dropScripts, files: overlay.map((path) => ({ path, class: "fixed", from: join(root, path) })) };
|
|
117
125
|
}
|
|
118
126
|
return modes;
|
|
119
127
|
}
|
|
@@ -132,6 +140,29 @@ export function modeFiles(template, mode = BASE_MODE) {
|
|
|
132
140
|
return template.files.filter((f) => !drop.has(f.path)).map((f) => over.get(f.path) ?? f);
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
/**
|
|
144
|
+
* The scripts an app in this mode has none of (template.json
|
|
145
|
+
* `modes.<mode>.dropScripts`). Browser mode drops `start` (0.8.0): a
|
|
146
|
+
* browser app is built once and served as files, nothing ever starts it,
|
|
147
|
+
* and the store refuses a browser app's package.json that names one (its
|
|
148
|
+
* own-rules.mjs, browserScriptRefusals). Until 0.8.0 init laid the base's
|
|
149
|
+
* `start` into every app.
|
|
150
|
+
*/
|
|
151
|
+
export function modeDropScripts(template, mode = BASE_MODE) {
|
|
152
|
+
return mode === BASE_MODE ? [] : [...(template.modes?.[mode]?.dropScripts ?? [])];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The template's package.json as a mode lays it: the base's, less the
|
|
156
|
+
* scripts the mode drops. null when the template has no merged file. */
|
|
157
|
+
export function modePackage(template, mode = BASE_MODE) {
|
|
158
|
+
const merged = template.files.find((f) => f.class === "merged");
|
|
159
|
+
if (!merged) return null;
|
|
160
|
+
const pkg = readJson(merged.from);
|
|
161
|
+
const drop = modeDropScripts(template, mode);
|
|
162
|
+
if (drop.length && pkg.scripts) pkg.scripts = Object.fromEntries(Object.entries(pkg.scripts).filter(([name]) => !drop.includes(name)));
|
|
163
|
+
return pkg;
|
|
164
|
+
}
|
|
165
|
+
|
|
135
166
|
export function loadTemplate(name) {
|
|
136
167
|
const dir = join(TEMPLATES, name);
|
|
137
168
|
const meta = readJson(join(dir, "template.json"));
|