@malloydata/malloyyo 0.2.39 → 0.2.41
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/frame-inpage-entry.tsx +8 -2
- package/dist/index.js +127 -50
- package/package.json +1 -1
|
@@ -35,10 +35,16 @@ const givensToUrl = (dashboard, givens) => {
|
|
|
35
35
|
|
|
36
36
|
mountInPage({
|
|
37
37
|
root: document.getElementById("root"),
|
|
38
|
-
// Governed query — the shell's trusted
|
|
38
|
+
// Governed query — the shell's trusted api/run (the same endpoint the iframe
|
|
39
39
|
// broker forwards to). Returns the raw result the runtime normalizes.
|
|
40
|
+
//
|
|
41
|
+
// RELATIVE. A leading slash resolves against the ORIGIN, so behind anything
|
|
42
|
+
// serving this page under a prefix — code-server's `/proxy/<port>/`, a
|
|
43
|
+
// Codespaces forwarded port — the POST went to the proxy's own root and came
|
|
44
|
+
// back `405 Method Not Allowed` from the proxy rather than reaching the dev
|
|
45
|
+
// server at all. Resolved against the document, it is correct at any depth.
|
|
40
46
|
run: (req, givens) =>
|
|
41
|
-
fetch("
|
|
47
|
+
fetch("api/run", {
|
|
42
48
|
method: "POST",
|
|
43
49
|
headers: { "content-type": "application/json" },
|
|
44
50
|
body: JSON.stringify({ d: name, query: req.query, malloy: req.malloy, givens }),
|
package/dist/index.js
CHANGED
|
@@ -2881,7 +2881,7 @@ function clearCreds(url6) {
|
|
|
2881
2881
|
}
|
|
2882
2882
|
|
|
2883
2883
|
// package.json
|
|
2884
|
-
var version = "0.2.
|
|
2884
|
+
var version = "0.2.41";
|
|
2885
2885
|
|
|
2886
2886
|
// src/http.ts
|
|
2887
2887
|
var USER_AGENT = `malloyyo/${version}`;
|
|
@@ -2928,15 +2928,34 @@ async function registerClient(registrationEndpoint, redirectUri) {
|
|
|
2928
2928
|
if (!res.ok) throw new Error(`client registration failed: ${res.status} ${await res.text()}`);
|
|
2929
2929
|
return (await res.json()).client_id;
|
|
2930
2930
|
}
|
|
2931
|
+
function browserless(platform = process.platform, env = process.env) {
|
|
2932
|
+
if (platform === "darwin" || platform === "win32") return false;
|
|
2933
|
+
return !env.DISPLAY && !env.WAYLAND_DISPLAY;
|
|
2934
|
+
}
|
|
2931
2935
|
function openBrowser(url6) {
|
|
2932
2936
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url6]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url6]] : ["xdg-open", [url6]];
|
|
2933
2937
|
try {
|
|
2934
|
-
spawn(cmd, args, { stdio: "ignore", detached: true })
|
|
2938
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
2939
|
+
child.on("error", () => {
|
|
2940
|
+
});
|
|
2941
|
+
child.unref();
|
|
2935
2942
|
} catch {
|
|
2936
2943
|
}
|
|
2937
2944
|
}
|
|
2945
|
+
function listenTarget(env = process.env) {
|
|
2946
|
+
const raw = env.MALLOYYO_OAUTH_PORT;
|
|
2947
|
+
let port = 0;
|
|
2948
|
+
if (raw !== void 0 && raw !== "") {
|
|
2949
|
+
port = Number(raw);
|
|
2950
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
2951
|
+
throw new Error(`MALLOYYO_OAUTH_PORT must be a port number between 1 and 65535, got "${raw}"`);
|
|
2952
|
+
}
|
|
2953
|
+
}
|
|
2954
|
+
return { host: env.MALLOYYO_OAUTH_HOST || "127.0.0.1", port };
|
|
2955
|
+
}
|
|
2938
2956
|
function awaitRedirect(state) {
|
|
2939
|
-
return new Promise((resolveServer) => {
|
|
2957
|
+
return new Promise((resolveServer, rejectServer) => {
|
|
2958
|
+
const { host, port: wanted } = listenTarget();
|
|
2940
2959
|
let resolveCode;
|
|
2941
2960
|
let rejectCode;
|
|
2942
2961
|
const code = new Promise((res, rej) => {
|
|
@@ -2961,13 +2980,33 @@ function awaitRedirect(state) {
|
|
|
2961
2980
|
if (ok) resolveCode(got);
|
|
2962
2981
|
else rejectCode(new Error(err ?? "state mismatch or missing code"));
|
|
2963
2982
|
});
|
|
2964
|
-
|
|
2983
|
+
let listening = false;
|
|
2984
|
+
server.on("error", (err) => {
|
|
2985
|
+
clearTimeout(timer);
|
|
2986
|
+
const detail = err.code === "EADDRINUSE" ? `${host}:${wanted} is already in use \u2014 set MALLOYYO_OAUTH_PORT to a free port` : err.message;
|
|
2987
|
+
const failure = new Error(`could not start the sign-in listener: ${detail}`);
|
|
2988
|
+
rejectCode(failure);
|
|
2989
|
+
if (!listening) {
|
|
2990
|
+
void code.catch(() => {
|
|
2991
|
+
});
|
|
2992
|
+
rejectServer(failure);
|
|
2993
|
+
}
|
|
2994
|
+
});
|
|
2995
|
+
server.listen(wanted, host, () => {
|
|
2996
|
+
listening = true;
|
|
2965
2997
|
const port = server.address().port;
|
|
2966
|
-
resolveServer({
|
|
2998
|
+
resolveServer({
|
|
2999
|
+
port,
|
|
3000
|
+
code,
|
|
3001
|
+
close: () => {
|
|
3002
|
+
clearTimeout(timer);
|
|
3003
|
+
server.close();
|
|
3004
|
+
}
|
|
3005
|
+
});
|
|
2967
3006
|
});
|
|
2968
3007
|
});
|
|
2969
3008
|
}
|
|
2970
|
-
async function login(baseUrl) {
|
|
3009
|
+
async function login(baseUrl, opts = {}) {
|
|
2971
3010
|
const ep = await discover(baseUrl);
|
|
2972
3011
|
const { verifier, challenge } = pkce();
|
|
2973
3012
|
const state = crypto.randomBytes(16).toString("base64url");
|
|
@@ -2985,11 +3024,24 @@ async function login(baseUrl) {
|
|
|
2985
3024
|
scope: "mcp",
|
|
2986
3025
|
state
|
|
2987
3026
|
}).toString();
|
|
2988
|
-
|
|
2989
|
-
|
|
3027
|
+
if (opts.noBrowser || browserless()) {
|
|
3028
|
+
console.log(`Visit this URL to sign in:
|
|
3029
|
+
|
|
2990
3030
|
${authUrl.toString()}
|
|
2991
3031
|
`);
|
|
2992
|
-
|
|
3032
|
+
if (!process.env.MALLOYYO_OAUTH_PORT) {
|
|
3033
|
+
console.log(
|
|
3034
|
+
"Note: sign-in redirects back to this machine on a random port.\n In a container, set MALLOYYO_OAUTH_PORT and MALLOYYO_OAUTH_HOST=0.0.0.0,\n and publish that port, so the browser can reach the redirect.\n"
|
|
3035
|
+
);
|
|
3036
|
+
}
|
|
3037
|
+
console.log("Waiting for sign-in to complete\u2026");
|
|
3038
|
+
} else {
|
|
3039
|
+
console.log("Opening your browser to sign in\u2026");
|
|
3040
|
+
console.log(`If it doesn't open, visit:
|
|
3041
|
+
${authUrl.toString()}
|
|
3042
|
+
`);
|
|
3043
|
+
openBrowser(authUrl.toString());
|
|
3044
|
+
}
|
|
2993
3045
|
const authCode = await code;
|
|
2994
3046
|
const res = await apiFetch(ep.token_endpoint, {
|
|
2995
3047
|
method: "POST",
|
|
@@ -3330,6 +3382,7 @@ function siblingList(current, all, href) {
|
|
|
3330
3382
|
// src/dashboard.ts
|
|
3331
3383
|
var resolveFrameEntry = () => path6.join(resolveRuntimeDir(), "..", "frame-entry.tsx");
|
|
3332
3384
|
var resolveInPageEntry = () => path6.join(resolveRuntimeDir(), "..", "frame-inpage-entry.tsx");
|
|
3385
|
+
var inlineScript = (js) => js.replace(/<\/(script)/gi, String.raw`<\/$1`);
|
|
3333
3386
|
var esc2 = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
3334
3387
|
function makeBundler() {
|
|
3335
3388
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -3405,9 +3458,26 @@ function makeInPageBundler() {
|
|
|
3405
3458
|
};
|
|
3406
3459
|
}
|
|
3407
3460
|
var html = (body, title) => `<!doctype html><html><head><meta charset="utf-8"><title>${esc2(title)}</title><meta name="viewport" content="width=device-width,initial-scale=1"><style>${NAV_CSS}</style></head><body style="margin:0">${body}</body></html>`;
|
|
3408
|
-
var
|
|
3461
|
+
var dashLink = (n) => `?d=${encodeURIComponent(n)}`;
|
|
3462
|
+
var DEV_PATHS = {
|
|
3463
|
+
/** Sibling dashboard, for the switcher in the nav bar. */
|
|
3464
|
+
dashboard: dashLink,
|
|
3465
|
+
/** In-page (tag-only) bundle. */
|
|
3466
|
+
inPage: (n) => `inpage.js?d=${encodeURIComponent(n)}`,
|
|
3467
|
+
/** Artifact bundle, for a dashboard with a Dashboard.tsx. */
|
|
3468
|
+
bundle: (n) => `bundle.js?d=${encodeURIComponent(n)}`,
|
|
3469
|
+
/** The sandboxed artifact document. Same origin as the shell, relative like
|
|
3470
|
+
everything else — the iframe is isolated by its OPAQUE origin (the
|
|
3471
|
+
`allow-scripts`-only sandbox), not by living on a second port. */
|
|
3472
|
+
frame: (n) => `frame?d=${encodeURIComponent(n)}`,
|
|
3473
|
+
/** Live-reload stream. */
|
|
3474
|
+
events: "events",
|
|
3475
|
+
/** The privileged query broker the parent shell brokers postMessage into. */
|
|
3476
|
+
run: "api/run"
|
|
3477
|
+
};
|
|
3478
|
+
var devSiblings = (dash, all) => siblingList(dash.name, all, dashLink);
|
|
3409
3479
|
function navHtml2(dash, all) {
|
|
3410
|
-
return navHtml(dash.name, all,
|
|
3480
|
+
return navHtml(dash.name, all, dashLink);
|
|
3411
3481
|
}
|
|
3412
3482
|
function inPageShell(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpecs) {
|
|
3413
3483
|
const info = {
|
|
@@ -3422,21 +3492,20 @@ function inPageShell(dash, all, givenSpecs, initialGivens, initialUrlState, tile
|
|
|
3422
3492
|
autorun: dash.autorun
|
|
3423
3493
|
};
|
|
3424
3494
|
return html(
|
|
3425
|
-
navHtml2(dash, all) + `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>try{new EventSource('
|
|
3495
|
+
navHtml2(dash, all) + `<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>try{new EventSource('${DEV_PATHS.events}').onmessage=()=>location.reload();}catch(e){}</script><script src="${DEV_PATHS.inPage(dash.name)}"></script>`,
|
|
3426
3496
|
dash.title
|
|
3427
3497
|
);
|
|
3428
3498
|
}
|
|
3429
|
-
function parentShell(dash,
|
|
3499
|
+
function parentShell(dash, all, initialGivens, initialUrlState) {
|
|
3430
3500
|
const givensQs = Object.entries({ ...initialGivens, ...initialUrlState }).map(([k, v]) => `&${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("");
|
|
3431
3501
|
const d = safeJson(dash.name);
|
|
3432
|
-
const fb = safeJson(frameBase);
|
|
3433
3502
|
const nav = navHtml2(dash, all);
|
|
3434
3503
|
return html(
|
|
3435
3504
|
`<div style="display:flex;flex-direction:column;height:100vh">` + nav + // allow-popups(+escape-sandbox): let a # link mark open its target in a
|
|
3436
3505
|
// normal new tab on click instead of being blocked by the sandbox.
|
|
3437
|
-
`<iframe id="f" sandbox="allow-scripts allow-
|
|
3506
|
+
`<iframe id="f" sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox" src="${DEV_PATHS.frame(dash.name)}${givensQs}" style="border:0;flex:1;width:100%"></iframe></div><script>
|
|
3438
3507
|
const f=document.getElementById('f');
|
|
3439
|
-
try{new EventSource('
|
|
3508
|
+
try{new EventSource('${DEV_PATHS.events}').onmessage=()=>location.reload();}catch(e){}
|
|
3440
3509
|
// The shareable URL has TWO namespaces the frame syncs independently:
|
|
3441
3510
|
// '$NAME' = a given (the governed query contract), '~key' = a custom
|
|
3442
3511
|
// component's useUrlState view-state. Each write must re-emit the other's
|
|
@@ -3451,7 +3520,7 @@ function shareUrl(dashboard){
|
|
|
3451
3520
|
return u.pathname+u.search;
|
|
3452
3521
|
}
|
|
3453
3522
|
window.addEventListener('message',async(e)=>{
|
|
3454
|
-
if(e.source!==f.contentWindow
|
|
3523
|
+
if(e.source!==f.contentWindow)return;
|
|
3455
3524
|
const m=e.data;
|
|
3456
3525
|
if(m&&m.type==='givens'){
|
|
3457
3526
|
G=m.givens||{};
|
|
@@ -3473,11 +3542,11 @@ window.addEventListener('message',async(e)=>{
|
|
|
3473
3542
|
if(!m||m.type!=='run')return;
|
|
3474
3543
|
let out;
|
|
3475
3544
|
try{
|
|
3476
|
-
const res=await fetch('
|
|
3545
|
+
const res=await fetch('${DEV_PATHS.run}',{method:'POST',headers:{'content-type':'application/json'},
|
|
3477
3546
|
body:JSON.stringify({d:${d},query:m.query,malloy:m.malloy,givens:m.givens,dashboard:m.dashboard})});
|
|
3478
3547
|
out=await res.json();
|
|
3479
3548
|
}catch(err){ out={ok:false,problems:[{message:String(err)}]}; }
|
|
3480
|
-
f.contentWindow.postMessage({type:'result',id:m.id,...out}
|
|
3549
|
+
f.contentWindow.postMessage({type:'result',id:m.id,...out},'*');
|
|
3481
3550
|
});
|
|
3482
3551
|
</script>`,
|
|
3483
3552
|
dash.title
|
|
@@ -3489,7 +3558,7 @@ function givensFromUrl(url6) {
|
|
|
3489
3558
|
function urlStateFromUrl(url6) {
|
|
3490
3559
|
return urlStateFromSearch(url6.search);
|
|
3491
3560
|
}
|
|
3492
|
-
function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpecs) {
|
|
3561
|
+
function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, bundleJs, tileSpecs) {
|
|
3493
3562
|
const info = {
|
|
3494
3563
|
name: dash.name,
|
|
3495
3564
|
query: dash.query,
|
|
@@ -3504,7 +3573,7 @@ function frameDoc(dash, all, givenSpecs, initialGivens, initialUrlState, tileSpe
|
|
|
3504
3573
|
autorun: dash.autorun
|
|
3505
3574
|
};
|
|
3506
3575
|
return html(
|
|
3507
|
-
`<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script
|
|
3576
|
+
`<div id="root"></div><script>window.__DASHBOARD__=${safeJson(info)};window.__DASHBOARDS__=${safeJson(devSiblings(dash, all))};window.__GIVENS__=${safeJson(givenSpecs)};window.__INITIAL_GIVENS__=${safeJson(initialGivens)};window.__INITIAL_URLSTATE__=${safeJson(initialUrlState)}</script><script>${inlineScript(bundleJs)}</script>`,
|
|
3508
3577
|
dash.title
|
|
3509
3578
|
);
|
|
3510
3579
|
}
|
|
@@ -3517,8 +3586,6 @@ async function serveDashboard(opts) {
|
|
|
3517
3586
|
await initConnections();
|
|
3518
3587
|
const root = path6.resolve(opts.root ?? process.cwd());
|
|
3519
3588
|
const port = opts.port ?? 4173;
|
|
3520
|
-
const framePort = port + 1;
|
|
3521
|
-
const frameBase = `http://localhost:${framePort}`;
|
|
3522
3589
|
const runner = await makeRunner(root);
|
|
3523
3590
|
if (!runner.entryExists()) {
|
|
3524
3591
|
throw new Error(`No index.malloy at ${root} \u2014 run this from a Malloy model repo.`);
|
|
@@ -3566,34 +3633,39 @@ async function serveDashboard(opts) {
|
|
|
3566
3633
|
console.error(` (file watch unavailable: ${e.message} \u2014 edits won't auto-reload)`);
|
|
3567
3634
|
}
|
|
3568
3635
|
const handler = async (req, res) => {
|
|
3569
|
-
const
|
|
3570
|
-
const url6 = new URL(req.url ?? "/", `http://localhost:${onFramePort ? framePort : port}`);
|
|
3636
|
+
const url6 = new URL(req.url ?? "/", `http://localhost:${port}`);
|
|
3571
3637
|
const send = (code, type, body, extra = {}) => {
|
|
3572
3638
|
res.writeHead(code, { "content-type": type, ...extra });
|
|
3573
3639
|
res.end(body);
|
|
3574
3640
|
};
|
|
3575
3641
|
try {
|
|
3576
|
-
if (
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
if (!g.ok) {
|
|
3581
|
-
return send(
|
|
3582
|
-
200,
|
|
3583
|
-
"text/html; charset=utf-8",
|
|
3584
|
-
html(`<pre style="color:crimson;padding:16px">model error: ${esc2(g.error)}</pre>`, dash.title)
|
|
3585
|
-
);
|
|
3586
|
-
}
|
|
3642
|
+
if (url6.pathname === "/frame") {
|
|
3643
|
+
const dash = pick(url6);
|
|
3644
|
+
const g = await resolveGivens(dash);
|
|
3645
|
+
if (!g.ok) {
|
|
3587
3646
|
return send(
|
|
3588
3647
|
200,
|
|
3589
3648
|
"text/html; charset=utf-8",
|
|
3590
|
-
|
|
3649
|
+
html(`<pre style="color:crimson;padding:16px">model error: ${esc2(g.error)}</pre>`, dash.title)
|
|
3591
3650
|
);
|
|
3592
3651
|
}
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3652
|
+
return send(
|
|
3653
|
+
200,
|
|
3654
|
+
"text/html; charset=utf-8",
|
|
3655
|
+
frameDoc(
|
|
3656
|
+
dash,
|
|
3657
|
+
dashboards,
|
|
3658
|
+
g.union,
|
|
3659
|
+
givensFromUrl(url6),
|
|
3660
|
+
urlStateFromUrl(url6),
|
|
3661
|
+
await bundle(dash),
|
|
3662
|
+
g.tiles
|
|
3663
|
+
),
|
|
3664
|
+
{ "cache-control": "no-store" }
|
|
3665
|
+
);
|
|
3666
|
+
}
|
|
3667
|
+
if (url6.pathname === "/bundle.js") {
|
|
3668
|
+
return send(200, "application/javascript; charset=utf-8", await bundle(pick(url6)));
|
|
3597
3669
|
}
|
|
3598
3670
|
if (url6.pathname === "/events") {
|
|
3599
3671
|
res.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
@@ -3616,17 +3688,24 @@ async function serveDashboard(opts) {
|
|
|
3616
3688
|
return send(
|
|
3617
3689
|
200,
|
|
3618
3690
|
"text/html; charset=utf-8",
|
|
3619
|
-
inPageShell(dash, dashboards, g.union, givensFromUrl(url6), urlStateFromUrl(url6), g.tiles)
|
|
3691
|
+
inPageShell(dash, dashboards, g.union, givensFromUrl(url6), urlStateFromUrl(url6), g.tiles),
|
|
3692
|
+
{ "cache-control": "no-store" }
|
|
3620
3693
|
);
|
|
3621
3694
|
}
|
|
3622
3695
|
return send(
|
|
3623
3696
|
200,
|
|
3624
3697
|
"text/html; charset=utf-8",
|
|
3625
|
-
parentShell(dash,
|
|
3698
|
+
parentShell(dash, dashboards, givensFromUrl(url6), urlStateFromUrl(url6)),
|
|
3699
|
+
{ "cache-control": "no-store" }
|
|
3626
3700
|
);
|
|
3627
3701
|
}
|
|
3628
3702
|
if (url6.pathname === "/inpage.js") {
|
|
3629
|
-
return send(
|
|
3703
|
+
return send(
|
|
3704
|
+
200,
|
|
3705
|
+
"application/javascript; charset=utf-8",
|
|
3706
|
+
await inPageBundle(),
|
|
3707
|
+
{ "cache-control": "no-store" }
|
|
3708
|
+
);
|
|
3630
3709
|
}
|
|
3631
3710
|
if (url6.pathname === "/api/run" && req.method === "POST") {
|
|
3632
3711
|
const { d, query, malloy, givens } = JSON.parse(await readBody(req));
|
|
@@ -3642,12 +3721,10 @@ async function serveDashboard(opts) {
|
|
|
3642
3721
|
}
|
|
3643
3722
|
};
|
|
3644
3723
|
const shellServer = http2.createServer(handler);
|
|
3645
|
-
const frameServer = http2.createServer(handler);
|
|
3646
3724
|
await new Promise((r) => shellServer.listen(port, r));
|
|
3647
|
-
await new Promise((r) => frameServer.listen(framePort, r));
|
|
3648
3725
|
console.error(`
|
|
3649
3726
|
malloyyo dashboard dev \u2014 model: ${root}`);
|
|
3650
|
-
console.error(` http://localhost:${port}
|
|
3727
|
+
console.error(` http://localhost:${port}/`);
|
|
3651
3728
|
for (const d of dashboards) {
|
|
3652
3729
|
const kind = d.tsxPath ? "custom (iframe)" : "tag-only (in-page)";
|
|
3653
3730
|
console.error(` \u2022 ${d.name} (${kind}) \u2192 http://localhost:${port}/?d=${d.name}`);
|
|
@@ -5389,9 +5466,9 @@ async function status(target, opts) {
|
|
|
5389
5466
|
console.log(` version ${s.version ?? "?"}` + (git?.sha ? ` ${git.branch}@${shortSha(git.sha)}` : ""));
|
|
5390
5467
|
console.log(` ${s.compileError ? `\u2717 ${s.compileError}` : `\u2713 compiled ${s.compiledAt ?? ""}`}`);
|
|
5391
5468
|
}
|
|
5392
|
-
async function loginCmd(target) {
|
|
5469
|
+
async function loginCmd(target, opts) {
|
|
5393
5470
|
const inst = resolveInstance(resolve3("."), target);
|
|
5394
|
-
await login(inst.url);
|
|
5471
|
+
await login(inst.url, { noBrowser: opts.browser === false });
|
|
5395
5472
|
console.log(`\u2713 logged in to ${inst.name} (${inst.url})`);
|
|
5396
5473
|
}
|
|
5397
5474
|
async function logoutCmd(target) {
|
|
@@ -5400,7 +5477,7 @@ async function logoutCmd(target) {
|
|
|
5400
5477
|
}
|
|
5401
5478
|
var program = new Command();
|
|
5402
5479
|
program.name("malloyyo").description("Publish Malloy models to a Malloyyo instance").version(version);
|
|
5403
|
-
program.command("login").argument("[target]", "target name or instance URL (optional if the config has one target)").description("sign in to an instance in your browser (stores a token)").action(loginCmd);
|
|
5480
|
+
program.command("login").argument("[target]", "target name or instance URL (optional if the config has one target)").option("--no-browser", "print the sign-in URL instead of launching a browser").description("sign in to an instance in your browser (stores a token)").action(loginCmd);
|
|
5404
5481
|
program.command("logout").argument("[target]", "target name or instance URL (optional if the config has one target)").description("forget the stored token for an instance").action(logoutCmd);
|
|
5405
5482
|
program.command("publish").argument(
|
|
5406
5483
|
"[target]",
|