@malloy-publisher/create-malloy-package 0.0.17 → 0.0.19
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 -4
- package/dist/index.js +10 -4
- package/package.json +2 -2
- package/templates/AGENTS.md +7 -2
- package/templates/index.malloy +20 -0
- package/templates/model.custom.xlsx.malloy +17 -5
package/README.md
CHANGED
|
@@ -86,13 +86,13 @@ know it worked when the agent's first Malloy query returns data.
|
|
|
86
86
|
## Query it
|
|
87
87
|
|
|
88
88
|
The web UI at http://localhost:4000 is the quickest look. For a check you can script,
|
|
89
|
-
|
|
90
|
-
`POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`.
|
|
91
|
-
the starter model's views by name:
|
|
89
|
+
a package's published models are queryable over REST at
|
|
90
|
+
`POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`. Address the
|
|
91
|
+
package's surface, `index.malloy`, and run one of the starter model's views by name:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
94
|
curl -s -X POST \
|
|
95
|
-
http://localhost:4000/api/v0/environments/default/packages/sales/models/
|
|
95
|
+
http://localhost:4000/api/v0/environments/default/packages/sales/models/index.malloy/query \
|
|
96
96
|
-H 'Content-Type: application/json' \
|
|
97
97
|
-d '{"sourceName": "sales", "queryName": "by_category", "compactJson": true}' \
|
|
98
98
|
| jq -r .result
|
|
@@ -144,6 +144,10 @@ sales/ the package
|
|
|
144
144
|
resolution differs from Publisher's (machine-specific
|
|
145
145
|
absolute path; drop it and open the editor at sales/ instead
|
|
146
146
|
if you commit the package)
|
|
147
|
+
index.malloy the published surface: imports the model below and
|
|
148
|
+
`export`s its source. What it exports is what Publisher
|
|
149
|
+
lists and what may be queried, so no manifest key is
|
|
150
|
+
needed; leave a source out to keep it internal
|
|
147
151
|
sales.malloy a starter model over the sample data
|
|
148
152
|
data/sales.csv the sample data
|
|
149
153
|
```
|
package/dist/index.js
CHANGED
|
@@ -666,6 +666,7 @@ function renderTemplate(name, vars) {
|
|
|
666
666
|
}
|
|
667
667
|
|
|
668
668
|
// src/scaffold.ts
|
|
669
|
+
var INDEX_MODEL_NAME = "index.malloy";
|
|
669
670
|
var MALLOY_AGENTS_FILE = "AGENTS.malloy.md";
|
|
670
671
|
var BRIEFING_MARKER = "This directory is a [Malloy Publisher](https://github.com/malloydata/publisher)";
|
|
671
672
|
var ENV_NAME = "default";
|
|
@@ -674,7 +675,7 @@ var MCP_PORT = 4040;
|
|
|
674
675
|
var ALT_PUBLISHER_PORT = PUBLISHER_PORT + 100;
|
|
675
676
|
var ALT_MCP_PORT = MCP_PORT + 100;
|
|
676
677
|
var BIND_HOST = "127.0.0.1";
|
|
677
|
-
var SERVER_VERSION = "0.
|
|
678
|
+
var SERVER_VERSION = "0.7.0";
|
|
678
679
|
function startCommandFor(envName) {
|
|
679
680
|
return `npx -y @malloy-publisher/server@${SERVER_VERSION} --server_root . ` + `--config ./publisher.config.json --host ${BIND_HOST} ` + `--watch-env ${envName}`;
|
|
680
681
|
}
|
|
@@ -771,7 +772,8 @@ function createPackage(options, result) {
|
|
|
771
772
|
validateDataFile(options.dataFile);
|
|
772
773
|
}
|
|
773
774
|
const sourceName = toMalloyIdentifier(name);
|
|
774
|
-
const
|
|
775
|
+
const modelIsIndex = sourceName.toLowerCase() === "index";
|
|
776
|
+
const modelFile = modelIsIndex ? INDEX_MODEL_NAME : `${sourceName}.malloy`;
|
|
775
777
|
const packageDir = path3.join(options.cwd, name);
|
|
776
778
|
const packageDirExists = fs4.existsSync(packageDir);
|
|
777
779
|
if (packageDirExists && !fs4.lstatSync(packageDir).isDirectory()) {
|
|
@@ -820,10 +822,14 @@ function createPackage(options, result) {
|
|
|
820
822
|
dataPath = "data/sales.csv";
|
|
821
823
|
writeFile(path3.join(packageDir, modelFile), renderTemplate("model.default.malloy", { sourceName }), options.cwd);
|
|
822
824
|
}
|
|
825
|
+
if (!modelIsIndex) {
|
|
826
|
+
writeFile(path3.join(packageDir, INDEX_MODEL_NAME), renderTemplate(INDEX_MODEL_NAME, { sourceName, modelFile }), options.cwd);
|
|
827
|
+
}
|
|
823
828
|
result.packageCreated = true;
|
|
824
829
|
result.packageName = name;
|
|
825
830
|
result.sourceName = sourceName;
|
|
826
831
|
result.modelFile = modelFile;
|
|
832
|
+
result.indexFile = modelIsIndex ? modelFile : INDEX_MODEL_NAME;
|
|
827
833
|
result.dataPath = dataPath;
|
|
828
834
|
result.written.push(`${name}/`);
|
|
829
835
|
if (packageDirExists) {
|
|
@@ -833,7 +839,7 @@ function createPackage(options, result) {
|
|
|
833
839
|
function forceDescription(name, modelFile, host) {
|
|
834
840
|
const agentFiles = host === "cursor" ? "AGENTS.md" : "AGENTS.md and CLAUDE.md";
|
|
835
841
|
const mcpPath = mcpConfigPathFor(host);
|
|
836
|
-
return `--force does not empty the directory. It rewrites ${name}/publisher.json, ` + `${name}/malloy-config.json, ${name}/${modelFile} and the data file it ` + `copies into ${name}/data/, and leaves anything else in there alone. ` + `It also refreshes .claude/skills/ ` + `from the bundled copies, as every run does. Outside the package it ` + `replaces ${agentFiles}; in package.json it sets only the "start" and ` + `"reset" scripts and keeps the rest of the file as it is; in ${mcpPath} it ` + `sets only the "malloy" server and keeps the others. publisher.config.json ` + `is extended, never rewritten, and .gitignore only gains the lines it is ` + `missing, with or without --force. None of those merges is a rewrite even ` + `when the file cannot be read: a package.json that is not valid JSON aborts ` + `the run before anything is written, and an unreadable ${mcpPath} or ` + `.gitignore is left alone and reported. If this directory has an AGENTS.md ` + `of its own, --force is not what you want for it: a run without --force ` + `keeps that file and writes the Publisher briefing to ${MALLOY_AGENTS_FILE} ` + `beside it instead, regenerating that one on every run.`;
|
|
842
|
+
return `--force does not empty the directory. It rewrites ${name}/publisher.json, ` + `${name}/malloy-config.json, ${name}/${modelFile}, ` + (modelFile === INDEX_MODEL_NAME ? `` : `${name}/${INDEX_MODEL_NAME} `) + `and the data file it ` + `copies into ${name}/data/, and leaves anything else in there alone. ` + `It also refreshes .claude/skills/ ` + `from the bundled copies, as every run does. Outside the package it ` + `replaces ${agentFiles}; in package.json it sets only the "start" and ` + `"reset" scripts and keeps the rest of the file as it is; in ${mcpPath} it ` + `sets only the "malloy" server and keeps the others. publisher.config.json ` + `is extended, never rewritten, and .gitignore only gains the lines it is ` + `missing, with or without --force. None of those merges is a rewrite even ` + `when the file cannot be read: a package.json that is not valid JSON aborts ` + `the run before anything is written, and an unreadable ${mcpPath} or ` + `.gitignore is left alone and reported. If this directory has an AGENTS.md ` + `of its own, --force is not what you want for it: a run without --force ` + `keeps that file and writes the Publisher briefing to ${MALLOY_AGENTS_FILE} ` + `beside it instead, regenerating that one on every run.`;
|
|
837
843
|
}
|
|
838
844
|
function mcpConfigPathFor(host) {
|
|
839
845
|
return host === "cursor" ? ".cursor/mcp.json" : ".mcp.json";
|
|
@@ -1408,7 +1414,7 @@ function packageSection(result, envPackages) {
|
|
|
1408
1414
|
];
|
|
1409
1415
|
if (result.packageCreated) {
|
|
1410
1416
|
const base = restBase(result.packageName);
|
|
1411
|
-
lines.push(`\`${result.packageName}/${result.modelFile}\` defines a Malloy source named \`${result.sourceName}\` over local data. Read it for the real source, field, and view names and use them verbatim; never guess them. The package's REST base is:`, "", "```", base, "```", "", "Run one of its views from a script:", "", "```bash", `curl -s -X POST ${base}/models/${result.
|
|
1417
|
+
lines.push(`\`${result.packageName}/${result.modelFile}\` defines a Malloy source named \`${result.sourceName}\` over local data. Read it for the real source, field, and view names and use them verbatim; never guess them.`, "", result.indexFile === result.modelFile ? `That file is also \`${result.indexFile}\`, this package's published surface: what it \`export\`s is what Publisher lists and what may be queried. Query through it.` : `\`${result.packageName}/${result.indexFile}\` is the package's published surface: it imports that model and \`export\`s \`${result.sourceName}\`. Publisher lists and accepts queries against the surface, so address queries to \`${result.indexFile}\`, not to \`${result.modelFile}\` -- a model off the surface is refused with a 404. Add a source to the \`export\` list to publish it.`, "", `One thing that surface cannot publish: a dashboard. A \`dashboards/*.malloy\` file is a file, not a source, so \`${result.indexFile}\` has no way to \`export\` it, and a dashboard added to this package is NOT served -- it is written, it compiles, and it is withheld with a load warning. To serve dashboards, declare an \`explores\` in \`publisher.json\` naming \`${result.indexFile}\` and every dashboard file; the explicit key overrides the convention. Decide that before building one, because the fix is a different curation shape rather than an extra line.`, "", `The package's REST base is:`, "", "```", base, "```", "", "Run one of its views from a script:", "", "```bash", `curl -s -X POST ${base}/models/${result.indexFile}/query \\`, " -H 'content-type: application/json' \\", ` -d '{"query":"run: ${result.sourceName} -> overview"}'`, "```", "", `The VS Code / Cursor Malloy extension resolves the model's relative \`duckdb.table('data/…')\` paths against the EDITOR WORKSPACE root, while Publisher resolves them against the package directory. \`${result.packageName}/malloy-config.json\` (generated, machine-specific absolute path) bridges that for an editor opened at this workspace root; without it, open the editor at \`${result.packageName}/\` itself, or a model Publisher serves fine shows unresolved-table errors in the editor.`, "");
|
|
1412
1418
|
}
|
|
1413
1419
|
if (others.length > 0) {
|
|
1414
1420
|
lines.push(otherPackagesParagraph(result, others, restBase), "");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malloy-publisher/create-malloy-package",
|
|
3
3
|
"description": "Scaffold a Malloy Publisher package and a local agent workspace, so one command takes you from nothing to an agent that can query your data.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.19",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test:e2e": "bun test --timeout 180000 ./tests/e2e"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@malloy-publisher/skills": "^0.1.
|
|
48
|
+
"@malloy-publisher/skills": "^0.1.23",
|
|
49
49
|
"commander": "^12.1.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
package/templates/AGENTS.md
CHANGED
|
@@ -154,8 +154,13 @@ watch-mode recompile that failed), and
|
|
|
154
154
|
connection by plain-English description, for modelling data that is not in this package
|
|
155
155
|
yet; it returns each table's columns and the `source:` line to start from). {{mcpNote}}
|
|
156
156
|
|
|
157
|
-
REST, for a script or a check that does not need an agent:
|
|
158
|
-
|
|
157
|
+
REST, for a script or a check that does not need an agent: a model on the package's
|
|
158
|
+
published surface is queryable at
|
|
159
|
+
`POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`. Not every
|
|
160
|
+
`.malloy` file on disk is: where the package has a root `index.malloy`, that file is the
|
|
161
|
+
surface, and a model it does not export is refused with a 404 that reads the same as a
|
|
162
|
+
model that does not exist. Address queries to the surface file, and take the model names
|
|
163
|
+
from the package listing below rather than from the filenames. After a
|
|
159
164
|
model-file edit `GET /api/v0/environments/<env>/packages/<package>?reload=true` recompiles
|
|
160
165
|
the package and comes back 424 with the compile errors when it does not compile. Any other
|
|
161
166
|
non-2xx is a failed check too, and its `message` says what went wrong: a 404 for a package
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// index.malloy: this package's published surface.
|
|
2
|
+
//
|
|
3
|
+
// Publisher reads a package's index.malloy as the list of what it publishes,
|
|
4
|
+
// so publisher.json needs no "explores" key: the surface is whatever this
|
|
5
|
+
// file exports.
|
|
6
|
+
//
|
|
7
|
+
// `export { ... }` is the curation, and it is a real boundary rather than
|
|
8
|
+
// just a listing filter. A source named here is discoverable AND queryable.
|
|
9
|
+
// A source left out still compiles, and other models can import, join and
|
|
10
|
+
// extend it, but a direct query against it is refused with a 404 --
|
|
11
|
+
// indistinguishable from a source that does not exist. Add a name here to
|
|
12
|
+
// publish it; leave one out to keep it as an internal building block.
|
|
13
|
+
//
|
|
14
|
+
// To curate listings WITHOUT refusing queries, keep an explicit "explores" in
|
|
15
|
+
// publisher.json alongside "queryableSources": "all". That is the one thing
|
|
16
|
+
// this file cannot express. See docs/discovery-and-access.md.
|
|
17
|
+
|
|
18
|
+
import "{{modelFile}}"
|
|
19
|
+
|
|
20
|
+
export { {{sourceName}} }
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
//
|
|
18
18
|
// STEP 1: look at the sheet, because the fix needs three things this file cannot
|
|
19
19
|
// tell you: which sheet, which row the header is on, and which column marks a
|
|
20
|
-
// real row. Add
|
|
20
|
+
// real row. Add the probe source below to `index.malloy` -- NOT to this file --
|
|
21
|
+
// and add `{{sourceName}}_probe` to the `export { ... }` line already there.
|
|
22
|
+
// Reload the package, then query `index.malloy` and run
|
|
21
23
|
// `{{sourceName}}_probe -> peek`. Delete it once you have what you need.
|
|
22
24
|
//
|
|
23
25
|
// source: {{sourceName}}_probe is duckdb.sql("""
|
|
@@ -29,9 +31,18 @@
|
|
|
29
31
|
// view: peek is { select: *; limit: 25 }
|
|
30
32
|
// }
|
|
31
33
|
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
34
|
+
// export { {{sourceName}}, {{sourceName}}_probe } -- the existing line, probe added
|
|
35
|
+
//
|
|
36
|
+
// Two reasons it goes there and not here. Raw SQL has to be part of a model at
|
|
37
|
+
// all: Publisher refuses it in an ad-hoc query ("`duckdb.sql(...)` cannot be
|
|
38
|
+
// used in a restricted query"). And `index.malloy` is this package's published
|
|
39
|
+
// surface, so it is the only model a query can be addressed to -- a query
|
|
40
|
+
// against this file is refused with a 404, and a source this package does not
|
|
41
|
+
// export is refused even when the query goes to the right file. Putting the
|
|
42
|
+
// probe in `index.malloy` and naming it in `export { ... }` satisfies both.
|
|
43
|
+
// Edit the export line that is already there rather than adding a second one:
|
|
44
|
+
// Malloy refuses a name that two export statements both list. Take the probe
|
|
45
|
+
// back out of that line when you delete it.
|
|
35
46
|
//
|
|
36
47
|
// `peek` prints the first 25 rows exactly as they sit in the sheet, banner rows
|
|
37
48
|
// and all, so you can count down to the real header row and see which column
|
|
@@ -76,7 +87,8 @@
|
|
|
76
87
|
// }
|
|
77
88
|
//
|
|
78
89
|
// Then reload the package, check the reload itself came back OK, and only then
|
|
79
|
-
// run `overview` again
|
|
90
|
+
// run `overview` again -- addressed to `index.malloy`, like every query against
|
|
91
|
+
// this package. A reload that fails to compile leaves the previously
|
|
80
92
|
// compiled model serving, so `overview` answers 200 with the OLD number and it
|
|
81
93
|
// looks as though your edit did nothing. The reason is in the reload response,
|
|
82
94
|
// not in the query.
|