@malloy-publisher/create-malloy-package 0.0.18 → 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 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
- every model is queryable over REST at
90
- `POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`. Run one of
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/sales.malloy/query \
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.6.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 modelFile = `${sourceName}.malloy`;
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.modelFile}/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.`, "");
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.18",
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.22",
48
+ "@malloy-publisher/skills": "^0.1.23",
49
49
  "commander": "^12.1.0"
50
50
  },
51
51
  "devDependencies": {
@@ -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: every model is queryable at
158
- `POST /api/v0/environments/<env>/packages/<package>/models/<model>/query`, and after a
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 this to the bottom of THIS FILE, reload the package, then run
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
- // It has to go in the file rather than being sent as a one-off query: Publisher
33
- // refuses raw SQL in an ad-hoc query ("`duckdb.sql(...)` cannot be used in a
34
- // restricted query"), and only allows it where it is part of a model.
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. A reload that fails to compile leaves the previously
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.