@malloydata/malloyyo 0.2.21 → 0.2.23
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/dist/index.js +90 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,19 +7,80 @@ import { resolve as resolve2 } from "node:path";
|
|
|
7
7
|
// src/config.ts
|
|
8
8
|
import { readFileSync, existsSync } from "node:fs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
|
+
var KNOWN_KEYS = /* @__PURE__ */ new Set(["analytics", "targets"]);
|
|
11
|
+
function isTarget(v) {
|
|
12
|
+
return typeof v === "object" && v !== null && typeof v.url === "string";
|
|
13
|
+
}
|
|
14
|
+
var warned = /* @__PURE__ */ new Set();
|
|
15
|
+
function warnOnce(key, msg) {
|
|
16
|
+
if (warned.has(key)) return;
|
|
17
|
+
warned.add(key);
|
|
18
|
+
console.warn(msg);
|
|
19
|
+
}
|
|
20
|
+
function parseMalloyyoConfig(raw) {
|
|
21
|
+
const declared = raw.targets;
|
|
22
|
+
const targets = typeof declared === "object" && declared !== null ? { ...declared } : {};
|
|
23
|
+
const legacy = [];
|
|
24
|
+
const unknown = [];
|
|
25
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
26
|
+
if (KNOWN_KEYS.has(k)) continue;
|
|
27
|
+
if (isTarget(v)) {
|
|
28
|
+
if (!(k in targets)) targets[k] = v;
|
|
29
|
+
legacy.push(k);
|
|
30
|
+
} else {
|
|
31
|
+
unknown.push(k);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (legacy.length) {
|
|
35
|
+
warnOnce(
|
|
36
|
+
"legacy-targets",
|
|
37
|
+
`malloy-config.json: publish target(s) ${legacy.map((t) => `"${t}"`).join(", ")} are at the top of the "malloyyo" block. Move them under "targets":
|
|
38
|
+
"malloyyo": { "targets": { ${legacy.map((t) => `"${t}": { \u2026 }`).join(", ")} } }
|
|
39
|
+
The old shape still works for now.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
if (unknown.length) {
|
|
43
|
+
warnOnce(
|
|
44
|
+
"unknown-keys",
|
|
45
|
+
`malloy-config.json: ignoring unknown key(s) in the "malloyyo" block: ${unknown.map((k) => `"${k}"`).join(", ")}. Known keys: ${[...KNOWN_KEYS].join(", ")}.`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const analytics = raw.analytics;
|
|
49
|
+
return {
|
|
50
|
+
analytics: typeof analytics === "string" && analytics ? analytics : void 0,
|
|
51
|
+
targets
|
|
52
|
+
};
|
|
53
|
+
}
|
|
10
54
|
function readTargetMap(dir) {
|
|
55
|
+
const raw = readMalloyyoBlock(dir);
|
|
56
|
+
if (raw) return parseMalloyyoConfig(raw).targets;
|
|
57
|
+
throw new Error(
|
|
58
|
+
`No \`malloyyo\` config found in ${dir} (looked for a "malloyyo" block in malloy-config.json, then malloyyo.json).`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
function readMalloyyoBlock(dir) {
|
|
11
62
|
const malloyConfig = join(dir, "malloy-config.json");
|
|
12
63
|
if (existsSync(malloyConfig)) {
|
|
13
|
-
|
|
14
|
-
|
|
64
|
+
try {
|
|
65
|
+
const json = JSON.parse(readFileSync(malloyConfig, "utf8"));
|
|
66
|
+
if (json.malloyyo && typeof json.malloyyo === "object") return json.malloyyo;
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
15
69
|
}
|
|
16
70
|
const standalone = join(dir, "malloyyo.json");
|
|
17
71
|
if (existsSync(standalone)) {
|
|
18
|
-
|
|
72
|
+
try {
|
|
73
|
+
return JSON.parse(readFileSync(standalone, "utf8"));
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
19
76
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
function readSiteConfig(dir) {
|
|
80
|
+
const raw = readMalloyyoBlock(dir);
|
|
81
|
+
if (!raw) return {};
|
|
82
|
+
const { analytics } = parseMalloyyoConfig(raw);
|
|
83
|
+
return analytics ? { analytics } : {};
|
|
23
84
|
}
|
|
24
85
|
var normalizeUrl = (u) => u.replace(/\/+$/, "");
|
|
25
86
|
function resolveTarget(dir, name) {
|
|
@@ -3275,7 +3336,14 @@ function navFor(dash, all, cleanUrls) {
|
|
|
3275
3336
|
(n) => cleanUrls ? `./${encodeURIComponent(n)}` : `./${encodeURIComponent(n)}.html`
|
|
3276
3337
|
);
|
|
3277
3338
|
}
|
|
3278
|
-
function
|
|
3339
|
+
function analyticsSnippet(id) {
|
|
3340
|
+
if (!id) return "";
|
|
3341
|
+
const j = JSON.stringify(id);
|
|
3342
|
+
return `<script async src="https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(id)}"></script>
|
|
3343
|
+
<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}
|
|
3344
|
+
gtag('js',new Date());gtag('config',${j});</script>`;
|
|
3345
|
+
}
|
|
3346
|
+
function page(dash, all, title, givenSpecs, tileSpecs, cleanUrls, analytics) {
|
|
3279
3347
|
const info = {
|
|
3280
3348
|
name: dash.name,
|
|
3281
3349
|
query: dash.query,
|
|
@@ -3295,6 +3363,7 @@ function page(dash, all, title, givenSpecs, tileSpecs, cleanUrls) {
|
|
|
3295
3363
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3296
3364
|
<title>${esc3(dash.title || dash.name)}${title ? ` \xB7 ${esc3(title)}` : ""}</title>
|
|
3297
3365
|
<link rel="stylesheet" href="./assets/site.css">
|
|
3366
|
+
${analyticsSnippet(analytics)}
|
|
3298
3367
|
</head>
|
|
3299
3368
|
<body>
|
|
3300
3369
|
${navFor(dash, all, cleanUrls)}
|
|
@@ -3317,7 +3386,7 @@ window.__GIVENS__ = ${JSON.stringify(givenSpecs)};
|
|
|
3317
3386
|
</html>
|
|
3318
3387
|
`;
|
|
3319
3388
|
}
|
|
3320
|
-
function indexPage(dashboards, title, custom, cleanUrls) {
|
|
3389
|
+
function indexPage(dashboards, title, custom, cleanUrls, analytics) {
|
|
3321
3390
|
const link = (n) => cleanUrls ? `./${encodeURIComponent(n)}` : `./${encodeURIComponent(n)}.html`;
|
|
3322
3391
|
const body = custom ? `<div id="root"></div>
|
|
3323
3392
|
<script>window.__DASHBOARDS__ = ${JSON.stringify(
|
|
@@ -3333,6 +3402,7 @@ function indexPage(dashboards, title, custom, cleanUrls) {
|
|
|
3333
3402
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3334
3403
|
<title>${esc3(title)}</title>
|
|
3335
3404
|
<link rel="stylesheet" href="./assets/site.css">
|
|
3405
|
+
${analyticsSnippet(analytics)}
|
|
3336
3406
|
</head>
|
|
3337
3407
|
<body>
|
|
3338
3408
|
${navHtml("", dashboards, link)}
|
|
@@ -3357,6 +3427,7 @@ async function bundleDashboards(opts = {}) {
|
|
|
3357
3427
|
const outDir = path7.resolve(root, opts.out ?? "docs");
|
|
3358
3428
|
const title = opts.title ?? path7.basename(root);
|
|
3359
3429
|
const target = opts.target ?? "pages";
|
|
3430
|
+
const analytics = opts.analytics ?? readSiteConfig(root).analytics;
|
|
3360
3431
|
const cleanUrls = target === "vercel";
|
|
3361
3432
|
const selfHostDuckdb = opts.duckdb === "bundled";
|
|
3362
3433
|
const runner = await makeRunner(root);
|
|
@@ -3493,9 +3564,9 @@ boot(Dashboard);
|
|
|
3493
3564
|
if (!got.ok) throw new Error(`dashboard ${d.name}: ${got.error}`);
|
|
3494
3565
|
specs = got.givens;
|
|
3495
3566
|
}
|
|
3496
|
-
fs6.writeFileSync(path7.join(outDir, `${d.name}.html`), page(d, dashboards, title, specs, tileSpecs, cleanUrls));
|
|
3567
|
+
fs6.writeFileSync(path7.join(outDir, `${d.name}.html`), page(d, dashboards, title, specs, tileSpecs, cleanUrls, analytics));
|
|
3497
3568
|
}
|
|
3498
|
-
fs6.writeFileSync(path7.join(outDir, "index.html"), indexPage(dashboards, title, !!landing, cleanUrls));
|
|
3569
|
+
fs6.writeFileSync(path7.join(outDir, "index.html"), indexPage(dashboards, title, !!landing, cleanUrls, analytics));
|
|
3499
3570
|
if (landing) {
|
|
3500
3571
|
await esbuild3.build({
|
|
3501
3572
|
stdin: {
|
|
@@ -3533,6 +3604,7 @@ bundled ${dashboards.length} dashboard(s) \u2192 ${path7.relative(process.cwd(),
|
|
|
3533
3604
|
selfHostDuckdb ? ` duckdb ${duck.length} assets (self-hosted)` : ` duckdb jsDelivr CDN (nothing copied)`
|
|
3534
3605
|
);
|
|
3535
3606
|
console.log(` model ${Object.keys(modelFiles).length} .malloy files inlined`);
|
|
3607
|
+
if (analytics) console.log(` analytics ${analytics}`);
|
|
3536
3608
|
if (copiedData.length) {
|
|
3537
3609
|
const mb = copiedData.reduce((a, r) => a + fs6.statSync(path7.join(root, r)).size, 0) / 1048576;
|
|
3538
3610
|
console.log(` data ${copiedData.length} file(s) copied, ${mb.toFixed(1)} MB (same-origin)`);
|
|
@@ -3668,7 +3740,7 @@ async function launchCmd(mode, opts) {
|
|
|
3668
3740
|
}
|
|
3669
3741
|
|
|
3670
3742
|
// package.json
|
|
3671
|
-
var version = "0.2.
|
|
3743
|
+
var version = "0.2.23";
|
|
3672
3744
|
|
|
3673
3745
|
// src/index.ts
|
|
3674
3746
|
function shortSha(sha) {
|
|
@@ -3775,7 +3847,7 @@ program.command("author").option("-C, --root <dir>", "project root (default: cur
|
|
|
3775
3847
|
program.command("test").option("-C, --root <dir>", "project root (default: current directory)").description("launch Claude wired ONLY to the explore surface \u2014 the claude.ai web preview").action(async (opts) => {
|
|
3776
3848
|
await launchCmd("test", opts);
|
|
3777
3849
|
});
|
|
3778
|
-
program.command("dashboard").argument("<action>", "action to run (dev | bundle)").option("-C, --root <dir>", "project root (default: current directory)").option("-p, --port <port>", "port to serve on (dev)", "4173").option("-o, --out <dir>", "output directory (bundle)", "docs").option("--title <title>", "site title (bundle; default: project directory name)").option("--target <target>", "deploy target: pages | vercel (bundle)", "pages").option("--duckdb <source>", "DuckDB binaries: cdn | bundled (bundle)", "cdn").option("--no-serve", "bundle only; don't serve the result (bundle)").description("preview dashboards locally (dev), or build a static site from them (bundle)").action(
|
|
3850
|
+
program.command("dashboard").argument("<action>", "action to run (dev | bundle)").option("-C, --root <dir>", "project root (default: current directory)").option("-p, --port <port>", "port to serve on (dev)", "4173").option("-o, --out <dir>", "output directory (bundle)", "docs").option("--title <title>", "site title (bundle; default: project directory name)").option("--target <target>", "deploy target: pages | vercel (bundle)", "pages").option("--duckdb <source>", "DuckDB binaries: cdn | bundled (bundle)", "cdn").option("--analytics <id>", "GA4 Measurement ID, overriding malloyyo.analytics in malloy-config.json (bundle)").option("--no-serve", "bundle only; don't serve the result (bundle)").description("preview dashboards locally (dev), or build a static site from them (bundle)").action(
|
|
3779
3851
|
async (action, opts) => {
|
|
3780
3852
|
if (action === "dev") {
|
|
3781
3853
|
await serveDashboard({ root: opts.root, port: Number(opts.port) });
|
|
@@ -3785,6 +3857,11 @@ program.command("dashboard").argument("<action>", "action to run (dev | bundle)"
|
|
|
3785
3857
|
if (opts.target !== "pages" && opts.target !== "vercel") {
|
|
3786
3858
|
throw new Error(`unknown --target '${opts.target}' (expected: pages | vercel)`);
|
|
3787
3859
|
}
|
|
3860
|
+
if (opts.analytics && !/^G-[A-Z0-9]+$/i.test(opts.analytics)) {
|
|
3861
|
+
throw new Error(
|
|
3862
|
+
`--analytics expects a GA4 Measurement ID like G-XXXXXXXXXX, got '${opts.analytics}'`
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3788
3865
|
if (opts.duckdb !== "cdn" && opts.duckdb !== "bundled") {
|
|
3789
3866
|
throw new Error(`unknown --duckdb '${opts.duckdb}' (expected: cdn | bundled)`);
|
|
3790
3867
|
}
|
|
@@ -3795,6 +3872,7 @@ program.command("dashboard").argument("<action>", "action to run (dev | bundle)"
|
|
|
3795
3872
|
serve: opts.serve,
|
|
3796
3873
|
target: opts.target,
|
|
3797
3874
|
duckdb: opts.duckdb,
|
|
3875
|
+
analytics: opts.analytics,
|
|
3798
3876
|
// `dashboard dev` owns 4173/4174; default the bundle preview clear of
|
|
3799
3877
|
// both so you can run the two side by side.
|
|
3800
3878
|
port: opts.port === "4173" ? 4180 : Number(opts.port)
|