@malloy-publisher/create-malloy-package 0.0.1
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/README.md +347 -0
- package/dist/index.js +1869 -0
- package/package.json +62 -0
- package/templates/AGENTS.md +192 -0
- package/templates/model.custom.malloy +16 -0
- package/templates/model.default.malloy +44 -0
- package/templates/sales.csv +11 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) The Linux Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# create-malloy-package
|
|
2
|
+
|
|
3
|
+
Scaffold a [Malloy Publisher](https://github.com/malloydata/publisher) package and a
|
|
4
|
+
local agent workspace in one command. You get a working semantic model over sample
|
|
5
|
+
data, a server config, and a pre-wired agent environment (MCP connection, agent
|
|
6
|
+
instructions, and the Malloy agent skills copied in), so you can go from nothing to
|
|
7
|
+
"an agent can query my data" without hand-editing anything.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
mkdir my-data && cd my-data
|
|
13
|
+
npm create @malloy-publisher/malloy-package sales
|
|
14
|
+
npm start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The workspace files land in the current directory and the package in `./sales`, so you
|
|
18
|
+
run `npm start` from where you created it (no need to `cd` into the package). The
|
|
19
|
+
scaffolder's own last lines are the ones to keep: the app URL, the MCP endpoint, and a
|
|
20
|
+
`curl` that tells you when the server is ready. `npm start` itself boots Publisher with
|
|
21
|
+
the package mounted in watch mode and then streams the server's log, which reports the
|
|
22
|
+
address it bound to (`http://127.0.0.1:4000`) and never says "ready" in so many words.
|
|
23
|
+
Run the readiness check the scaffolder printed:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
curl -s http://localhost:4000/api/v0/status
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It answers `"operationalState":"serving"` once the server is up. On a first run that
|
|
30
|
+
includes downloading the server, so it can take a minute; poll rather than guessing a
|
|
31
|
+
wait.
|
|
32
|
+
|
|
33
|
+
`serving` describes the server, not your package, so follow it with the question that
|
|
34
|
+
is actually about your data:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
curl -s http://localhost:4000/api/v0/environments/default/packages
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A package that failed to load is missing from that list, and an empty `[]` means
|
|
41
|
+
nothing loaded at all while the status endpoint still says `serving`. A data file
|
|
42
|
+
DuckDB cannot read is the usual cause; the server log carries the reason on an error
|
|
43
|
+
line reading `Failed to load package <name>`.
|
|
44
|
+
|
|
45
|
+
`default` is the environment name in a fresh workspace. If the scaffolder added your
|
|
46
|
+
package to a `publisher.config.json` that was already there, the environment is
|
|
47
|
+
whatever that file names, and the generated agent briefing spells out the URLs for
|
|
48
|
+
this workspace. That briefing is `AGENTS.md`, or `AGENTS.malloy.md` in a directory
|
|
49
|
+
that already had an `AGENTS.md` of its own; see "Running it again" below.
|
|
50
|
+
|
|
51
|
+
## Query it
|
|
52
|
+
|
|
53
|
+
The web UI at http://localhost:4000 is the quickest look. For a check you can script,
|
|
54
|
+
every model is queryable over REST at
|
|
55
|
+
`POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`. Run one of
|
|
56
|
+
the starter model's views by name:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
curl -s -X POST \
|
|
60
|
+
http://localhost:4000/api/v0/environments/default/packages/sales/models/sales.malloy/query \
|
|
61
|
+
-H 'Content-Type: application/json' \
|
|
62
|
+
-d '{"sourceName": "sales", "queryName": "by_category", "compactJson": true}' \
|
|
63
|
+
| jq -r .result
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
[{"category":"Furniture","total_amount":789},{"category":"Electronics","total_amount":489.95},{"category":"Apparel","total_amount":375}]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The source is named after the package, and the sample model ships the views
|
|
71
|
+
`by_category`, `by_region`, `sales_by_month` and `overview`. A package seeded with
|
|
72
|
+
`--data` starts with `overview` alone, since the scaffolder does not know your columns.
|
|
73
|
+
The model in the URL is the file inside the package. To send Malloy rather than name a
|
|
74
|
+
view, swap the `-d` payload for one that carries a query and no `sourceName`:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
-d '{"query": "run: sales -> by_category", "compactJson": true}'
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The response is `{"result": "<rows as a JSON string>", "resource": "..."}`, hence the
|
|
81
|
+
`jq -r .result` above. Leave `compactJson` off to get the full Malloy result with the
|
|
82
|
+
type metadata the renderer uses.
|
|
83
|
+
|
|
84
|
+
It has to be a POST, and the path has to be right: any `/api/v0/...` URL that matches
|
|
85
|
+
no route falls through to the web app and answers `200` with an HTML page, which reads
|
|
86
|
+
as success until you look at the body.
|
|
87
|
+
|
|
88
|
+
Agents should reach the same models through MCP rather than curl, which buys them
|
|
89
|
+
schema discovery, compile checks and a reload that needs no restart. The generated
|
|
90
|
+
briefing lists those tools.
|
|
91
|
+
|
|
92
|
+
## What it creates
|
|
93
|
+
|
|
94
|
+
Running `npm create @malloy-publisher/malloy-package sales` in an empty directory produces:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
publisher.config.json the server config, with your package registered
|
|
98
|
+
package.json "npm start" runs Publisher in watch mode, "npm run reset"
|
|
99
|
+
does the same but rebuilds state from the config first
|
|
100
|
+
.gitignore ignores node_modules/, publisher_data/, publisher.db*,
|
|
101
|
+
*.log and .DS_Store
|
|
102
|
+
.mcp.json a pre-wired MCP connection to the local server
|
|
103
|
+
CLAUDE.md / AGENTS.md short, package-scoped agent instructions
|
|
104
|
+
.claude/skills/ the Malloy agent skills, copied in as real files
|
|
105
|
+
sales/ the package
|
|
106
|
+
publisher.json the manifest
|
|
107
|
+
sales.malloy a starter model over the sample data
|
|
108
|
+
data/sales.csv the sample data
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The `publisher.db*` entry is a glob rather than a plain `publisher.db` on purpose:
|
|
112
|
+
DuckDB leaves a `.wal` sidecar behind even after a clean shutdown, so without the glob
|
|
113
|
+
a `git add -A` would stage the server's database state.
|
|
114
|
+
|
|
115
|
+
### Running it again
|
|
116
|
+
|
|
117
|
+
If the workspace files already exist (you ran it once before), a second run extends
|
|
118
|
+
them instead of overwriting them:
|
|
119
|
+
|
|
120
|
+
- the new package is added to the existing `publisher.config.json`, keeping the
|
|
121
|
+
environments, connections, and packages already registered there;
|
|
122
|
+
- the `malloy` server is merged into the existing MCP config (`.mcp.json`, or
|
|
123
|
+
`.cursor/mcp.json` for Cursor) alongside every other server in it, so a hand-edited
|
|
124
|
+
MCP config keeps the servers you put there;
|
|
125
|
+
- an existing `.gitignore` is appended to, never rewritten: it gains only the entries
|
|
126
|
+
above that are not in it already, keeps every rule of yours byte for byte, and is
|
|
127
|
+
listed under "Wrote these" as `.gitignore (n entries appended, nothing removed)`;
|
|
128
|
+
- the files it will not rewrite (`package.json`, `CLAUDE.md`, and an `AGENTS.md` this
|
|
129
|
+
tool did not write) are left as they are and listed under "Left these existing files
|
|
130
|
+
alone" in the output, so the run reports what it did not do as well as what it did.
|
|
131
|
+
|
|
132
|
+
The agent briefing is the exception to that last bullet, and has a section of its own
|
|
133
|
+
below.
|
|
134
|
+
|
|
135
|
+
`--force` replaces `CLAUDE.md`, and an `AGENTS.md` that is yours rather than this
|
|
136
|
+
tool's, outright, so move anything of yours out of them first. The other three files
|
|
137
|
+
are merges, not rewrites, because most of what is in them is yours:
|
|
138
|
+
|
|
139
|
+
- `.gitignore`: appended to, with or without `--force`. Appending is idempotent, so
|
|
140
|
+
`--force` changes nothing here and there is nothing of yours to move out first.
|
|
141
|
+
- `package.json`: `--force` sets the `start` and `reset` scripts and keeps the rest of
|
|
142
|
+
the manifest. Under `--force`, a `package.json` that cannot be read, or that is not
|
|
143
|
+
valid JSON, or that holds something other than a JSON object, stops the run before
|
|
144
|
+
anything is written at all, rather than being replaced with a stub. Without
|
|
145
|
+
`--force` the manifest is never written to in the first place, so a file in any of
|
|
146
|
+
those states is left alone and listed with the reason, and the run prints the full
|
|
147
|
+
`npx @malloy-publisher/server ...` command to use instead of an `npm` script.
|
|
148
|
+
- the MCP config (`.mcp.json`, or `.cursor/mcp.json` for Cursor): only the `malloy` key
|
|
149
|
+
is this tool's, so every other server in the file survives with or without `--force`.
|
|
150
|
+
|
|
151
|
+
An MCP config the tool cannot read as a JSON object is the one it cannot merge into: a
|
|
152
|
+
syntax error, or an `mcpServers` that is not an object. `--force` does not change that.
|
|
153
|
+
The file is left byte for byte as it was, listed under "Left these existing files
|
|
154
|
+
alone" with the reason, and the `malloy` server block is printed for you to paste in by
|
|
155
|
+
hand. Paste it, or fix the file and run again.
|
|
156
|
+
|
|
157
|
+
**Restart the second package with `npm run reset`, not `npm start`.** Registering the
|
|
158
|
+
package in `publisher.config.json` is not enough on its own once the server has run:
|
|
159
|
+
Publisher reads that file only while `publisher_data/` holds no database, and from the
|
|
160
|
+
second boot on its own persisted state wins. A package added later is therefore
|
|
161
|
+
registered, reported as created, and never served, with nothing in the server log to
|
|
162
|
+
say why. `npm run reset` is the same boot with `--init`, which rebuilds the persisted
|
|
163
|
+
state from the config; after it has served the new package once, `npm start` is enough
|
|
164
|
+
again. That holds for the `reset` script this tool writes. Where the scaffolder left an
|
|
165
|
+
existing `package.json` alone, a `reset` script already in it is not this tool's and
|
|
166
|
+
need not carry `--init` at all, so use the printed `npx @malloy-publisher/server ...`
|
|
167
|
+
command with `--init` on the end instead. `--init` is deliberately not part of `start`,
|
|
168
|
+
because it also clears runtime
|
|
169
|
+
state (materialized tables, saved theme) on every boot. If you moved the ports, pass
|
|
170
|
+
them here too (`npm run reset -- --port <p> --mcp_port <m>`); see "Ports and network
|
|
171
|
+
exposure" below.
|
|
172
|
+
|
|
173
|
+
**Stop the running server before you reset.** A workspace holds one DuckDB database and
|
|
174
|
+
one lock on it, so a second server over the same directory cannot start even on
|
|
175
|
+
different ports. It prints
|
|
176
|
+
`Could not set lock on file "publisher.db": Conflicting lock is held ... (PID nnnn)`,
|
|
177
|
+
then sits at `"operationalState":"initializing"` and stays there rather than exiting.
|
|
178
|
+
That covers `npm run reset` while `npm start` is up, and any second server pointed at
|
|
179
|
+
this same workspace. Two workspaces in two directories are fine, as long as they are
|
|
180
|
+
not on the same ports.
|
|
181
|
+
|
|
182
|
+
If the scaffolder left an existing `package.json` alone, it added no `start` or `reset`
|
|
183
|
+
script of its own. That manifest may well have scripts by those names already, but they
|
|
184
|
+
boot whatever their author meant them to boot: not necessarily Publisher, not
|
|
185
|
+
necessarily this workspace, not necessarily on loopback, and in the `reset` case not
|
|
186
|
+
necessarily with `--init`. So the run does not recommend them blind. It prints the full
|
|
187
|
+
`npx @malloy-publisher/server ...` command to run instead, and says that no Publisher
|
|
188
|
+
`start` (or `reset`) script was added here; the reset is that same command with `--init`
|
|
189
|
+
on the end. Where it read enough of the existing script to say something specific, it
|
|
190
|
+
gives the reason as well: that the script boots Publisher with no `--host`, or writes
|
|
191
|
+
the `--host=` form Publisher silently drops, or binds a non-loopback address, or names
|
|
192
|
+
the server inside a longer command line, or in the `reset` case carries no `--init`. A
|
|
193
|
+
script that boots something else entirely (a bundler, a test runner) gets the general
|
|
194
|
+
sentence rather than a named reason, because nothing here parsed what it runs.
|
|
195
|
+
|
|
196
|
+
#### The agent briefing, and `AGENTS.malloy.md`
|
|
197
|
+
|
|
198
|
+
The briefing is the one file that is regenerated rather than merged or skipped. It is
|
|
199
|
+
written in full from the run's own state (the start command, the ports, the package,
|
|
200
|
+
the skills count), so there is nothing in it to preserve, and a copy left describing
|
|
201
|
+
the workspace as it was two packages ago is the failure it exists to prevent.
|
|
202
|
+
|
|
203
|
+
- In a workspace this tool scaffolded, the briefing is `AGENTS.md`, and every later
|
|
204
|
+
run rewrites it. It is listed under "Overwrote these", alongside `.claude/skills/`,
|
|
205
|
+
which is refreshed the same way and for the same reason.
|
|
206
|
+
- In a directory that already had an `AGENTS.md` of its own, that file is not this
|
|
207
|
+
tool's to touch and is left byte for byte as it was. The briefing goes beside it as
|
|
208
|
+
**`AGENTS.malloy.md`**, and the `CLAUDE.md` the run writes points there instead, as
|
|
209
|
+
does the run's closing line. `AGENTS.malloy.md` is this tool's file, so later runs
|
|
210
|
+
rewrite it too.
|
|
211
|
+
|
|
212
|
+
Either way the briefing describes one package: the one that run created. Scaffold a
|
|
213
|
+
second package into the same workspace and the briefing is regenerated about the
|
|
214
|
+
second one, while the server goes on serving both. The list of everything the
|
|
215
|
+
workspace actually serves is the packages endpoint:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
curl -s http://localhost:4000/api/v0/environments/default/packages
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Ports and network exposure
|
|
222
|
+
|
|
223
|
+
The server listens on 4000 (web UI and REST) and 4040 (MCP), Publisher's defaults.
|
|
224
|
+
Pass `--port` and `--mcp_port` to move them, with npm's `--` separator in front:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm start -- --port 4100 --mcp_port 4140
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The flags belong to that one boot and nothing remembers them, so every later boot needs
|
|
231
|
+
them again, `npm run reset` included. A bare `npm run reset` goes back to 4000 and 4040
|
|
232
|
+
and collides with whatever was in the way the first time:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npm run reset -- --port 4100 --mcp_port 4140
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
4100 and 4140 are a guess at a free pair, not a checked answer. Run the readiness check
|
|
239
|
+
against the new port and read `location` and `packages` out of it, exactly as you would
|
|
240
|
+
on 4000.
|
|
241
|
+
|
|
242
|
+
Moving the MCP port also means changing the `url` in `.mcp.json` (or
|
|
243
|
+
`.cursor/mcp.json`) to match, since that file was written for 4040.
|
|
244
|
+
|
|
245
|
+
**A port collision looks like success.** If another Publisher already holds 4000 and
|
|
246
|
+
4040, the second one fails to bind and exits 1, but the last lines it prints are the
|
|
247
|
+
green `Service marked as ready` and `Environment store successfully initialized` from
|
|
248
|
+
the startup work that did finish; the `EADDRINUSE` error is well above them. The
|
|
249
|
+
readiness check then answers `"operationalState":"serving"` from the *other* server,
|
|
250
|
+
and because both workspaces name their environment `default`, the only tell is the
|
|
251
|
+
`location` and `packages` in the response describing a different directory. Check
|
|
252
|
+
those two fields, or move this server onto free ports.
|
|
253
|
+
|
|
254
|
+
Moving ports only helps when the server in the way is serving a different workspace. If
|
|
255
|
+
it is this workspace's own server, free ports change nothing: the second one takes the
|
|
256
|
+
database lock error above and never reaches `serving`. Stop the first server instead.
|
|
257
|
+
|
|
258
|
+
The `start` script this tool writes passes `--host 127.0.0.1`, so the workspace is
|
|
259
|
+
reachable from your machine only. Publisher's own default is `0.0.0.0`, which suits a
|
|
260
|
+
deployed server; here neither the REST API nor the MCP endpoint has any authentication
|
|
261
|
+
in front of it, so binding to every interface would hand the local network a read of
|
|
262
|
+
your data. To expose it deliberately, change `--host` in the `start` script to `0.0.0.0`
|
|
263
|
+
(or to a specific interface) and put something that authenticates in front of it.
|
|
264
|
+
|
|
265
|
+
All of that describes the script this tool writes. In a directory that already had a
|
|
266
|
+
`package.json`, the scaffolder leaves it alone (see "Running it again"), so `npm start`
|
|
267
|
+
runs whatever `start` was already there, binding wherever that command says. The same
|
|
268
|
+
goes for everything else on this page that reads off a flag: `--port` and `--mcp_port`
|
|
269
|
+
decide the URLs, `--server_root` and `--config` decide which workspace is served at
|
|
270
|
+
all, and `--watch-env` decides whether an edit on disk is picked up without a reload.
|
|
271
|
+
Read them out of the command you are about to run rather than off this page.
|
|
272
|
+
|
|
273
|
+
Then read it off the server, which is the only ground truth. On boot it prints
|
|
274
|
+
`Publisher server listening at http://127.0.0.1:4000` and an `MCP server listening at`
|
|
275
|
+
line beside it, and those are the addresses it really bound. Publisher accepts an
|
|
276
|
+
unknown flag without complaint, so a mistyped `--hostt` is dropped in silence and the
|
|
277
|
+
bind falls back to the `0.0.0.0` default while the command still reads as loopback. The
|
|
278
|
+
`=` form goes the same way: Publisher reads `--host` and its address as two separate
|
|
279
|
+
arguments, so `--host=127.0.0.1` matches no flag it knows, is dropped without a word,
|
|
280
|
+
and binds `0.0.0.0`. Write the flag and the address with a space between them.
|
|
281
|
+
|
|
282
|
+
## Options
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm create @malloy-publisher/malloy-package [name] -- [options]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`npm create` parses the command line with npm's own config parser before handing
|
|
289
|
+
what is left to the scaffolder, so flags must sit behind a `--` separator. Without
|
|
290
|
+
it npm swallows `--force` as one of its own settings and turns `--data mydata.csv`
|
|
291
|
+
into two stray positional arguments:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
npm create @malloy-publisher/malloy-package sales -- --data mydata.csv
|
|
295
|
+
npm create @malloy-publisher/malloy-package sales -- --client cursor
|
|
296
|
+
npm create @malloy-publisher/malloy-package sales -- --force
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Running the published bin directly takes the flags as-is, with no separator:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
npx @malloy-publisher/create-malloy-package sales --data mydata.csv
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
- `name` (positional): the package name. Omit it to only set up the agent workspace in
|
|
306
|
+
the current directory (write the MCP connection, agent instructions, and skills)
|
|
307
|
+
without scaffolding a package.
|
|
308
|
+
- `--data <file>`: seed the package from your own CSV, Parquet, or XLSX file instead of
|
|
309
|
+
the built-in sample. The file is copied into the package and the starter model points
|
|
310
|
+
at it. DuckDB reads all three formats in place; an Excel file is read as its first
|
|
311
|
+
sheet. It seeds a new package, so it requires a package name: it cannot be combined
|
|
312
|
+
with the setup-only mode above, and passing it without a name is an error rather than
|
|
313
|
+
a silently ignored flag.
|
|
314
|
+
- `--client <claude-code|cursor>`: which agent client to wire up. Defaults to
|
|
315
|
+
`claude-code`. `AGENTS.md` and the skills in `.claude/skills/` are written for every
|
|
316
|
+
client; the MCP config file (`.mcp.json` for Claude Code, `.cursor/mcp.json` for
|
|
317
|
+
Cursor) and `CLAUDE.md` are the client-specific parts. The flag is `--client` rather
|
|
318
|
+
than `--host` because the generated start command passes Publisher's own `--host`
|
|
319
|
+
for the bind address, and two meanings of one flag name in a single workspace is a
|
|
320
|
+
trap.
|
|
321
|
+
- `--force`: overwrite existing workspace files instead of extending them. The `--`
|
|
322
|
+
separator matters most here: npm has a `--force` of its own, so a bare one never
|
|
323
|
+
reaches the scaffolder.
|
|
324
|
+
|
|
325
|
+
## Requirements
|
|
326
|
+
|
|
327
|
+
Node 20 or newer. The generated `npm start` uses `npx` to fetch the Publisher server on
|
|
328
|
+
first run, so no global install is needed. The default template runs entirely on the
|
|
329
|
+
package's built-in DuckDB sandbox, so no database credentials are required.
|
|
330
|
+
|
|
331
|
+
### The workspace path
|
|
332
|
+
|
|
333
|
+
Create the workspace somewhere whose full path is made only of letters, digits, `-`,
|
|
334
|
+
`_`, `.` and `/`. DuckDB cannot read a data file under a path containing a space, a
|
|
335
|
+
parenthesis, an apostrophe, or any non-ASCII character, and Publisher resolves the
|
|
336
|
+
model's relative table path against the workspace directory before that check runs. So
|
|
337
|
+
a single space anywhere above the package makes every model in it fail to load, with
|
|
338
|
+
the server still reporting healthy and the only visible symptom an empty package list.
|
|
339
|
+
|
|
340
|
+
Common directories that trip it: `~/Documents/My Projects`, `~/Google Drive`,
|
|
341
|
+
`~/OneDrive - Company`, and any home directory whose username carries an accent. The
|
|
342
|
+
scaffolder checks this before writing anything and refuses to run in such a directory,
|
|
343
|
+
naming the offending character. Move to a path like `~/malloy-workspace` and run again.
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
MIT
|