@dashai/cli 0.26.0 → 0.28.0
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 +2 -0
- package/dist/bin.js +1405 -497
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -4,7 +4,7 @@ import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, read
|
|
|
4
4
|
import { hostname, tmpdir, platform, homedir } from 'os';
|
|
5
5
|
import { resolve, join, relative, dirname, basename, win32, sep } from 'path';
|
|
6
6
|
import { execFileSync, spawn, spawnSync } from 'child_process';
|
|
7
|
-
import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm, multiselect } from '@clack/prompts';
|
|
7
|
+
import { intro, log, select, isCancel, cancel, spinner, outro, text, confirm as confirm$1, multiselect } from '@clack/prompts';
|
|
8
8
|
import { Command, Option } from 'commander';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
@@ -178,16 +178,12 @@ function removeModuleEntry(slug) {
|
|
|
178
178
|
function isLoggedIn() {
|
|
179
179
|
return readSavedToken() !== null;
|
|
180
180
|
}
|
|
181
|
-
var FILE_NAME, DIR_NAME, DEFAULT_API_URL
|
|
181
|
+
var FILE_NAME, DIR_NAME, DEFAULT_API_URL;
|
|
182
182
|
var init_config = __esm({
|
|
183
183
|
"src/lib/config.ts"() {
|
|
184
184
|
FILE_NAME = "auth.json";
|
|
185
185
|
DIR_NAME = "dashwise";
|
|
186
|
-
DEFAULT_API_URL = "https://api.dashwise.
|
|
187
|
-
KNOWN_API_URLS = {
|
|
188
|
-
dev: "https://api.dashwise.dev",
|
|
189
|
-
prod: "https://api.dashwise.net"
|
|
190
|
-
};
|
|
186
|
+
DEFAULT_API_URL = "https://api.dashwise.net";
|
|
191
187
|
}
|
|
192
188
|
});
|
|
193
189
|
|
|
@@ -283,7 +279,7 @@ async function apiRequest(method, path, body, options = {}) {
|
|
|
283
279
|
function asEnvelope(v) {
|
|
284
280
|
if (!v || typeof v !== "object") return null;
|
|
285
281
|
const env = v;
|
|
286
|
-
const out2 = {};
|
|
282
|
+
const out2 = { body: env };
|
|
287
283
|
if (typeof env.code === "string") out2.code = env.code;
|
|
288
284
|
else if (typeof env.error === "string" && CODE_SHAPE.test(env.error)) out2.code = env.error;
|
|
289
285
|
if (typeof env.message === "string") out2.message = env.message;
|
|
@@ -557,6 +553,14 @@ async function getModuleBySlug(slug, options = {}) {
|
|
|
557
553
|
function isInstallGated(r) {
|
|
558
554
|
return r.install_gated === true;
|
|
559
555
|
}
|
|
556
|
+
async function getUpgradePreview(installationId, version, options = {}) {
|
|
557
|
+
return apiRequest(
|
|
558
|
+
"GET",
|
|
559
|
+
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
560
|
+
void 0,
|
|
561
|
+
options
|
|
562
|
+
);
|
|
563
|
+
}
|
|
560
564
|
async function installModule(moduleId, body, options = {}) {
|
|
561
565
|
return apiRequest(
|
|
562
566
|
"POST",
|
|
@@ -640,6 +644,8 @@ var init_api_client = __esm({
|
|
|
640
644
|
code;
|
|
641
645
|
context;
|
|
642
646
|
retriable;
|
|
647
|
+
/** The whole parsed error body, when the backend sent a JSON object. */
|
|
648
|
+
body;
|
|
643
649
|
constructor(status, envelope, fallbackMessage) {
|
|
644
650
|
const code2 = envelope?.code ?? codeFromStatus(status);
|
|
645
651
|
const message = envelope?.message ?? fallbackMessage;
|
|
@@ -649,6 +655,7 @@ var init_api_client = __esm({
|
|
|
649
655
|
this.code = code2;
|
|
650
656
|
this.context = envelope?.context;
|
|
651
657
|
this.retriable = envelope?.retriable ?? status >= 500;
|
|
658
|
+
this.body = envelope?.body;
|
|
652
659
|
}
|
|
653
660
|
};
|
|
654
661
|
CODE_SHAPE = /^[A-Z][A-Z0-9_]*$/;
|
|
@@ -1187,16 +1194,16 @@ async function devCommand(options) {
|
|
|
1187
1194
|
}
|
|
1188
1195
|
const callbackOrigins = computeCallbackOrigins(port, options.callbackOrigin ?? []);
|
|
1189
1196
|
try {
|
|
1190
|
-
const
|
|
1197
|
+
const session2 = await registerDevSession(apiUrl, apiToken, {
|
|
1191
1198
|
target_workspace_id: workspaceContext.id,
|
|
1192
1199
|
callback_origins: callbackOrigins
|
|
1193
1200
|
});
|
|
1194
|
-
revokeDevSession = () => revokeDevSessionBestEffort(apiUrl, apiToken,
|
|
1195
|
-
devHandoffEnv = devSessionHandoffEnv(
|
|
1196
|
-
info(` ${dim("dev session")} ${
|
|
1201
|
+
revokeDevSession = () => revokeDevSessionBestEffort(apiUrl, apiToken, session2.id);
|
|
1202
|
+
devHandoffEnv = devSessionHandoffEnv(session2);
|
|
1203
|
+
info(` ${dim("dev session")} ${session2.id}`);
|
|
1197
1204
|
info(` ${dim("workspace")} ${workspaceContext.name} (id=${workspaceContext.id}, slug=${workspaceContext.slug})`);
|
|
1198
1205
|
info(` ${dim("callbacks")} ${callbackOrigins.join(", ")}`);
|
|
1199
|
-
info(` ${dim("expires")} ${
|
|
1206
|
+
info(` ${dim("expires")} ${session2.expires_at}`);
|
|
1200
1207
|
info(
|
|
1201
1208
|
` ${dim("sign-in")} ${Object.keys(devHandoffEnv).length > 0 ? "zero-click (server-side dev handoff armed)" : "browser SSO (backend issued no handoff secret \u2014 pre-2026-07-15 backend)"}`
|
|
1202
1209
|
);
|
|
@@ -1455,11 +1462,11 @@ async function resolveModuleApiKey(slug, entry, apiUrl, cliBearer) {
|
|
|
1455
1462
|
success(`Upgraded ${code(slug)} to a dwk_ API key (id=${minted.api_key.id}).`);
|
|
1456
1463
|
return upgraded;
|
|
1457
1464
|
}
|
|
1458
|
-
function devSessionHandoffEnv(
|
|
1459
|
-
const secret =
|
|
1465
|
+
function devSessionHandoffEnv(session2) {
|
|
1466
|
+
const secret = session2.handoff_secret;
|
|
1460
1467
|
if (typeof secret !== "string" || secret.length === 0) return {};
|
|
1461
1468
|
return {
|
|
1462
|
-
DASHWISE_DEV_SESSION_ID:
|
|
1469
|
+
DASHWISE_DEV_SESSION_ID: session2.id,
|
|
1463
1470
|
DASHWISE_DEV_HANDOFF_SECRET: secret
|
|
1464
1471
|
};
|
|
1465
1472
|
}
|
|
@@ -2742,6 +2749,7 @@ var init_custom = __esm({
|
|
|
2742
2749
|
"@tailwindcss/postcss": "^4",
|
|
2743
2750
|
"class-variance-authority": "^0.7.1",
|
|
2744
2751
|
clsx: "^2.1.1",
|
|
2752
|
+
geist: "^1.7.2",
|
|
2745
2753
|
"lucide-react": "^0.577.0",
|
|
2746
2754
|
next: "^15.0.0",
|
|
2747
2755
|
"next-themes": "^0.4.6",
|
|
@@ -7266,6 +7274,15 @@ var init_custom = __esm({
|
|
|
7266
7274
|
integrity: "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
|
|
7267
7275
|
license: "MIT"
|
|
7268
7276
|
},
|
|
7277
|
+
"node_modules/geist": {
|
|
7278
|
+
version: "1.7.2",
|
|
7279
|
+
resolved: "https://registry.npmjs.org/geist/-/geist-1.7.2.tgz",
|
|
7280
|
+
integrity: "sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==",
|
|
7281
|
+
license: "SIL OPEN FONT LICENSE",
|
|
7282
|
+
peerDependencies: {
|
|
7283
|
+
next: ">=13.2.0"
|
|
7284
|
+
}
|
|
7285
|
+
},
|
|
7269
7286
|
"node_modules/gensync": {
|
|
7270
7287
|
version: "1.0.0-beta.2",
|
|
7271
7288
|
resolved: "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
|
|
@@ -10168,7 +10185,13 @@ function nextConfig() {
|
|
|
10168
10185
|
// we DERIVE NEXT_PUBLIC_DASHWISE_API_URL here from DASHWISE_API_URL at
|
|
10169
10186
|
// build/start time \u2014 you never set the NEXT_PUBLIC_ var yourself. If
|
|
10170
10187
|
// you already have one set (e.g. a custom setup), it wins.
|
|
10171
|
-
const apiUrl = process.env.DASHWISE_API_URL;
|
|
10188
|
+
const apiUrl = process.env.DASHWISE_API_URL ?? ${JSON.stringify(DEFAULT_API_URL)};
|
|
10189
|
+
// The browser-facing API target: an explicit NEXT_PUBLIC_ override wins (the
|
|
10190
|
+
// server may reach the API on an internal address). The embedding app is the
|
|
10191
|
+
// app next to THAT public target, never the internal one.
|
|
10192
|
+
const publicApiUrl = process.env.NEXT_PUBLIC_DASHWISE_API_URL ?? apiUrl;
|
|
10193
|
+
const appUrl = new URL(publicApiUrl);
|
|
10194
|
+
appUrl.hostname = appUrl.hostname.replace(/^api\\./, "app.");
|
|
10172
10195
|
|
|
10173
10196
|
const nextConfig = {
|
|
10174
10197
|
// Required for the platform-hosted (per-install Lambda) runtime: the
|
|
@@ -10179,8 +10202,7 @@ const nextConfig = {
|
|
|
10179
10202
|
typescript: { ignoreBuildErrors: true },
|
|
10180
10203
|
eslint: { ignoreDuringBuilds: true },
|
|
10181
10204
|
env: {
|
|
10182
|
-
NEXT_PUBLIC_DASHWISE_API_URL:
|
|
10183
|
-
process.env.NEXT_PUBLIC_DASHWISE_API_URL ?? apiUrl,
|
|
10205
|
+
NEXT_PUBLIC_DASHWISE_API_URL: publicApiUrl,
|
|
10184
10206
|
},
|
|
10185
10207
|
// WHO MAY FRAME THIS MODULE.
|
|
10186
10208
|
//
|
|
@@ -10196,7 +10218,7 @@ const nextConfig = {
|
|
|
10196
10218
|
async headers() {
|
|
10197
10219
|
const ancestors =
|
|
10198
10220
|
process.env.DASHWISE_FRAME_ANCESTORS ??
|
|
10199
|
-
|
|
10221
|
+
appUrl.origin;
|
|
10200
10222
|
return [
|
|
10201
10223
|
{
|
|
10202
10224
|
source: '/:path*',
|
|
@@ -10526,12 +10548,34 @@ export const config = {
|
|
|
10526
10548
|
}
|
|
10527
10549
|
function appLayoutCustomTsx(opts) {
|
|
10528
10550
|
return `import './globals.css';
|
|
10551
|
+
import { GeistSans } from 'geist/font/sans';
|
|
10552
|
+
import { GeistMono } from 'geist/font/mono';
|
|
10529
10553
|
import { AuthProvider, HostRouteSync } from '@dashai/sdk/auth/client';
|
|
10530
10554
|
import { PlatformProvider, PLATFORM_PREPAINT_SCRIPT } from '@dashai/sdk/platform';
|
|
10531
10555
|
import { ThemeProvider } from '@/components/theme-provider';
|
|
10532
10556
|
import { PlatformThemeSync } from '@/components/platform-theme-sync';
|
|
10533
10557
|
import { getSession } from '@/lib/session';
|
|
10534
10558
|
|
|
10559
|
+
// The Dashwise app renders in Geist, and \`globals.css\` names that family at the
|
|
10560
|
+
// front of \`--font-sans\`. Loading it here is what makes that name resolve: drop
|
|
10561
|
+
// these two lines and the stack falls straight through to system-ui, so the
|
|
10562
|
+
// module reads as a different product the moment anyone looks at text.
|
|
10563
|
+
//
|
|
10564
|
+
// The \`geist\` package ships the font files and loads them through
|
|
10565
|
+
// \`next/font/local\`: no download at build time (a cloud sandbox may only
|
|
10566
|
+
// reach npm and Dashwise, L434) and no runtime request, so nothing to declare
|
|
10567
|
+
// in \`runtime.outbound_apis\`. Never switch to \`next/font/google\`: that
|
|
10568
|
+
// fetches from Google during \`next build\`. The \`.variable\` classes on
|
|
10569
|
+
// <html> are what make Next emit the @font-face rules at all; they also expose
|
|
10570
|
+
// \`var(--font-geist-sans)\` for components that want it directly.
|
|
10571
|
+
//
|
|
10572
|
+
// They sit on <html>, not <body>: \`--font-sans\` in globals.css is declared on
|
|
10573
|
+
// :root (= <html>) and reads \`var(--font-geist-sans)\`, and a custom property
|
|
10574
|
+
// resolves where it is declared. The family name next/font generates is not
|
|
10575
|
+
// "Geist", so naming the family literally would not find the loaded files.
|
|
10576
|
+
const geistSans = GeistSans;
|
|
10577
|
+
const geistMono = GeistMono;
|
|
10578
|
+
|
|
10535
10579
|
export const metadata = {
|
|
10536
10580
|
title: ${JSON.stringify(opts.name)},
|
|
10537
10581
|
description: ${JSON.stringify(`Dashwise module: ${opts.slug}`)},
|
|
@@ -10556,7 +10600,12 @@ export default async function RootLayout({ children }: { children: React.ReactNo
|
|
|
10556
10600
|
// renders inside the Dashwise app shell, where the header/nav/theme toggle
|
|
10557
10601
|
// already exist. The pre-paint script below only ever UPGRADES it to
|
|
10558
10602
|
// "standalone", so chrome can never flash on top of the app's own.
|
|
10559
|
-
<html
|
|
10603
|
+
<html
|
|
10604
|
+
lang="en"
|
|
10605
|
+
data-dw-surface="embedded"
|
|
10606
|
+
className={\`\${geistSans.variable} \${geistMono.variable}\`}
|
|
10607
|
+
suppressHydrationWarning
|
|
10608
|
+
>
|
|
10560
10609
|
<head>
|
|
10561
10610
|
{/* Runs before first paint: stamps data-dw-surface on <html> and
|
|
10562
10611
|
re-applies the cached workspace brand. Keeps \`.dw-standalone-only\`
|
|
@@ -11219,6 +11268,7 @@ function packageJsonCustom(opts) {
|
|
|
11219
11268
|
// below that fails with "every import from @dashai/generated
|
|
11220
11269
|
// throws", which points nowhere near the cause.
|
|
11221
11270
|
"@dashai/sdk": SCAFFOLD_SDK_RANGE,
|
|
11271
|
+
geist: GEIST_DEP_RANGE,
|
|
11222
11272
|
next: "^15.0.0",
|
|
11223
11273
|
react: "^19.0.0",
|
|
11224
11274
|
"react-dom": "^19.0.0",
|
|
@@ -11367,13 +11417,18 @@ function globalsCss() {
|
|
|
11367
11417
|
--code-bg: oklch(0.14 0 0);
|
|
11368
11418
|
--code-fg: oklch(0.97 0 0);
|
|
11369
11419
|
|
|
11420
|
+
/* Geist is loaded from the \`geist\` package in app/layout.tsx, which puts
|
|
11421
|
+
--font-geist-sans/--font-geist-mono on <html> (= :root), so they resolve
|
|
11422
|
+
here. Keep those classes on <html>: on <body> the var() below would be
|
|
11423
|
+
unresolved at :root and the whole stack would fall back silently. The
|
|
11424
|
+
second argument of var() keeps "Geist" as a fallback name. */
|
|
11370
11425
|
--font-sans:
|
|
11371
|
-
|
|
11426
|
+
var(--font-geist-sans, "Geist"), ui-sans-serif, system-ui, -apple-system,
|
|
11372
11427
|
BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial,
|
|
11373
11428
|
"Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
|
|
11374
11429
|
"Segoe UI Symbol", "Noto Color Emoji";
|
|
11375
11430
|
--font-mono:
|
|
11376
|
-
|
|
11431
|
+
var(--font-geist-mono, "Geist Mono"), ui-monospace, SFMono-Regular, Menlo,
|
|
11377
11432
|
Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
11378
11433
|
|
|
11379
11434
|
--radius: 0.625rem;
|
|
@@ -11583,27 +11638,42 @@ import { Slot } from "radix-ui";
|
|
|
11583
11638
|
|
|
11584
11639
|
import { cn } from "@/lib/utils";
|
|
11585
11640
|
|
|
11641
|
+
// A faithful copy of the Dashwise app's own button kit \u2014 only the \`cn\` import
|
|
11642
|
+
// path and the "use client" directive above differ. Variants your module does
|
|
11643
|
+
// not use yet are kept on purpose: divergence is what makes a vendored
|
|
11644
|
+
// design-system file drift out of sync with the app it is supposed to match.
|
|
11586
11645
|
const buttonVariants = cva(
|
|
11587
|
-
|
|
11646
|
+
// Focus ring: full-opacity \`ring\` with a background-coloured offset.
|
|
11647
|
+
// The stock shadcn ring (\`ring-ring/50\` drawn directly on the button)
|
|
11648
|
+
// measures 2.30:1 against the page and 2.73:1 against a primary fill \u2014
|
|
11649
|
+
// below the 3:1 that WCAG 1.4.11 / 2.4.11 require of a focus indicator,
|
|
11650
|
+
// i.e. effectively invisible on the most-used control in any module.
|
|
11651
|
+
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
11588
11652
|
{
|
|
11589
11653
|
variants: {
|
|
11590
11654
|
variant: {
|
|
11591
11655
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
11592
11656
|
destructive:
|
|
11593
|
-
"bg-destructive text-white hover:bg-destructive/90",
|
|
11657
|
+
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
|
|
11594
11658
|
outline:
|
|
11595
|
-
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
11659
|
+
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
|
11596
11660
|
secondary:
|
|
11597
11661
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
11598
11662
|
ghost:
|
|
11599
|
-
"hover:bg-accent hover:text-accent-foreground",
|
|
11663
|
+
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
11600
11664
|
link: "text-primary underline-offset-4 hover:underline",
|
|
11665
|
+
"destructive-soft":
|
|
11666
|
+
"bg-destructive/10 text-destructive border border-destructive/20 shadow-xs hover:bg-destructive/20",
|
|
11601
11667
|
},
|
|
11602
11668
|
size: {
|
|
11603
11669
|
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
11670
|
+
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
|
|
11604
11671
|
sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
|
|
11605
11672
|
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
11606
11673
|
icon: "size-9",
|
|
11674
|
+
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
|
|
11675
|
+
"icon-sm": "size-8",
|
|
11676
|
+
"icon-lg": "size-10",
|
|
11607
11677
|
},
|
|
11608
11678
|
},
|
|
11609
11679
|
defaultVariants: {
|
|
@@ -11627,6 +11697,8 @@ function Button({
|
|
|
11627
11697
|
return (
|
|
11628
11698
|
<Comp
|
|
11629
11699
|
data-slot="button"
|
|
11700
|
+
data-variant={variant}
|
|
11701
|
+
data-size={size}
|
|
11630
11702
|
className={cn(buttonVariants({ variant, size, className }))}
|
|
11631
11703
|
{...props}
|
|
11632
11704
|
/>
|
|
@@ -12351,6 +12423,15 @@ the workspace's theme and branding.
|
|
|
12351
12423
|
| corners / elevation | \`rounded-md\`/\`rounded-lg\`, \`shadow-xs\`\u2026\`shadow-md\` |
|
|
12352
12424
|
| type | inherit \`--font-sans\`; ids and code use \`font-mono\` |
|
|
12353
12425
|
|
|
12426
|
+
**Don't re-declare the font.** \`app/layout.tsx\` loads Geist from the
|
|
12427
|
+
\`geist\` package and puts its variables on \`<html>\`; \`globals.css\` reads
|
|
12428
|
+
them with \`var(--font-geist-sans, "Geist")\`. Leave both alone. Never switch to
|
|
12429
|
+
\`next/font/google\` (it downloads from Google during \`next build\`, which fails
|
|
12430
|
+
in a cloud sandbox) and never move the variables to \`<body>\` (the var() at
|
|
12431
|
+
\`:root\` would be unresolved and text silently falls back to Tailwind's default
|
|
12432
|
+
sans). Same class of trap for \`--shadow-*\`, which must be mapped into
|
|
12433
|
+
\`@theme\` to be reachable by \`shadow-xs\`/\`shadow-sm\`.
|
|
12434
|
+
|
|
12354
12435
|
**Theme and branding arrive from the host.** \`PlatformProvider\` +
|
|
12355
12436
|
\`PlatformThemeSync\` (already wired in \`app/layout.tsx\`) follow the app's
|
|
12356
12437
|
light/dark choice and apply the workspace's brand colour to \`--primary\` /
|
|
@@ -12853,9 +12934,7 @@ or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
|
12853
12934
|
person in plain words that the changes are saved and that Release in
|
|
12854
12935
|
Dashwise includes them. Don't ask them to merge anything.
|
|
12855
12936
|
- Never force-push and never rewrite the default branch's history.
|
|
12856
|
-
-
|
|
12857
|
-
pushed, tell them it is ready: in Dashwise open the module, click Release,
|
|
12858
|
-
then Take live.
|
|
12937
|
+
- When the work is pushed, tell the person it is ready to release.
|
|
12859
12938
|
|
|
12860
12939
|
## Cloud workflow
|
|
12861
12940
|
|
|
@@ -12866,8 +12945,8 @@ or in their own \`CLAUDE.md\` / \`AGENTS.md\`), follow them instead.
|
|
|
12866
12945
|
- \`npm run smoke:production\` starts the built app on a local loopback port. If a cloud sandbox refuses that port, rerun the command with the network permission; never skip the check.
|
|
12867
12946
|
- Report what you changed, the pushed SHA and the checks you ran, including failures, in plain words.
|
|
12868
12947
|
- Never print, persist or commit secrets. Repository code delivery needs no Dashwise token. Do not log in or access company data for setup or checks; see "Dashwise login" for full development.
|
|
12869
|
-
-
|
|
12870
|
-
-
|
|
12948
|
+
- Before releasing, installing/updating an installation or taking live, propose the exact action in the chat and wait for the person to accept or change it; never do it on your own.
|
|
12949
|
+
- When the person accepts, do exactly that action if your Dashwise connection allows it; if Dashwise refuses, tell them what it said (for example a permission to turn on in My Account). If you have no way to do it, tell them in one sentence where to click (in Dashwise open the module, click Release, then Take live). Never ask for a \`dashwise login\` or a token just to release.
|
|
12871
12950
|
|
|
12872
12951
|
## If you can't see the repository
|
|
12873
12952
|
|
|
@@ -12911,13 +12990,15 @@ function companyApiHost(apiUrl) {
|
|
|
12911
12990
|
return null;
|
|
12912
12991
|
}
|
|
12913
12992
|
}
|
|
12993
|
+
function docsApiHost(apiUrl) {
|
|
12994
|
+
return companyApiHost(apiUrl) ?? new URL(DEFAULT_API_URL).host;
|
|
12995
|
+
}
|
|
12914
12996
|
function apiHostLine(apiUrl) {
|
|
12915
|
-
const host =
|
|
12916
|
-
return
|
|
12997
|
+
const host = docsApiHost(apiUrl);
|
|
12998
|
+
return `\`${host}\` (this company's Dashwise API; the company uses Dashwise at ${HOSTED_API_HOSTS[host] ?? host})`;
|
|
12917
12999
|
}
|
|
12918
13000
|
function codexEnvironmentInstructions(apiUrl) {
|
|
12919
|
-
const
|
|
12920
|
-
const apiUrlValue = host ? `https://${host}` : "https://<the company API host above>";
|
|
13001
|
+
const apiUrlValue = `https://${docsApiHost(apiUrl)}`;
|
|
12921
13002
|
return `## Codex cloud environment
|
|
12922
13003
|
|
|
12923
13004
|
When the person asks you to set up the Codex cloud environment for this
|
|
@@ -12964,14 +13045,13 @@ Publishing is their click.
|
|
|
12964
13045
|
`;
|
|
12965
13046
|
}
|
|
12966
13047
|
function claudeCloudNote(apiUrl) {
|
|
12967
|
-
const host = companyApiHost(apiUrl);
|
|
12968
13048
|
return `## Claude cloud
|
|
12969
13049
|
|
|
12970
13050
|
Claude Code on the web has no environment setup chat. It reads this repository
|
|
12971
13051
|
and \`CLAUDE.md\`, and \`.claude/settings.json\` runs \`scripts/cloud-setup.sh\`
|
|
12972
13052
|
when a cloud session starts. Node 22 or 24 both work. If
|
|
12973
13053
|
the environment has a network setting, allow the same domains as for Codex:
|
|
12974
|
-
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} and
|
|
13054
|
+
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} and \`${docsApiHost(apiUrl)}\`. No secrets.
|
|
12975
13055
|
`;
|
|
12976
13056
|
}
|
|
12977
13057
|
function agentsMd(opts) {
|
|
@@ -13215,15 +13295,17 @@ remain human actions. No Dashwise token is needed for
|
|
|
13215
13295
|
repository code delivery.
|
|
13216
13296
|
`;
|
|
13217
13297
|
}
|
|
13218
|
-
var NODE_ENGINES, DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, MODULE_SLUG_REGEX, CLOUD_ALLOWED_HOSTS, HOSTED_API_HOSTS;
|
|
13298
|
+
var NODE_ENGINES, DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL, GEIST_DEP_RANGE, MODULE_SLUG_REGEX, CLOUD_ALLOWED_HOSTS, HOSTED_API_HOSTS;
|
|
13219
13299
|
var init_scaffold = __esm({
|
|
13220
13300
|
"src/lib/scaffold.ts"() {
|
|
13301
|
+
init_config();
|
|
13221
13302
|
init_sdk_version();
|
|
13222
13303
|
init_simple();
|
|
13223
13304
|
init_custom();
|
|
13224
13305
|
NODE_ENGINES = "22.x || 24.x";
|
|
13225
13306
|
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
13226
13307
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
13308
|
+
GEIST_DEP_RANGE = "^1.7.2";
|
|
13227
13309
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
13228
13310
|
CLOUD_ALLOWED_HOSTS = [
|
|
13229
13311
|
"registry.npmjs.org",
|
|
@@ -14056,6 +14138,339 @@ var init_diff_check = __esm({
|
|
|
14056
14138
|
}
|
|
14057
14139
|
});
|
|
14058
14140
|
|
|
14141
|
+
// src/lib/permission-hint.ts
|
|
14142
|
+
function str(v) {
|
|
14143
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
14144
|
+
}
|
|
14145
|
+
function num(v) {
|
|
14146
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
14147
|
+
}
|
|
14148
|
+
function featureFromPermission(permission) {
|
|
14149
|
+
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
14150
|
+
return m?.[1];
|
|
14151
|
+
}
|
|
14152
|
+
function featureSubject(ctx) {
|
|
14153
|
+
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
14154
|
+
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
14155
|
+
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
14156
|
+
}
|
|
14157
|
+
function needsAdmin(what, module, ask) {
|
|
14158
|
+
const scope = module ? ` for ${module}` : "";
|
|
14159
|
+
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
14160
|
+
}
|
|
14161
|
+
function refusalHint(code2, context, subject = {}) {
|
|
14162
|
+
const ctx = context ?? {};
|
|
14163
|
+
const module = subject.module ?? str(ctx.module_slug);
|
|
14164
|
+
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
14165
|
+
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
14166
|
+
const named = module ?? "this module";
|
|
14167
|
+
const reason = str(ctx.reason);
|
|
14168
|
+
if (code2 === "MODULE_CAPABILITY_DENIED" && reason === "authoring_access_required") {
|
|
14169
|
+
return {
|
|
14170
|
+
message: "You need authoring access to export development data; ask someone who manages module access.",
|
|
14171
|
+
needsGrant: true
|
|
14172
|
+
};
|
|
14173
|
+
}
|
|
14174
|
+
if (code2 === "RAW_DATA_NOT_ALLOWED") {
|
|
14175
|
+
if (reason === "raw_data_write_required") {
|
|
14176
|
+
return {
|
|
14177
|
+
message: "You don't have access to change this data. Ask a data list Admin for Editor access in the Data Hub Permissions tab.",
|
|
14178
|
+
needsGrant: true
|
|
14179
|
+
};
|
|
14180
|
+
}
|
|
14181
|
+
if (reason === "installation_access_required") {
|
|
14182
|
+
return {
|
|
14183
|
+
message: "You cannot open this installation with your current access; ask someone who manages installation access.",
|
|
14184
|
+
needsGrant: true
|
|
14185
|
+
};
|
|
14186
|
+
}
|
|
14187
|
+
return {
|
|
14188
|
+
message: "You don't have access to view this data. Ask a data list Admin for Viewer access in the Data Hub Permissions tab.",
|
|
14189
|
+
needsGrant: true
|
|
14190
|
+
};
|
|
14191
|
+
}
|
|
14192
|
+
if (code2 === "VALIDATION_ERROR" && reason === "module_context_required") {
|
|
14193
|
+
return {
|
|
14194
|
+
message: "Open the intended module project and select the company and installation.",
|
|
14195
|
+
needsGrant: false
|
|
14196
|
+
};
|
|
14197
|
+
}
|
|
14198
|
+
if (code2 === "VALIDATION_ERROR" && reason === "context_mismatch") {
|
|
14199
|
+
return {
|
|
14200
|
+
message: "The selected company and installation do not match; check your selection.",
|
|
14201
|
+
needsGrant: false
|
|
14202
|
+
};
|
|
14203
|
+
}
|
|
14204
|
+
if (code2 === "UNAUTHENTICATED" && reason === "module_api_credential_missing") {
|
|
14205
|
+
return {
|
|
14206
|
+
message: "No module API key is configured; configure an existing key for the intended installation.",
|
|
14207
|
+
needsGrant: false
|
|
14208
|
+
};
|
|
14209
|
+
}
|
|
14210
|
+
switch (code2) {
|
|
14211
|
+
case "DRAFT_INSTALLATION_MISSING":
|
|
14212
|
+
case "DEV_INSTALLATION_MISSING":
|
|
14213
|
+
return {
|
|
14214
|
+
message: "This module has no dev installation; ask a module builder to create one.",
|
|
14215
|
+
needsGrant: false
|
|
14216
|
+
};
|
|
14217
|
+
case "MANIFEST_MANAGED_BY_REPOSITORY":
|
|
14218
|
+
return {
|
|
14219
|
+
message: "This module's column rules are managed in module.json. Change human_edit and human_edit_reason there, then build and release the module.",
|
|
14220
|
+
needsGrant: false
|
|
14221
|
+
};
|
|
14222
|
+
case "NOT_FOUND":
|
|
14223
|
+
case "MODULE_NOT_FOUND":
|
|
14224
|
+
return { message: "Not found; check your selection or ask for access.", needsGrant: false };
|
|
14225
|
+
case "UNAUTHORIZED":
|
|
14226
|
+
case "UNAUTHENTICATED":
|
|
14227
|
+
case "TOKEN_EXPIRED":
|
|
14228
|
+
return { message: "Run dashwise login, then retry.", needsGrant: false };
|
|
14229
|
+
case "FEATURE_DISABLED":
|
|
14230
|
+
case "PERMISSION_DENIED": {
|
|
14231
|
+
const what = featureSubject(ctx);
|
|
14232
|
+
if (!what) {
|
|
14233
|
+
return {
|
|
14234
|
+
message: `You don't have permission to do this${module ? ` for ${module}` : ""}. Ask your Dashwise admin for access.`,
|
|
14235
|
+
needsGrant: true
|
|
14236
|
+
};
|
|
14237
|
+
}
|
|
14238
|
+
return { message: needsAdmin(what, module, "enable it"), needsGrant: true };
|
|
14239
|
+
}
|
|
14240
|
+
case "RUNTIME_OPTION_OPERATOR_ONLY": {
|
|
14241
|
+
const option = str(ctx.option);
|
|
14242
|
+
const what = (option && OPTION_SUBJECTS[option]) ?? (option ? `"${option}"` : "This runtime option");
|
|
14243
|
+
return { message: needsAdmin(what, module, "change it"), needsGrant: true };
|
|
14244
|
+
}
|
|
14245
|
+
case REBUILD_BUMP_OPERATOR_ONLY: {
|
|
14246
|
+
const bump = str(ctx.bump) ?? "minor";
|
|
14247
|
+
return {
|
|
14248
|
+
message: needsAdmin(`A rebuild with a ${bump} version bump`, module, "run it"),
|
|
14249
|
+
needsGrant: true
|
|
14250
|
+
};
|
|
14251
|
+
}
|
|
14252
|
+
case "IMAGE_TAG_EXISTS":
|
|
14253
|
+
return {
|
|
14254
|
+
message: `This rebuild of ${named} needs a higher version bump than patch. A rebuild with a minor version bump needs a Dashwise admin. Ask your Dashwise admin to run it.`,
|
|
14255
|
+
needsGrant: true
|
|
14256
|
+
};
|
|
14257
|
+
case "RUNTIME_IMAGE_NOT_ELIGIBLE": {
|
|
14258
|
+
const version = str(ctx.version);
|
|
14259
|
+
const target = installationId !== void 0 ? ` --rollout installs:${installationId}` : "";
|
|
14260
|
+
return {
|
|
14261
|
+
message: `${named}${version ? ` v${version}` : ""} was built before streaming support. Rebuild it and upgrade this install first: dashwise module rebuild${module ? ` ${module}` : ""}${target}`,
|
|
14262
|
+
needsGrant: false
|
|
14263
|
+
};
|
|
14264
|
+
}
|
|
14265
|
+
case "RUNTIME_SWAP_PENDING": {
|
|
14266
|
+
const version = str(ctx.version);
|
|
14267
|
+
return {
|
|
14268
|
+
message: `${named}${installLabel} is still finishing its update${version ? ` to v${version}` : ""}, so streaming cannot be turned on yet. Try again in a few minutes.`,
|
|
14269
|
+
needsGrant: false
|
|
14270
|
+
};
|
|
14271
|
+
}
|
|
14272
|
+
case "RUNTIME_OPTION_UNAVAILABLE": {
|
|
14273
|
+
const option = str(ctx.option);
|
|
14274
|
+
const what = (option && OPTION_SUBJECTS[option]) ?? "This runtime option";
|
|
14275
|
+
return { message: `${what} is switched off platform-wide right now.`, needsGrant: false };
|
|
14276
|
+
}
|
|
14277
|
+
case "RUNTIME_CHANGE_IN_PROGRESS": {
|
|
14278
|
+
const op = num(ctx.operation_id);
|
|
14279
|
+
return {
|
|
14280
|
+
message: `A runtime change is already being applied to ${named}${op !== void 0 ? ` (operation #${op})` : ""}. Wait for it, then check: dashwise module runtime${module ? ` ${module}` : ""}`,
|
|
14281
|
+
needsGrant: false
|
|
14282
|
+
};
|
|
14283
|
+
}
|
|
14284
|
+
case "BUILD_IN_PROGRESS": {
|
|
14285
|
+
const buildId = num(ctx.build_id);
|
|
14286
|
+
const status = str(ctx.status);
|
|
14287
|
+
const which = buildId !== void 0 ? ` (#${buildId}${status ? `, ${status}` : ""})` : "";
|
|
14288
|
+
return {
|
|
14289
|
+
message: `A build of ${named} is already running${which}. Wait for it to finish, then run the rebuild again.`,
|
|
14290
|
+
needsGrant: false
|
|
14291
|
+
};
|
|
14292
|
+
}
|
|
14293
|
+
case "REBUILD_SOURCE_NOT_FOUND":
|
|
14294
|
+
return {
|
|
14295
|
+
message: `${named} has no build from source to rebuild. Publish it first: dashwise module publish`,
|
|
14296
|
+
needsGrant: false
|
|
14297
|
+
};
|
|
14298
|
+
case "INSTALLATION_NOT_FOUND":
|
|
14299
|
+
return {
|
|
14300
|
+
message: `${installationId !== void 0 ? `Installation #${installationId}` : "The installation"} was not found, or you cannot see it.`,
|
|
14301
|
+
needsGrant: false
|
|
14302
|
+
};
|
|
14303
|
+
case "PUBLISH_RATE_LIMITED":
|
|
14304
|
+
case "PUBLISH_USER_RATE_LIMITED":
|
|
14305
|
+
return {
|
|
14306
|
+
message: `Too many builds of ${named} in the last hour. Try again later.`,
|
|
14307
|
+
needsGrant: false
|
|
14308
|
+
};
|
|
14309
|
+
// Install invitations (`dashwise module invites`, Südbau retro 6).
|
|
14310
|
+
case "AUTHOR_ADMIN_REQUIRED":
|
|
14311
|
+
return {
|
|
14312
|
+
message: `Only an admin of the company that owns ${named} can manage its install invitations. Ask that company's admin.`,
|
|
14313
|
+
needsGrant: true
|
|
14314
|
+
};
|
|
14315
|
+
case "COMPANY_ADMIN_REQUIRED":
|
|
14316
|
+
return {
|
|
14317
|
+
message: "Only an admin of this company can see or decline its install invitations. Ask your company's admin.",
|
|
14318
|
+
needsGrant: true
|
|
14319
|
+
};
|
|
14320
|
+
case "MODULE_PRIVATE_SHARE_FIRST":
|
|
14321
|
+
return {
|
|
14322
|
+
message: `${named} is still private. Share it with your company in Dashwise first, then invite another company.`,
|
|
14323
|
+
needsGrant: false
|
|
14324
|
+
};
|
|
14325
|
+
default:
|
|
14326
|
+
if (MODULE_RIGHT_CODES.has(code2)) {
|
|
14327
|
+
return {
|
|
14328
|
+
message: `You can't manage ${named}${installLabel}. Ask your Dashwise admin for access.`,
|
|
14329
|
+
needsGrant: true
|
|
14330
|
+
};
|
|
14331
|
+
}
|
|
14332
|
+
return null;
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
function reportCliError(message, opts) {
|
|
14336
|
+
if (opts.json) {
|
|
14337
|
+
out(
|
|
14338
|
+
JSON.stringify({
|
|
14339
|
+
error: {
|
|
14340
|
+
code: opts.code ?? "CLI_ERROR",
|
|
14341
|
+
message: message.replace(ANSI, ""),
|
|
14342
|
+
status: null,
|
|
14343
|
+
context: null
|
|
14344
|
+
},
|
|
14345
|
+
hint: null,
|
|
14346
|
+
needs_grant: false
|
|
14347
|
+
})
|
|
14348
|
+
);
|
|
14349
|
+
} else {
|
|
14350
|
+
fail(message);
|
|
14351
|
+
}
|
|
14352
|
+
return 1;
|
|
14353
|
+
}
|
|
14354
|
+
function reportFailedOutcome(failure, hint, opts) {
|
|
14355
|
+
if (opts.json) {
|
|
14356
|
+
out(
|
|
14357
|
+
JSON.stringify({
|
|
14358
|
+
error: {
|
|
14359
|
+
code: failure.code,
|
|
14360
|
+
message: failure.message ?? null,
|
|
14361
|
+
status: null,
|
|
14362
|
+
context: null
|
|
14363
|
+
},
|
|
14364
|
+
hint: hint?.message ?? null,
|
|
14365
|
+
needs_grant: hint?.needsGrant ?? false,
|
|
14366
|
+
...opts.extra
|
|
14367
|
+
})
|
|
14368
|
+
);
|
|
14369
|
+
} else {
|
|
14370
|
+
fail(hint?.message ?? opts.fallback);
|
|
14371
|
+
}
|
|
14372
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14373
|
+
}
|
|
14374
|
+
function isTransientPollError(err) {
|
|
14375
|
+
if (!(err instanceof ApiError)) return true;
|
|
14376
|
+
return err.status === 0 || err.status === 429 || err.status >= 500;
|
|
14377
|
+
}
|
|
14378
|
+
function isTypedRequestError(err) {
|
|
14379
|
+
if (err instanceof ApiError) return true;
|
|
14380
|
+
if (!(err instanceof Error)) return false;
|
|
14381
|
+
const candidate = err;
|
|
14382
|
+
return typeof candidate.code === "string" && typeof candidate.status === "number" && (candidate.context === void 0 || candidate.context !== null && typeof candidate.context === "object" && !Array.isArray(candidate.context));
|
|
14383
|
+
}
|
|
14384
|
+
function reportRefusal(err, subject, opts) {
|
|
14385
|
+
const writeJson = (message) => {
|
|
14386
|
+
if (opts.stderr) process.stderr.write(`${message}
|
|
14387
|
+
`);
|
|
14388
|
+
else out(message);
|
|
14389
|
+
};
|
|
14390
|
+
if (!isTypedRequestError(err)) {
|
|
14391
|
+
const message = err?.message ?? String(err);
|
|
14392
|
+
if (opts.json) {
|
|
14393
|
+
writeJson(
|
|
14394
|
+
JSON.stringify({
|
|
14395
|
+
error: {
|
|
14396
|
+
code: "CLI_ERROR",
|
|
14397
|
+
message: message.replace(ANSI, ""),
|
|
14398
|
+
status: null,
|
|
14399
|
+
context: null
|
|
14400
|
+
},
|
|
14401
|
+
hint: null,
|
|
14402
|
+
needs_grant: false
|
|
14403
|
+
})
|
|
14404
|
+
);
|
|
14405
|
+
} else {
|
|
14406
|
+
fail(`${opts.action} failed: ${message}`);
|
|
14407
|
+
}
|
|
14408
|
+
return 1;
|
|
14409
|
+
}
|
|
14410
|
+
const hint = err.status === 401 && err.context?.reason !== "module_api_credential_missing" ? {
|
|
14411
|
+
message: opts.moduleKey ? MODULE_KEY_REFUSED[opts.moduleKey] : "Your login has expired. Run: dashwise login",
|
|
14412
|
+
needsGrant: false
|
|
14413
|
+
} : refusalHint(err.code, err.context, subject);
|
|
14414
|
+
if (opts.json) {
|
|
14415
|
+
writeJson(
|
|
14416
|
+
JSON.stringify({
|
|
14417
|
+
error: {
|
|
14418
|
+
code: err.code,
|
|
14419
|
+
message: err.message,
|
|
14420
|
+
status: err.status,
|
|
14421
|
+
context: err.context ?? null
|
|
14422
|
+
},
|
|
14423
|
+
hint: hint?.message ?? null,
|
|
14424
|
+
needs_grant: hint?.needsGrant ?? false
|
|
14425
|
+
})
|
|
14426
|
+
);
|
|
14427
|
+
} else if (hint) {
|
|
14428
|
+
fail(
|
|
14429
|
+
`${hint.message} (${err.code})${err.context ? ` context=${JSON.stringify(err.context)}` : ""}`
|
|
14430
|
+
);
|
|
14431
|
+
} else {
|
|
14432
|
+
fail(
|
|
14433
|
+
`${opts.action} failed (${err.code}): ${err.message}${err.context ? ` context=${JSON.stringify(err.context)}` : ""}`
|
|
14434
|
+
);
|
|
14435
|
+
}
|
|
14436
|
+
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
14437
|
+
}
|
|
14438
|
+
var EXIT_NEEDS_GRANT, EXIT_TIMED_OUT, REBUILD_BUMP_OPERATOR_ONLY, FEATURE_SUBJECTS, OPTION_SUBJECTS, MODULE_RIGHT_CODES, ANSI, MODULE_KEY_REFUSED;
|
|
14439
|
+
var init_permission_hint = __esm({
|
|
14440
|
+
"src/lib/permission-hint.ts"() {
|
|
14441
|
+
init_api_client();
|
|
14442
|
+
init_output();
|
|
14443
|
+
EXIT_NEEDS_GRANT = 3;
|
|
14444
|
+
EXIT_TIMED_OUT = 4;
|
|
14445
|
+
REBUILD_BUMP_OPERATOR_ONLY = "REBUILD_BUMP_OPERATOR_ONLY";
|
|
14446
|
+
FEATURE_SUBJECTS = {
|
|
14447
|
+
module_runtime_streaming: "Streaming",
|
|
14448
|
+
module_rebuild: "Rebuild",
|
|
14449
|
+
module_building: "Building modules",
|
|
14450
|
+
module_runtime_sizing: "Memory size"
|
|
14451
|
+
};
|
|
14452
|
+
OPTION_SUBJECTS = {
|
|
14453
|
+
streaming: "Streaming",
|
|
14454
|
+
memory_mb: "Memory size",
|
|
14455
|
+
reapply: "Re-applying the runtime",
|
|
14456
|
+
timeout: "The timeout",
|
|
14457
|
+
reserved_concurrency: "Reserved concurrency",
|
|
14458
|
+
log_retention: "Log retention"
|
|
14459
|
+
};
|
|
14460
|
+
MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
14461
|
+
"MODULE_CAPABILITY_DENIED",
|
|
14462
|
+
"PUBLISH_REQUIRES_ADMIN",
|
|
14463
|
+
"NOT_COPY_OWNER"
|
|
14464
|
+
]);
|
|
14465
|
+
ANSI = /\x1b\[[0-9;]*m/g;
|
|
14466
|
+
MODULE_KEY_REFUSED = {
|
|
14467
|
+
env: "The module key in DASHWISE_API_KEY was refused (expired or revoked). Replace it with a current key, or unset it to use this module's saved key. dashwise login does not renew module keys.",
|
|
14468
|
+
env_file: "The module key in .env.local (DASHWISE_API_KEY) was refused (expired or revoked). Replace it, or remove that line to use this module's saved key. dashwise login does not renew module keys.",
|
|
14469
|
+
saved: `This module's saved key was refused (expired or revoked). In your CLI auth.json, set only modules[<slug>].apiKey to an empty string (""), keeping installationId and all other fields. Then run dashwise dev from this module's directory with DASHWISE_API_KEY unset to mint and save a replacement key for the same installation. dashwise login does not renew module keys.`
|
|
14470
|
+
};
|
|
14471
|
+
}
|
|
14472
|
+
});
|
|
14473
|
+
|
|
14059
14474
|
// src/lib/manifest-reference-lint.ts
|
|
14060
14475
|
function referenceFieldWarnings(manifest) {
|
|
14061
14476
|
const tables = manifest?.owns?.tables;
|
|
@@ -14394,7 +14809,7 @@ async function destroyDevInstallCommand(options) {
|
|
|
14394
14809
|
return 0;
|
|
14395
14810
|
}
|
|
14396
14811
|
if (!options.yes && process.stdout.isTTY) {
|
|
14397
|
-
const choice = await confirm({
|
|
14812
|
+
const choice = await confirm$1({
|
|
14398
14813
|
message: `Destroy dev install ${code(`${slug} (id=${entry.installationId})`)}? Tables and rows will be soft-deleted (recoverable for 30 days via dashboard trash).`
|
|
14399
14814
|
});
|
|
14400
14815
|
if (isCancel(choice) || !choice) {
|
|
@@ -14532,48 +14947,58 @@ __export(push_exports, {
|
|
|
14532
14947
|
schemaPushCommand: () => schemaPushCommand
|
|
14533
14948
|
});
|
|
14534
14949
|
async function schemaPushCommand(options) {
|
|
14950
|
+
const report = (err) => reportRefusal(err, {}, { ...options, stderr: true, action: "Schema push" });
|
|
14951
|
+
const contextError = (message) => report(
|
|
14952
|
+
new ApiError(
|
|
14953
|
+
0,
|
|
14954
|
+
{
|
|
14955
|
+
code: "VALIDATION_ERROR",
|
|
14956
|
+
message,
|
|
14957
|
+
context: { reason: "module_context_required" }
|
|
14958
|
+
},
|
|
14959
|
+
message
|
|
14960
|
+
)
|
|
14961
|
+
);
|
|
14535
14962
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
14536
14963
|
let manifest;
|
|
14537
14964
|
try {
|
|
14538
14965
|
manifest = readManifestOrThrow(projectRoot);
|
|
14539
14966
|
} catch (err) {
|
|
14540
|
-
|
|
14541
|
-
return 1;
|
|
14967
|
+
return contextError(err.message);
|
|
14542
14968
|
}
|
|
14543
14969
|
const slug = manifest.module?.slug;
|
|
14544
14970
|
if (!slug) {
|
|
14545
|
-
|
|
14546
|
-
return 1;
|
|
14971
|
+
return contextError("module.json has no module.slug.");
|
|
14547
14972
|
}
|
|
14548
14973
|
const cfg = readConfig();
|
|
14549
14974
|
if (!cfg) {
|
|
14550
|
-
|
|
14551
|
-
|
|
14975
|
+
return report(
|
|
14976
|
+
new ApiError(401, { code: "UNAUTHENTICATED", message: "Not logged in." }, "Not logged in.")
|
|
14977
|
+
);
|
|
14552
14978
|
}
|
|
14553
14979
|
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
14554
14980
|
let moduleId;
|
|
14555
14981
|
try {
|
|
14556
14982
|
moduleId = (await getModuleBySlug(slug, auth)).id;
|
|
14557
14983
|
} catch (err) {
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
|
|
14562
|
-
|
|
14563
|
-
}
|
|
14564
|
-
fail(`Could not resolve module ${code(slug)}: ${err.message}`);
|
|
14565
|
-
return 1;
|
|
14984
|
+
return reportRefusal(
|
|
14985
|
+
err,
|
|
14986
|
+
{ module: slug },
|
|
14987
|
+
{ ...options, stderr: true, action: "Resolve module" }
|
|
14988
|
+
);
|
|
14566
14989
|
}
|
|
14567
14990
|
const entry = readModuleEntry(slug);
|
|
14568
14991
|
const dwkAuth = entry?.apiKey ? { baseUrl: resolveApiUrl(), auth: { token: entry.apiKey } } : null;
|
|
14569
14992
|
let remote = null;
|
|
14570
14993
|
let previewUnavailableReason = null;
|
|
14994
|
+
let previewError = null;
|
|
14571
14995
|
if (!dwkAuth) {
|
|
14572
|
-
previewUnavailableReason = `no dev-install
|
|
14996
|
+
previewUnavailableReason = `no dev-install dwk_ key is cached for ${slug} in auth.json`;
|
|
14573
14997
|
} else {
|
|
14574
14998
|
try {
|
|
14575
14999
|
remote = (await getInstallationSchema(dwkAuth)).manifest;
|
|
14576
15000
|
} catch (err) {
|
|
15001
|
+
previewError = err;
|
|
14577
15002
|
const detail = err instanceof ApiError ? `${err.code} ${err.status}: ${err.message}` : err.message;
|
|
14578
15003
|
previewUnavailableReason = `reading the dev-install manifest failed (${detail}) \u2014 the cached key may be expired or revoked`;
|
|
14579
15004
|
}
|
|
@@ -14613,15 +15038,32 @@ async function schemaPushCommand(options) {
|
|
|
14613
15038
|
}
|
|
14614
15039
|
}
|
|
14615
15040
|
} else {
|
|
14616
|
-
|
|
14617
|
-
|
|
15041
|
+
if (!options.json) {
|
|
15042
|
+
warn(
|
|
15043
|
+
`Cannot preview what this push would change: ${previewUnavailableReason}.
|
|
14618
15044
|
Without the preview there is no way to know whether the push REMOVES tables/fields (live DDL on the dev install).`
|
|
14619
|
-
|
|
15045
|
+
);
|
|
15046
|
+
}
|
|
14620
15047
|
if (!options.yes) {
|
|
14621
|
-
|
|
14622
|
-
|
|
15048
|
+
if (previewError instanceof ApiError && [401, 403, 404].includes(previewError.status)) {
|
|
15049
|
+
return reportRefusal(
|
|
15050
|
+
previewError,
|
|
15051
|
+
{ module: slug },
|
|
15052
|
+
{ ...options, stderr: true, action: "Schema preview", moduleKey: "saved" }
|
|
15053
|
+
);
|
|
15054
|
+
}
|
|
15055
|
+
const message = `Refusing to push without the destructive-change preview (${previewUnavailableReason}). Re-run with --yes to push anyway (the backend still validates the apply).`;
|
|
15056
|
+
return report(
|
|
15057
|
+
new ApiError(
|
|
15058
|
+
0,
|
|
15059
|
+
{
|
|
15060
|
+
code: "PREVIEW_UNAVAILABLE",
|
|
15061
|
+
message,
|
|
15062
|
+
context: { reason: dwkAuth ? "preview_read_failed" : "module_key_missing" }
|
|
15063
|
+
},
|
|
15064
|
+
message
|
|
15065
|
+
)
|
|
14623
15066
|
);
|
|
14624
|
-
return 1;
|
|
14625
15067
|
}
|
|
14626
15068
|
info(
|
|
14627
15069
|
dim(
|
|
@@ -14638,7 +15080,7 @@ async function schemaPushCommand(options) {
|
|
|
14638
15080
|
auth
|
|
14639
15081
|
);
|
|
14640
15082
|
} catch (err) {
|
|
14641
|
-
return surfaceSchemaPushError(err, slug);
|
|
15083
|
+
return surfaceSchemaPushError(err, slug, options.json);
|
|
14642
15084
|
}
|
|
14643
15085
|
printApplySummary(applied);
|
|
14644
15086
|
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
@@ -14715,21 +15157,23 @@ function strProp(v, key) {
|
|
|
14715
15157
|
}
|
|
14716
15158
|
return void 0;
|
|
14717
15159
|
}
|
|
14718
|
-
function surfaceSchemaPushError(err, slug) {
|
|
15160
|
+
function surfaceSchemaPushError(err, slug, json) {
|
|
15161
|
+
if (json || !(err instanceof ApiError) || refusalHint(err.code, err.context, { module: slug }) || err.status === 401) {
|
|
15162
|
+
return reportRefusal(err, { module: slug }, { json, stderr: true, action: "Schema push" });
|
|
15163
|
+
}
|
|
15164
|
+
process.stderr.write(
|
|
15165
|
+
`(${err.code})${err.context ? ` context=${JSON.stringify(err.context)}` : ""}
|
|
15166
|
+
`
|
|
15167
|
+
);
|
|
14719
15168
|
if (err instanceof ApiError) {
|
|
14720
15169
|
switch (err.code) {
|
|
14721
15170
|
case "MANIFEST_INVALID":
|
|
14722
15171
|
fail(`Manifest rejected by the backend validator: ${err.message}`);
|
|
14723
15172
|
for (const issue of extractIssues2(err.context)) {
|
|
14724
|
-
|
|
15173
|
+
process.stderr.write(` ${dim("-")} ${issue}
|
|
15174
|
+
`);
|
|
14725
15175
|
}
|
|
14726
15176
|
return 1;
|
|
14727
|
-
case "DRAFT_INSTALLATION_MISSING":
|
|
14728
|
-
fail(
|
|
14729
|
-
`Module ${code(slug)} has no dev (local/sandbox) install in its owner workspace \u2014 production installs have NO draft plane, so there is nothing to push onto.
|
|
14730
|
-
This happens after ${code("dashwise module install --replace-dev-install")}. Schema changes now ship as published versions: edit module.json, ${code("dashwise module publish --bump <patch|minor|major>")}, then ${code("dashwise module install")} to upgrade.`
|
|
14731
|
-
);
|
|
14732
|
-
return 1;
|
|
14733
15177
|
case "UNIQUE_INDEX_BACKFILL_CONFLICT":
|
|
14734
15178
|
fail(
|
|
14735
15179
|
`Cannot create a UNIQUE index \u2014 existing live rows already violate it: ${err.message}
|
|
@@ -14750,14 +15194,15 @@ function surfaceSchemaPushError(err, slug) {
|
|
|
14750
15194
|
case "FORBIDDEN":
|
|
14751
15195
|
case "NOT_WORKSPACE_MEMBER":
|
|
14752
15196
|
fail(
|
|
14753
|
-
`Not authorized: pushing a draft manifest requires membership of the module's owner
|
|
15197
|
+
`Not authorized: pushing a draft manifest requires membership of the module's owner company.`
|
|
14754
15198
|
);
|
|
14755
15199
|
return 1;
|
|
14756
15200
|
default:
|
|
14757
15201
|
if (err.status === 400) {
|
|
14758
15202
|
fail(`Manifest rejected: ${err.message}`);
|
|
14759
15203
|
for (const issue of extractIssues2(err.context)) {
|
|
14760
|
-
|
|
15204
|
+
process.stderr.write(` ${dim("-")} ${issue}
|
|
15205
|
+
`);
|
|
14761
15206
|
}
|
|
14762
15207
|
return 1;
|
|
14763
15208
|
}
|
|
@@ -14789,6 +15234,7 @@ var init_push = __esm({
|
|
|
14789
15234
|
init_config();
|
|
14790
15235
|
init_manifest_io();
|
|
14791
15236
|
init_manifest_merge();
|
|
15237
|
+
init_permission_hint();
|
|
14792
15238
|
init_output();
|
|
14793
15239
|
init_manifest_reference_lint();
|
|
14794
15240
|
}
|
|
@@ -15965,29 +16411,11 @@ async function runDoctorChecks(dir, deps) {
|
|
|
15965
16411
|
return checks;
|
|
15966
16412
|
}
|
|
15967
16413
|
function targetCheck(apiUrl) {
|
|
15968
|
-
const source = process.env.DASHWISE_API_URL?.trim() ? "from DASHWISE_API_URL" : "from your saved login";
|
|
15969
|
-
if (sameOrigin(apiUrl, KNOWN_API_URLS.prod)) {
|
|
15970
|
-
return {
|
|
15971
|
-
id: "target",
|
|
15972
|
-
label: "Target",
|
|
15973
|
-
status: "warn",
|
|
15974
|
-
detail: `PRODUCTION (${apiUrl}, ${source}). Commands here change real company data.`,
|
|
15975
|
-
fix: `To build against dev, run ${code(`dashwise login --api-url ${KNOWN_API_URLS.dev}`)} or set ${code(`DASHWISE_API_URL=${KNOWN_API_URLS.dev}`)}.`
|
|
15976
|
-
};
|
|
15977
|
-
}
|
|
15978
|
-
if (sameOrigin(apiUrl, KNOWN_API_URLS.dev)) {
|
|
15979
|
-
return {
|
|
15980
|
-
id: "target",
|
|
15981
|
-
label: "Target",
|
|
15982
|
-
status: "ok",
|
|
15983
|
-
detail: `dev (${apiUrl}, ${source})`
|
|
15984
|
-
};
|
|
15985
|
-
}
|
|
15986
16414
|
return {
|
|
15987
16415
|
id: "target",
|
|
15988
16416
|
label: "Target",
|
|
15989
16417
|
status: "ok",
|
|
15990
|
-
detail:
|
|
16418
|
+
detail: apiUrl
|
|
15991
16419
|
};
|
|
15992
16420
|
}
|
|
15993
16421
|
async function connectionOnly(apiUrl) {
|
|
@@ -16168,13 +16596,6 @@ function sameRepo(a, b) {
|
|
|
16168
16596
|
const ka = repoKey(a);
|
|
16169
16597
|
return ka !== null && ka === repoKey(b);
|
|
16170
16598
|
}
|
|
16171
|
-
function sameOrigin(a, b) {
|
|
16172
|
-
try {
|
|
16173
|
-
return new URL(a).origin === new URL(b).origin;
|
|
16174
|
-
} catch {
|
|
16175
|
-
return false;
|
|
16176
|
-
}
|
|
16177
|
-
}
|
|
16178
16599
|
function safeReadConfig2() {
|
|
16179
16600
|
try {
|
|
16180
16601
|
return readConfig();
|
|
@@ -16335,7 +16756,7 @@ function startMcpProxy(opts) {
|
|
|
16335
16756
|
const reconnectMs = opts.reconnectMs ?? 2e3;
|
|
16336
16757
|
const verbose = opts.verbose === true;
|
|
16337
16758
|
const readOnly = opts.readOnly === true;
|
|
16338
|
-
let
|
|
16759
|
+
let session2 = opts.session;
|
|
16339
16760
|
let sessionId = null;
|
|
16340
16761
|
let protocolVersion = null;
|
|
16341
16762
|
let readTools = null;
|
|
@@ -16364,7 +16785,7 @@ function startMcpProxy(opts) {
|
|
|
16364
16785
|
parsed = JSON.parse(text4);
|
|
16365
16786
|
} catch {
|
|
16366
16787
|
log2(
|
|
16367
|
-
`Discarded a non-JSON response from ${redactUrl(
|
|
16788
|
+
`Discarded a non-JSON response from ${redactUrl(session2.connectUrl)} (${source}); stdout carries MCP frames only.`
|
|
16368
16789
|
);
|
|
16369
16790
|
trace(`discarded ${Buffer.byteLength(text4, "utf8")} bytes of non-JSON`);
|
|
16370
16791
|
return;
|
|
@@ -16374,7 +16795,7 @@ function startMcpProxy(opts) {
|
|
|
16374
16795
|
) : parsed !== null && typeof parsed === "object" && "jsonrpc" in parsed;
|
|
16375
16796
|
if (!looksJsonRpc) {
|
|
16376
16797
|
log2(
|
|
16377
|
-
`Discarded a JSON response from ${redactUrl(
|
|
16798
|
+
`Discarded a JSON response from ${redactUrl(session2.connectUrl)} that is not JSON-RPC (${source}).`
|
|
16378
16799
|
);
|
|
16379
16800
|
return;
|
|
16380
16801
|
}
|
|
@@ -16405,7 +16826,7 @@ function startMcpProxy(opts) {
|
|
|
16405
16826
|
reauthAttempt = (async () => {
|
|
16406
16827
|
log2("The session key expired or was rotated \u2014 re-authenticating once.");
|
|
16407
16828
|
try {
|
|
16408
|
-
|
|
16829
|
+
session2 = await reauth();
|
|
16409
16830
|
sessionId = null;
|
|
16410
16831
|
return true;
|
|
16411
16832
|
} catch (err) {
|
|
@@ -16446,7 +16867,7 @@ function startMcpProxy(opts) {
|
|
|
16446
16867
|
};
|
|
16447
16868
|
const post = async (text4, isInitialize) => {
|
|
16448
16869
|
if (stopped) return null;
|
|
16449
|
-
return doFetch(
|
|
16870
|
+
return doFetch(session2.connectUrl, {
|
|
16450
16871
|
method: "POST",
|
|
16451
16872
|
headers: requestHeaders(isInitialize),
|
|
16452
16873
|
body: text4
|
|
@@ -16474,7 +16895,7 @@ function startMcpProxy(opts) {
|
|
|
16474
16895
|
res = await post(text4, isInitialize);
|
|
16475
16896
|
} catch (err) {
|
|
16476
16897
|
const message = err.message;
|
|
16477
|
-
log2(`Transport error talking to ${redactUrl(
|
|
16898
|
+
log2(`Transport error talking to ${redactUrl(session2.connectUrl)}: ${message}`);
|
|
16478
16899
|
emit(transportErrorFrame(frame?.id ?? null, `Transport error: ${message}`));
|
|
16479
16900
|
return { kind: "failed" };
|
|
16480
16901
|
}
|
|
@@ -16498,7 +16919,7 @@ function startMcpProxy(opts) {
|
|
|
16498
16919
|
const bodyText = await res.text();
|
|
16499
16920
|
if (!res.ok) {
|
|
16500
16921
|
const detail = res.status === 401 ? "the endpoint key is no longer valid (expired, rotated by another `dashwise mcp`, or revoked)" : `HTTP ${res.status}`;
|
|
16501
|
-
log2(`Refused by ${redactUrl(
|
|
16922
|
+
log2(`Refused by ${redactUrl(session2.connectUrl)}: ${detail}`);
|
|
16502
16923
|
emit(transportErrorFrame(frame?.id ?? null, `Dashwise MCP server: ${detail}`));
|
|
16503
16924
|
if (res.status === 401) {
|
|
16504
16925
|
exitCode = 1;
|
|
@@ -16585,7 +17006,7 @@ function startMcpProxy(opts) {
|
|
|
16585
17006
|
streamAbort = new AbortController();
|
|
16586
17007
|
let res;
|
|
16587
17008
|
try {
|
|
16588
|
-
res = await doFetch(
|
|
17009
|
+
res = await doFetch(session2.connectUrl, {
|
|
16589
17010
|
method: "GET",
|
|
16590
17011
|
headers: {
|
|
16591
17012
|
Accept: "text/event-stream",
|
|
@@ -16621,7 +17042,7 @@ function startMcpProxy(opts) {
|
|
|
16621
17042
|
}
|
|
16622
17043
|
streamUnavailable = true;
|
|
16623
17044
|
log2(
|
|
16624
|
-
`Server stream refused by ${redactUrl(
|
|
17045
|
+
`Server stream refused by ${redactUrl(session2.connectUrl)}: the session could not be renewed (the endpoint key expired, was rotated, or was revoked).`
|
|
16625
17046
|
);
|
|
16626
17047
|
emit(
|
|
16627
17048
|
transportErrorFrame(
|
|
@@ -16898,9 +17319,9 @@ async function mcpCommand(options = {}) {
|
|
|
16898
17319
|
return 1;
|
|
16899
17320
|
}
|
|
16900
17321
|
const label = options.endpointName ?? defaultEndpointLabel();
|
|
16901
|
-
let
|
|
17322
|
+
let session2;
|
|
16902
17323
|
try {
|
|
16903
|
-
|
|
17324
|
+
session2 = await openCliSession({
|
|
16904
17325
|
workspaceId: workspace2.id,
|
|
16905
17326
|
label,
|
|
16906
17327
|
...scopes !== void 0 ? { scopes } : {}
|
|
@@ -16909,24 +17330,24 @@ async function mcpCommand(options = {}) {
|
|
|
16909
17330
|
fail(describeSessionError(err, apiUrl, scopes));
|
|
16910
17331
|
return 1;
|
|
16911
17332
|
}
|
|
16912
|
-
if (scopes !== void 0 && !Array.isArray(
|
|
16913
|
-
await revokeSession(
|
|
17333
|
+
if (scopes !== void 0 && !Array.isArray(session2.scopes)) {
|
|
17334
|
+
await revokeSession(session2.endpointId);
|
|
16914
17335
|
fail(SCOPES_NOT_APPLIED);
|
|
16915
17336
|
return 1;
|
|
16916
17337
|
}
|
|
16917
17338
|
info(
|
|
16918
|
-
`Dashwise MCP \u2014 workspace ${workspace2.slug}, endpoint #${
|
|
17339
|
+
`Dashwise MCP \u2014 workspace ${workspace2.slug}, endpoint #${session2.endpointId} (${session2.name}).`
|
|
16919
17340
|
);
|
|
16920
|
-
info(`Session key expires ${dim(
|
|
16921
|
-
info(describeScopes(
|
|
17341
|
+
info(`Session key expires ${dim(session2.expiresAt)}.`);
|
|
17342
|
+
info(describeScopes(session2.scopes));
|
|
16922
17343
|
if (options.readOnly) {
|
|
16923
17344
|
warn(
|
|
16924
17345
|
"Read-only: write tools are refused by this proxy (a client-side guard, not a server-enforced scope \u2014 for that, use --scope)."
|
|
16925
17346
|
);
|
|
16926
17347
|
}
|
|
16927
|
-
let ownedEndpointId =
|
|
17348
|
+
let ownedEndpointId = session2.endpointId;
|
|
16928
17349
|
const proxy = startMcpProxy({
|
|
16929
|
-
session,
|
|
17350
|
+
session: session2,
|
|
16930
17351
|
reauth: async () => {
|
|
16931
17352
|
const fresh = await openCliSession({
|
|
16932
17353
|
workspaceId: workspace2.id,
|
|
@@ -17939,7 +18360,9 @@ function convertModule(opts) {
|
|
|
17939
18360
|
"app/layout.jsx",
|
|
17940
18361
|
"src/app/layout.tsx"
|
|
17941
18362
|
].find((f) => existsSync(join(dir, f)));
|
|
18363
|
+
let layoutCreated = false;
|
|
17942
18364
|
if (!layoutFile) {
|
|
18365
|
+
layoutCreated = true;
|
|
17943
18366
|
writeIfMissing("app/layout.tsx", appLayoutCustomTsx(sopts));
|
|
17944
18367
|
} else if (!readFileSync(join(dir, layoutFile), "utf-8").includes("AuthProvider")) {
|
|
17945
18368
|
warnings.push(
|
|
@@ -17955,10 +18378,13 @@ function convertModule(opts) {
|
|
|
17955
18378
|
`${nextConfigFile}: add \`output: 'standalone'\` \u2014 required for the module runtime bundle.`
|
|
17956
18379
|
);
|
|
17957
18380
|
}
|
|
17958
|
-
|
|
17959
|
-
|
|
18381
|
+
const addDeps = {};
|
|
18382
|
+
if (!pkg.dependencies?.["@dashai/sdk"]) addDeps["@dashai/sdk"] = SDK_DEP_RANGE;
|
|
18383
|
+
if (layoutCreated && !pkg.dependencies?.geist) addDeps.geist = GEIST_DEP_RANGE;
|
|
18384
|
+
if (Object.keys(addDeps).length > 0) {
|
|
18385
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, ...addDeps };
|
|
17960
18386
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
17961
|
-
created.push(
|
|
18387
|
+
created.push(`package.json (added ${Object.keys(addDeps).join(", ")})`);
|
|
17962
18388
|
}
|
|
17963
18389
|
const outboundApis = scanOutboundHosts(dir);
|
|
17964
18390
|
const runtimeBlockAdded = normalizeManifest(join(dir, "module.json"), {
|
|
@@ -18782,13 +19208,13 @@ init_api_client();
|
|
|
18782
19208
|
function redactedConsumer(redacted) {
|
|
18783
19209
|
switch (redacted) {
|
|
18784
19210
|
case "private_copy":
|
|
18785
|
-
return "someone's private
|
|
19211
|
+
return "someone's private installation";
|
|
18786
19212
|
case "other_team_copy":
|
|
18787
|
-
return "another team's
|
|
19213
|
+
return "another team's installation";
|
|
18788
19214
|
case "development_copy":
|
|
18789
|
-
return "a development
|
|
19215
|
+
return "a development installation you cannot see";
|
|
18790
19216
|
default:
|
|
18791
|
-
return "
|
|
19217
|
+
return "an installation you cannot see";
|
|
18792
19218
|
}
|
|
18793
19219
|
}
|
|
18794
19220
|
var WIDEN_WORDS = {
|
|
@@ -18806,7 +19232,7 @@ var WIDEN_WORDS = {
|
|
|
18806
19232
|
function describeWidening(widening) {
|
|
18807
19233
|
return widening.map((w) => WIDEN_WORDS[w] ?? w).join(", ");
|
|
18808
19234
|
}
|
|
18809
|
-
async function
|
|
19235
|
+
async function getUpgradePreview2(installationId, version, options = {}) {
|
|
18810
19236
|
return apiRequest(
|
|
18811
19237
|
"GET",
|
|
18812
19238
|
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
@@ -18873,6 +19299,9 @@ function selectInstallsByKind(installs, moduleId) {
|
|
|
18873
19299
|
function isRealRuntimeUrl(url) {
|
|
18874
19300
|
return !!url && /^https:\/\//i.test(url);
|
|
18875
19301
|
}
|
|
19302
|
+
function isRuntimeProvisioned(row) {
|
|
19303
|
+
return row.runtime_https ?? isRealRuntimeUrl(row.runtime_url);
|
|
19304
|
+
}
|
|
18876
19305
|
function isDraftVersion(v) {
|
|
18877
19306
|
return !v || v.includes("draft");
|
|
18878
19307
|
}
|
|
@@ -18916,6 +19345,71 @@ async function resolveWorkspaceWithFallback(opts) {
|
|
|
18916
19345
|
// src/commands/module/install.ts
|
|
18917
19346
|
init_output();
|
|
18918
19347
|
init_connect_status();
|
|
19348
|
+
|
|
19349
|
+
// src/lib/upgrade-plan.ts
|
|
19350
|
+
init_api_client();
|
|
19351
|
+
init_output();
|
|
19352
|
+
function upgradePlanOf(err) {
|
|
19353
|
+
if (!(err instanceof ApiError) || err.code !== "UPGRADE_CONFIRM_REQUIRED") {
|
|
19354
|
+
return null;
|
|
19355
|
+
}
|
|
19356
|
+
const body = err.body;
|
|
19357
|
+
if (!body || typeof body.drift_hash !== "string") return null;
|
|
19358
|
+
return {
|
|
19359
|
+
...body,
|
|
19360
|
+
drift_hash: body.drift_hash,
|
|
19361
|
+
breaks_consumers: Array.isArray(body.breaks_consumers) ? body.breaks_consumers : []
|
|
19362
|
+
};
|
|
19363
|
+
}
|
|
19364
|
+
var REDACTED_LABEL = {
|
|
19365
|
+
private_copy: "someone's private installation",
|
|
19366
|
+
other_team_copy: "another team's installation",
|
|
19367
|
+
development_copy: "a development installation"
|
|
19368
|
+
};
|
|
19369
|
+
function consumerLabel(c2) {
|
|
19370
|
+
if (c2.redacted) return REDACTED_LABEL[c2.redacted] ?? "an installation you cannot see";
|
|
19371
|
+
const name = c2.module_name ?? c2.module_slug ?? "a module";
|
|
19372
|
+
const slug = c2.module_slug && c2.module_name ? ` (${c2.module_slug})` : "";
|
|
19373
|
+
const id = c2.installation_id != null ? ` #${c2.installation_id}` : "";
|
|
19374
|
+
return `${name}${slug}${id}`;
|
|
19375
|
+
}
|
|
19376
|
+
var count2 = (n, noun) => n == null ? "" : `, ${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
19377
|
+
function renderUpgradePlan(slug, plan) {
|
|
19378
|
+
const n = plan.breaks_consumers.length;
|
|
19379
|
+
const to = plan.to_version ? ` to ${code(`v${plan.to_version}`)}` : "";
|
|
19380
|
+
if (n === 0) {
|
|
19381
|
+
info(`Upgrading ${code(slug)}${to} no longer disconnects any module that reads from it.`);
|
|
19382
|
+
} else {
|
|
19383
|
+
warn(
|
|
19384
|
+
`Upgrading ${code(slug)}${to} breaks ${n} module${n === 1 ? "" : "s"} that read from it. ${n === 1 ? "It is" : "They are"} disconnected until updated:`
|
|
19385
|
+
);
|
|
19386
|
+
for (const c2 of plan.breaks_consumers) info(` - ${consumerLabel(c2)}`);
|
|
19387
|
+
}
|
|
19388
|
+
const d = plan.drift?.details;
|
|
19389
|
+
const lines = [
|
|
19390
|
+
...(d?.tables_to_remove ?? []).map(
|
|
19391
|
+
(t) => `remove table ${code(t.name)}${count2(t.rows, "row")}`
|
|
19392
|
+
),
|
|
19393
|
+
...(d?.fields_to_remove ?? []).map(
|
|
19394
|
+
(f) => `remove field ${code(`${f.table}.${f.name}`)}${count2(f.values, "value")}`
|
|
19395
|
+
),
|
|
19396
|
+
...(d?.fields_to_retype ?? []).map(
|
|
19397
|
+
(f) => `change field ${code(`${f.table}.${f.name}`)} from ${f.from} to ${f.to}${count2(f.values, "value")}`
|
|
19398
|
+
)
|
|
19399
|
+
];
|
|
19400
|
+
if (lines.length > 0) {
|
|
19401
|
+
info("It also changes this installation:");
|
|
19402
|
+
for (const line of lines) info(` - ${line}`);
|
|
19403
|
+
}
|
|
19404
|
+
}
|
|
19405
|
+
function planLosesData(plan) {
|
|
19406
|
+
const drift = plan.drift;
|
|
19407
|
+
if (!drift) return true;
|
|
19408
|
+
const details = drift.details;
|
|
19409
|
+
return (details?.tables_to_remove?.some((t) => t.rows !== 0) ?? (drift.tables_to_remove?.length ?? 0) > 0) || (details?.fields_to_remove?.some((f) => f.values !== 0) ?? (drift.fields_to_remove?.length ?? 0) > 0) || (details?.fields_to_retype?.some((f) => f.values !== 0) ?? (drift.fields_to_retype?.length ?? 0) > 0);
|
|
19410
|
+
}
|
|
19411
|
+
|
|
19412
|
+
// src/commands/module/install.ts
|
|
18919
19413
|
async function moduleInstallCommand(slugArg, options) {
|
|
18920
19414
|
const slug = resolveSlug(slugArg, options.dir);
|
|
18921
19415
|
if (!slug) {
|
|
@@ -18973,14 +19467,27 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18973
19467
|
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
18974
19468
|
const byKind = selectInstallsByKind(installs, mod.id);
|
|
18975
19469
|
const production = byKind.all.filter((row2) => !isDevInstallKind(row2.kind));
|
|
18976
|
-
if (production.length > 1) {
|
|
18977
|
-
fail(
|
|
18978
|
-
"Several production installations exist for this module in this company. Select the installation to update in the app. Nothing was changed."
|
|
18979
|
-
);
|
|
18980
|
-
return 1;
|
|
18981
|
-
}
|
|
18982
|
-
existing = production[0];
|
|
18983
19470
|
devInstall = byKind.dev;
|
|
19471
|
+
if (options.installation !== void 0) {
|
|
19472
|
+
existing = production.find((row2) => row2.id === options.installation);
|
|
19473
|
+
if (!existing) {
|
|
19474
|
+
fail(
|
|
19475
|
+
`Installation #${options.installation} is not a production installation of ${code(mod.slug)} you can see in this company. Nothing was changed.`
|
|
19476
|
+
);
|
|
19477
|
+
listInstallationCandidates(production);
|
|
19478
|
+
return 1;
|
|
19479
|
+
}
|
|
19480
|
+
} else {
|
|
19481
|
+
const candidates = production.filter((row2) => row2.held !== true);
|
|
19482
|
+
if (candidates.length > 1 || candidates.length === 0 && production.length > 0) {
|
|
19483
|
+
fail(
|
|
19484
|
+
candidates.length > 1 ? `Several production installations of ${code(mod.slug)} exist in this company. Choose one with ${code("--installation <id>")}. Nothing was changed.` : `The only production installations of ${code(mod.slug)} here are held. To update one, choose it with ${code("--installation <id>")}. Nothing was changed.`
|
|
19485
|
+
);
|
|
19486
|
+
listInstallationCandidates(production);
|
|
19487
|
+
return 1;
|
|
19488
|
+
}
|
|
19489
|
+
existing = candidates[0];
|
|
19490
|
+
}
|
|
18984
19491
|
} catch (err) {
|
|
18985
19492
|
fail(
|
|
18986
19493
|
`Could not read workspace installations: ${err.message}`
|
|
@@ -19027,7 +19534,7 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19027
19534
|
);
|
|
19028
19535
|
} else {
|
|
19029
19536
|
info(
|
|
19030
|
-
`Existing production install
|
|
19537
|
+
`Existing production install ${describeInstallation(existing)}${installedVersion2 ? ` at ${code(`v${installedVersion2}`)}` : ""} \u2014 this will upgrade it to ${code(`v${targetVersion}`)}.`
|
|
19031
19538
|
);
|
|
19032
19539
|
}
|
|
19033
19540
|
}
|
|
@@ -19035,15 +19542,19 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19035
19542
|
let lossy = false;
|
|
19036
19543
|
let lossAccepted = false;
|
|
19037
19544
|
if (existing) {
|
|
19038
|
-
const rerun = rerunLine(mod.slug, workspace2.slug, targetVersion, options, devInstallToReplace !== void 0);
|
|
19545
|
+
const rerun = rerunLine(mod.slug, workspace2.slug, targetVersion, existing.id, options, devInstallToReplace !== void 0);
|
|
19039
19546
|
const previewed = await previewUpgrade(existing.id, targetVersion, options, auth, rerun, devInstallToReplace?.id);
|
|
19040
19547
|
if (typeof previewed === "number") return previewed;
|
|
19041
19548
|
preview = previewed.preview;
|
|
19042
19549
|
lossy = previewed.lossy;
|
|
19043
19550
|
lossAccepted = previewed.accepted;
|
|
19044
19551
|
}
|
|
19552
|
+
if (options.force && (preview?.breaks_consumers?.length ?? 0) > 0 && !options.yes && (!process.stdout.isTTY || !process.stdin.isTTY || options.json)) {
|
|
19553
|
+
fail(`Nothing was changed. Re-run with ${code("--force --yes")} to confirm the listed disconnections, or run in a terminal to be asked.`);
|
|
19554
|
+
return 1;
|
|
19555
|
+
}
|
|
19045
19556
|
const askForLoss = lossy && !lossAccepted;
|
|
19046
|
-
if (askForLoss || !options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
19557
|
+
if (askForLoss || !options.yes && process.stdout.isTTY && process.stdin.isTTY && !confirmedViaPicker) {
|
|
19047
19558
|
const lossClause = lossy ? " It moves the data listed above to the Trash or converts it." : "";
|
|
19048
19559
|
const message = devInstallToReplace ? isUpgrade ? `Destroy the dev install (revokes local-dev credentials) and upgrade ${code(mod.slug)} to ${code(`v${targetVersion}`)} in ${code(workspace2.slug)}?${lossClause}` : `Replace the dev install (revokes local-dev credentials) and install ${code(mod.slug)} ${code(`v${targetVersion}`)} into ${code(workspace2.slug)} as production?` : `${isUpgrade ? "Upgrade" : "Install"} ${code(mod.slug)} ${code(`v${targetVersion}`)} into workspace ${code(workspace2.slug)}?${lossClause}`;
|
|
19049
19560
|
const answer = await select({
|
|
@@ -19070,26 +19581,63 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19070
19581
|
}
|
|
19071
19582
|
info("Provisioning the runtime \u2014 this can take a minute\u2026");
|
|
19072
19583
|
let result;
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
19086
|
-
|
|
19087
|
-
|
|
19584
|
+
let confirmedPlan;
|
|
19585
|
+
for (let round = 0; ; round++) {
|
|
19586
|
+
try {
|
|
19587
|
+
result = await installModule(
|
|
19588
|
+
mod.id,
|
|
19589
|
+
{
|
|
19590
|
+
workspaceId: workspace2.id,
|
|
19591
|
+
// L420(3): always the resolved version and, for an upgrade, the
|
|
19592
|
+
// announced installation; after a confirm, the plan's version.
|
|
19593
|
+
version: confirmedPlan?.to_version ?? targetVersion,
|
|
19594
|
+
...existing ? { installation_id: existing.id } : {},
|
|
19595
|
+
...options.force !== void 0 ? { force: options.force } : {},
|
|
19596
|
+
...confirmedPlan ? { drift_hash: confirmedPlan.drift_hash } : preview ? { drift_hash: preview.drift_hash } : {}
|
|
19597
|
+
},
|
|
19598
|
+
auth
|
|
19599
|
+
);
|
|
19600
|
+
break;
|
|
19601
|
+
} catch (err) {
|
|
19602
|
+
let plan = null;
|
|
19603
|
+
if (confirmedPlan && err instanceof ApiError && err.code === "DRIFT_CHANGED" && round < MAX_PLAN_ROUNDS) {
|
|
19604
|
+
warn(
|
|
19605
|
+
"What this upgrade changes has changed since it was shown. Showing the new plan."
|
|
19606
|
+
);
|
|
19607
|
+
plan = await refetchUpgradePlan(confirmedPlan, auth);
|
|
19608
|
+
if (!plan) {
|
|
19609
|
+
return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
|
|
19610
|
+
}
|
|
19611
|
+
} else {
|
|
19612
|
+
plan = options.force && !confirmedPlan ? upgradePlanOf(err) : null;
|
|
19613
|
+
if (!plan || round >= MAX_PLAN_ROUNDS) {
|
|
19614
|
+
return surfaceInstallError(err, mod.slug, workspace2.slug, devInstallReplaced);
|
|
19615
|
+
}
|
|
19616
|
+
}
|
|
19617
|
+
if (!existing || plan.installation_id !== existing.id || plan.to_version !== targetVersion || !plan.drift_hash) {
|
|
19618
|
+
fail("The upgrade plan does not match the announced installation and version. Nothing was changed; check the installation in the app.");
|
|
19619
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19620
|
+
return 1;
|
|
19621
|
+
}
|
|
19622
|
+
const confirmed = await confirmUpgradePlan(
|
|
19623
|
+
mod.slug,
|
|
19624
|
+
plan,
|
|
19625
|
+
options,
|
|
19626
|
+
preview?.drift_hash,
|
|
19627
|
+
rerunLine(mod.slug, workspace2.slug, targetVersion, existing.id, options, devInstallToReplace !== void 0)
|
|
19628
|
+
);
|
|
19629
|
+
if (confirmed !== 0) {
|
|
19630
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19631
|
+
return confirmed;
|
|
19632
|
+
}
|
|
19633
|
+
confirmedPlan = plan;
|
|
19634
|
+
}
|
|
19088
19635
|
}
|
|
19089
19636
|
if (existing && (isInstallGated(result) || result.installation.id !== existing.id)) {
|
|
19090
19637
|
fail(
|
|
19091
19638
|
`The backend did not confirm an in-place update of installation #${existing.id}. Check its state in the app before retrying; no fallback install will be attempted.`
|
|
19092
19639
|
);
|
|
19640
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19093
19641
|
return 1;
|
|
19094
19642
|
}
|
|
19095
19643
|
if (isInstallGated(result)) {
|
|
@@ -19119,7 +19667,7 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19119
19667
|
}
|
|
19120
19668
|
const publicUrl = row?.public_url ?? null;
|
|
19121
19669
|
const installedVersion = row?.module_version?.version ?? targetVersion;
|
|
19122
|
-
const runtimeProvisioned =
|
|
19670
|
+
const runtimeProvisioned = row ? isRuntimeProvisioned(row) : false;
|
|
19123
19671
|
const readiness = await fetchUsesReadiness(result.installation.id, auth);
|
|
19124
19672
|
const setupPending = readiness?.setupPending ?? false;
|
|
19125
19673
|
if (options.json) {
|
|
@@ -19132,8 +19680,8 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19132
19680
|
public_url: publicUrl,
|
|
19133
19681
|
verified,
|
|
19134
19682
|
// P5-08: the confirmed preview; `null` for a fresh install (nothing to preview).
|
|
19135
|
-
drift_hash: preview?.drift_hash ?? null,
|
|
19136
|
-
data_loss: preview ? lossy : null,
|
|
19683
|
+
drift_hash: confirmedPlan?.drift_hash ?? preview?.drift_hash ?? null,
|
|
19684
|
+
data_loss: confirmedPlan ? planLosesData(confirmedPlan) : preview ? lossy : null,
|
|
19137
19685
|
// `null` when the binding surface was unreachable; `[]` when the
|
|
19138
19686
|
// module declares no slots. The two are not the same thing.
|
|
19139
19687
|
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
@@ -19232,7 +19780,7 @@ async function resolveDevInstallCollision(existing, slug, workspaceSlug, options
|
|
|
19232
19780
|
info(explanation);
|
|
19233
19781
|
return "replace";
|
|
19234
19782
|
}
|
|
19235
|
-
if (process.stdout.isTTY && !options.yes) {
|
|
19783
|
+
if (process.stdout.isTTY && process.stdin.isTTY && !options.yes) {
|
|
19236
19784
|
info(explanation);
|
|
19237
19785
|
const answer = await select({
|
|
19238
19786
|
message: `How should ${code(slug)} be installed into ${code(workspaceSlug)}?`,
|
|
@@ -19293,6 +19841,62 @@ async function replaceDevInstall(existing, slug, auth) {
|
|
|
19293
19841
|
}
|
|
19294
19842
|
return 0;
|
|
19295
19843
|
}
|
|
19844
|
+
var MAX_PLAN_ROUNDS = 3;
|
|
19845
|
+
async function refetchUpgradePlan(previous, auth) {
|
|
19846
|
+
if (previous.installation_id === void 0 || previous.to_version === void 0) {
|
|
19847
|
+
return null;
|
|
19848
|
+
}
|
|
19849
|
+
try {
|
|
19850
|
+
const preview = await getUpgradePreview(
|
|
19851
|
+
previous.installation_id,
|
|
19852
|
+
previous.to_version,
|
|
19853
|
+
auth
|
|
19854
|
+
);
|
|
19855
|
+
return typeof preview.drift_hash === "string" ? {
|
|
19856
|
+
...preview,
|
|
19857
|
+
breaks_consumers: Array.isArray(preview.breaks_consumers) ? preview.breaks_consumers : []
|
|
19858
|
+
} : null;
|
|
19859
|
+
} catch {
|
|
19860
|
+
return null;
|
|
19861
|
+
}
|
|
19862
|
+
}
|
|
19863
|
+
async function confirmUpgradePlan(slug, plan, options, acceptedPreviewHash, rerun) {
|
|
19864
|
+
renderUpgradePlan(slug, plan);
|
|
19865
|
+
const losesData = planLosesData(plan);
|
|
19866
|
+
const lossAccepted = plan.drift_hash === acceptedPreviewHash || options.acceptDataLoss === plan.drift_hash;
|
|
19867
|
+
if (losesData && !lossAccepted && (agentSessionMarker() || !process.stdout.isTTY || !process.stdin.isTTY || options.json)) {
|
|
19868
|
+
fail("This upgrade loses data. --yes does not accept data loss. Nothing was changed.");
|
|
19869
|
+
info(`To accept exactly the loss listed above, a person re-runs:
|
|
19870
|
+
${rerun(plan.drift_hash)}`);
|
|
19871
|
+
return 1;
|
|
19872
|
+
}
|
|
19873
|
+
if (options.yes && (!losesData || lossAccepted)) {
|
|
19874
|
+
info(`${code("--yes")}: confirming this plan.`);
|
|
19875
|
+
return 0;
|
|
19876
|
+
}
|
|
19877
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY || options.json) {
|
|
19878
|
+
fail(
|
|
19879
|
+
`Nothing was changed. Re-run with ${code("--force --yes")} to confirm this plan, or run it in a terminal to be asked.`
|
|
19880
|
+
);
|
|
19881
|
+
return 1;
|
|
19882
|
+
}
|
|
19883
|
+
const answer = await select({
|
|
19884
|
+
message: losesData ? `Upgrade ${code(slug)}, lose the data listed above and disconnect the listed modules?` : plan.breaks_consumers.length === 0 ? `Upgrade ${code(slug)} with these changes?` : `Disconnect ${plan.breaks_consumers.length === 1 ? "this module" : "these modules"} and upgrade ${code(slug)}?`,
|
|
19885
|
+
options: [
|
|
19886
|
+
{
|
|
19887
|
+
value: "go",
|
|
19888
|
+
label: losesData ? "Yes, upgrade and lose the data listed above" : plan.breaks_consumers.length === 0 ? "Yes, upgrade" : "Yes, upgrade and disconnect them"
|
|
19889
|
+
},
|
|
19890
|
+
{ value: "cancel", label: "No, abort" }
|
|
19891
|
+
],
|
|
19892
|
+
initialValue: "cancel"
|
|
19893
|
+
});
|
|
19894
|
+
if (isCancel(answer) || answer === "cancel") {
|
|
19895
|
+
fail("Aborted. Nothing was changed.");
|
|
19896
|
+
return 130;
|
|
19897
|
+
}
|
|
19898
|
+
return 0;
|
|
19899
|
+
}
|
|
19296
19900
|
function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
19297
19901
|
if (err instanceof ApiError) {
|
|
19298
19902
|
switch (err.code) {
|
|
@@ -19317,8 +19921,8 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
19317
19921
|
break;
|
|
19318
19922
|
case "UPGRADE_BREAKS_CONSUMERS":
|
|
19319
19923
|
fail(
|
|
19320
|
-
`Upgrading ${code(slug)} would break modules that
|
|
19321
|
-
|
|
19924
|
+
`Upgrading ${code(slug)} would break modules that read from it.
|
|
19925
|
+
Re-run with ${code("--force")} to see which ones and confirm the upgrade.`
|
|
19322
19926
|
);
|
|
19323
19927
|
break;
|
|
19324
19928
|
case "FORBIDDEN":
|
|
@@ -19364,10 +19968,26 @@ function usesInstallJson(readiness) {
|
|
|
19364
19968
|
}))
|
|
19365
19969
|
};
|
|
19366
19970
|
}
|
|
19971
|
+
var AUDIENCE = {
|
|
19972
|
+
company: "company",
|
|
19973
|
+
teams: "Team Spaces",
|
|
19974
|
+
people: "named people"
|
|
19975
|
+
};
|
|
19976
|
+
function describeInstallation(row) {
|
|
19977
|
+
const details = [
|
|
19978
|
+
row.reach?.kind ? AUDIENCE[row.reach.kind] ?? row.reach.kind : null,
|
|
19979
|
+
row.held ? "held" : null,
|
|
19980
|
+
row.public_url
|
|
19981
|
+
].filter((part) => Boolean(part));
|
|
19982
|
+
return `#${row.id}${row.name ? ` "${row.name}"` : ""}${details.length ? ` (${details.join(", ")})` : ""}`;
|
|
19983
|
+
}
|
|
19984
|
+
function listInstallationCandidates(rows) {
|
|
19985
|
+
for (const row of rows) info(` ${describeInstallation(row)}`);
|
|
19986
|
+
}
|
|
19367
19987
|
async function previewUpgrade(installationId, targetVersion, options, auth, rerun, devInstallToReplaceId) {
|
|
19368
19988
|
let preview;
|
|
19369
19989
|
try {
|
|
19370
|
-
preview = await
|
|
19990
|
+
preview = await getUpgradePreview2(installationId, targetVersion, auth);
|
|
19371
19991
|
} catch (err) {
|
|
19372
19992
|
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19373
19993
|
const checkNote = options.acceptDataLoss !== void 0 ? `, so ${code("--accept-data-loss")} cannot be checked` : "";
|
|
@@ -19391,7 +20011,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19391
20011
|
const broken = preview.breaks_consumers ?? [];
|
|
19392
20012
|
if (broken.length > 0) {
|
|
19393
20013
|
const names = broken.map(
|
|
19394
|
-
(c2) => c2.module_slug ? `${c2.module_slug}${c2.installation_id ? ` #${c2.installation_id}` : ""}` : redactedConsumer(c2.redacted)
|
|
20014
|
+
(c2) => !c2.redacted && c2.module_slug ? `${c2.module_slug}${c2.installation_id ? ` #${c2.installation_id}` : ""}` : redactedConsumer(c2.redacted)
|
|
19395
20015
|
);
|
|
19396
20016
|
warn(` Breaks ${broken.length} module${broken.length === 1 ? "" : "s"} that read${broken.length === 1 ? "s" : ""} this one: ${names.join(", ")}`);
|
|
19397
20017
|
}
|
|
@@ -19401,7 +20021,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19401
20021
|
}
|
|
19402
20022
|
if (options.acceptDataLoss !== void 0 && options.acceptDataLoss !== preview.drift_hash) {
|
|
19403
20023
|
fail(
|
|
19404
|
-
`${code("--accept-data-loss")} does not match this upgrade's preview: the
|
|
20024
|
+
`${code("--accept-data-loss")} does not match this upgrade's preview: the installation or the release changed since that hash was printed. Nothing was changed.`
|
|
19405
20025
|
);
|
|
19406
20026
|
info(`To accept the change listed above, a person re-runs:
|
|
19407
20027
|
${rerun(preview.drift_hash)}`);
|
|
@@ -19411,7 +20031,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19411
20031
|
if (lossy && !accepted) {
|
|
19412
20032
|
const yesNote = options.yes ? ` ${code("--yes")} does not accept data loss.` : "";
|
|
19413
20033
|
const marker = agentSessionMarker();
|
|
19414
|
-
const why = marker ? `this is an agent session (${code(marker)} is set): a person decides` : !process.stdout.isTTY ? "there is no terminal to ask in" : null;
|
|
20034
|
+
const why = marker ? `this is an agent session (${code(marker)} is set): a person decides` : !process.stdout.isTTY || !process.stdin.isTTY || options.json ? "there is no terminal to ask in" : null;
|
|
19415
20035
|
if (why) {
|
|
19416
20036
|
const alsoDev = devInstallToReplaceId !== void 0 ? ` and destroys dev install #${devInstallToReplaceId} first` : "";
|
|
19417
20037
|
fail(`This upgrade loses the data listed above${alsoDev}, and ${why}.${yesNote} Refused. Nothing was changed.`);
|
|
@@ -19422,12 +20042,13 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19422
20042
|
}
|
|
19423
20043
|
return { preview, lossy, accepted };
|
|
19424
20044
|
}
|
|
19425
|
-
function rerunLine(slug, workspaceSlug, targetVersion, options, replacingDevInstall) {
|
|
20045
|
+
function rerunLine(slug, workspaceSlug, targetVersion, installationId, options, replacingDevInstall) {
|
|
19426
20046
|
return (hash) => [
|
|
19427
20047
|
"dashwise module install",
|
|
19428
20048
|
slug,
|
|
19429
20049
|
`--workspace ${workspaceSlug}`,
|
|
19430
20050
|
`--version ${targetVersion}`,
|
|
20051
|
+
`--installation ${installationId}`,
|
|
19431
20052
|
...options.force ? ["--force"] : [],
|
|
19432
20053
|
...replacingDevInstall ? ["--replace-dev-install"] : [],
|
|
19433
20054
|
`--accept-data-loss ${hash}`
|
|
@@ -20117,270 +20738,61 @@ init_api_client();
|
|
|
20117
20738
|
init_config();
|
|
20118
20739
|
init_manifest_io();
|
|
20119
20740
|
init_output();
|
|
20120
|
-
|
|
20121
|
-
|
|
20122
|
-
|
|
20123
|
-
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
var FEATURE_SUBJECTS = {
|
|
20128
|
-
module_runtime_streaming: "Streaming",
|
|
20129
|
-
module_rebuild: "Rebuild",
|
|
20130
|
-
module_building: "Building modules",
|
|
20131
|
-
module_runtime_sizing: "Memory size"
|
|
20132
|
-
};
|
|
20133
|
-
var OPTION_SUBJECTS = {
|
|
20134
|
-
streaming: "Streaming",
|
|
20135
|
-
memory_mb: "Memory size",
|
|
20136
|
-
reapply: "Re-applying the runtime",
|
|
20137
|
-
timeout: "The timeout",
|
|
20138
|
-
reserved_concurrency: "Reserved concurrency",
|
|
20139
|
-
log_retention: "Log retention"
|
|
20140
|
-
};
|
|
20141
|
-
var MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
20142
|
-
"MODULE_CAPABILITY_DENIED",
|
|
20143
|
-
"PUBLISH_REQUIRES_ADMIN",
|
|
20144
|
-
"NOT_COPY_OWNER"
|
|
20145
|
-
]);
|
|
20146
|
-
function str(v) {
|
|
20147
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
20148
|
-
}
|
|
20149
|
-
function num(v) {
|
|
20150
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
20151
|
-
}
|
|
20152
|
-
function featureFromPermission(permission) {
|
|
20153
|
-
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
20154
|
-
return m?.[1];
|
|
20155
|
-
}
|
|
20156
|
-
function featureSubject(ctx) {
|
|
20157
|
-
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
20158
|
-
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
20159
|
-
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
20160
|
-
}
|
|
20161
|
-
function needsAdmin(what, module, ask) {
|
|
20162
|
-
const scope = module ? ` for ${module}` : "";
|
|
20163
|
-
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
20741
|
+
init_permission_hint();
|
|
20742
|
+
var FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
20743
|
+
var positiveId = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
20744
|
+
function isRepositoryBuild(value, moduleId, buildId) {
|
|
20745
|
+
if (!value || typeof value !== "object") return false;
|
|
20746
|
+
const build = value;
|
|
20747
|
+
return positiveId(build.id) && build.module_id === moduleId && (buildId === void 0 || build.id === buildId) && build.source === "repo" && typeof build.commit_sha === "string" && FULL_SHA.test(build.commit_sha) && ["queued", "building", "succeeded", "failed"].includes(build.status ?? "");
|
|
20164
20748
|
}
|
|
20165
|
-
function
|
|
20166
|
-
const
|
|
20167
|
-
const
|
|
20168
|
-
|
|
20169
|
-
|
|
20170
|
-
|
|
20171
|
-
|
|
20172
|
-
|
|
20173
|
-
|
|
20174
|
-
const what = featureSubject(ctx);
|
|
20175
|
-
if (!what) {
|
|
20176
|
-
return {
|
|
20177
|
-
message: `You don't have permission to do this${module ? ` for ${module}` : ""}. Ask your Dashwise admin for access.`,
|
|
20178
|
-
needsGrant: true
|
|
20179
|
-
};
|
|
20180
|
-
}
|
|
20181
|
-
return { message: needsAdmin(what, module, "enable it"), needsGrant: true };
|
|
20749
|
+
async function moduleReleaseCommand(slugArg, options) {
|
|
20750
|
+
const json = options.json;
|
|
20751
|
+
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
20752
|
+
let slug = slugArg;
|
|
20753
|
+
if (!slug) {
|
|
20754
|
+
try {
|
|
20755
|
+
const localSlug = readManifest(options.dir)?.module?.slug;
|
|
20756
|
+
if (typeof localSlug === "string" && localSlug.length > 0) slug = localSlug;
|
|
20757
|
+
} catch {
|
|
20182
20758
|
}
|
|
20183
|
-
|
|
20184
|
-
|
|
20185
|
-
|
|
20186
|
-
|
|
20759
|
+
}
|
|
20760
|
+
if (!slug) return usage2("Pass a module slug or run from a directory with a valid module.json.");
|
|
20761
|
+
const resuming = options.build !== void 0;
|
|
20762
|
+
if (resuming && !positiveId(options.build)) return usage2("--build expects a positive build id.");
|
|
20763
|
+
if (resuming && (options.commitSha !== void 0 || options.bump !== void 0 || options.noWait)) {
|
|
20764
|
+
return usage2("--build only reads an existing release; do not combine it with --commit-sha, --bump or --no-wait.");
|
|
20765
|
+
}
|
|
20766
|
+
if (!resuming && (!options.commitSha || !FULL_SHA.test(options.commitSha))) {
|
|
20767
|
+
return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
|
|
20768
|
+
}
|
|
20769
|
+
const bump = options.bump ?? "minor";
|
|
20770
|
+
if (!["patch", "minor", "major"].includes(bump)) return usage2("--bump expects patch, minor or major.");
|
|
20771
|
+
const timeoutMs = (options.timeoutSec ?? 360) * 1e3;
|
|
20772
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return usage2("--timeout expects a positive number of seconds.");
|
|
20773
|
+
const cfg = readConfig();
|
|
20774
|
+
if (!isLoggedIn() || !cfg) return reportCliError("Not logged in. Run `dashwise login` first.", { json });
|
|
20775
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
20776
|
+
let moduleId;
|
|
20777
|
+
try {
|
|
20778
|
+
const mod = await getModuleBySlug(slug, auth);
|
|
20779
|
+
moduleId = mod.id;
|
|
20780
|
+
slug = mod.slug;
|
|
20781
|
+
} catch (err) {
|
|
20782
|
+
return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
|
|
20783
|
+
}
|
|
20784
|
+
if (!resuming && !options.yes) {
|
|
20785
|
+
if (json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
20786
|
+
return usage2("Release creates a version. Pass --yes to confirm in non-interactive or --json mode.");
|
|
20187
20787
|
}
|
|
20188
|
-
|
|
20189
|
-
|
|
20190
|
-
|
|
20191
|
-
|
|
20192
|
-
|
|
20193
|
-
|
|
20194
|
-
|
|
20195
|
-
|
|
20196
|
-
return {
|
|
20197
|
-
message: `This rebuild of ${named} needs a higher version bump than patch. A rebuild with a minor version bump needs a Dashwise admin. Ask your Dashwise admin to run it.`,
|
|
20198
|
-
needsGrant: true
|
|
20199
|
-
};
|
|
20200
|
-
case "RUNTIME_IMAGE_NOT_ELIGIBLE": {
|
|
20201
|
-
const version = str(ctx.version);
|
|
20202
|
-
const target = installationId !== void 0 ? ` --rollout installs:${installationId}` : "";
|
|
20203
|
-
return {
|
|
20204
|
-
message: `${named}${version ? ` v${version}` : ""} was built before streaming support. Rebuild it and upgrade this install first: dashwise module rebuild${module ? ` ${module}` : ""}${target}`,
|
|
20205
|
-
needsGrant: false
|
|
20206
|
-
};
|
|
20207
|
-
}
|
|
20208
|
-
case "RUNTIME_SWAP_PENDING": {
|
|
20209
|
-
const version = str(ctx.version);
|
|
20210
|
-
return {
|
|
20211
|
-
message: `${named}${installLabel} is still finishing its update${version ? ` to v${version}` : ""}, so streaming cannot be turned on yet. Try again in a few minutes.`,
|
|
20212
|
-
needsGrant: false
|
|
20213
|
-
};
|
|
20214
|
-
}
|
|
20215
|
-
case "RUNTIME_OPTION_UNAVAILABLE": {
|
|
20216
|
-
const option = str(ctx.option);
|
|
20217
|
-
const what = (option && OPTION_SUBJECTS[option]) ?? "This runtime option";
|
|
20218
|
-
return { message: `${what} is switched off platform-wide right now.`, needsGrant: false };
|
|
20219
|
-
}
|
|
20220
|
-
case "RUNTIME_CHANGE_IN_PROGRESS": {
|
|
20221
|
-
const op = num(ctx.operation_id);
|
|
20222
|
-
return {
|
|
20223
|
-
message: `A runtime change is already being applied to ${named}${op !== void 0 ? ` (operation #${op})` : ""}. Wait for it, then check: dashwise module runtime${module ? ` ${module}` : ""}`,
|
|
20224
|
-
needsGrant: false
|
|
20225
|
-
};
|
|
20226
|
-
}
|
|
20227
|
-
case "BUILD_IN_PROGRESS": {
|
|
20228
|
-
const buildId = num(ctx.build_id);
|
|
20229
|
-
const status = str(ctx.status);
|
|
20230
|
-
const which = buildId !== void 0 ? ` (#${buildId}${status ? `, ${status}` : ""})` : "";
|
|
20231
|
-
return {
|
|
20232
|
-
message: `A build of ${named} is already running${which}. Wait for it to finish, then run the rebuild again.`,
|
|
20233
|
-
needsGrant: false
|
|
20234
|
-
};
|
|
20235
|
-
}
|
|
20236
|
-
case "REBUILD_SOURCE_NOT_FOUND":
|
|
20237
|
-
return {
|
|
20238
|
-
message: `${named} has no build from source to rebuild. Publish it first: dashwise module publish`,
|
|
20239
|
-
needsGrant: false
|
|
20240
|
-
};
|
|
20241
|
-
case "INSTALLATION_NOT_FOUND":
|
|
20242
|
-
return {
|
|
20243
|
-
message: `${installationId !== void 0 ? `Installation #${installationId}` : "The installation"} was not found, or you cannot see it.`,
|
|
20244
|
-
needsGrant: false
|
|
20245
|
-
};
|
|
20246
|
-
case "PUBLISH_RATE_LIMITED":
|
|
20247
|
-
case "PUBLISH_USER_RATE_LIMITED":
|
|
20248
|
-
return {
|
|
20249
|
-
message: `Too many builds of ${named} in the last hour. Try again later.`,
|
|
20250
|
-
needsGrant: false
|
|
20251
|
-
};
|
|
20252
|
-
default:
|
|
20253
|
-
if (MODULE_RIGHT_CODES.has(code2)) {
|
|
20254
|
-
return {
|
|
20255
|
-
message: `You can't manage ${named}${installLabel}. Ask your Dashwise admin for access.`,
|
|
20256
|
-
needsGrant: true
|
|
20257
|
-
};
|
|
20258
|
-
}
|
|
20259
|
-
return null;
|
|
20260
|
-
}
|
|
20261
|
-
}
|
|
20262
|
-
var ANSI = /\x1b\[[0-9;]*m/g;
|
|
20263
|
-
function reportCliError(message, opts) {
|
|
20264
|
-
if (opts.json) {
|
|
20265
|
-
out(
|
|
20266
|
-
JSON.stringify({
|
|
20267
|
-
error: { code: opts.code ?? "CLI_ERROR", message: message.replace(ANSI, ""), status: null, context: null },
|
|
20268
|
-
hint: null,
|
|
20269
|
-
needs_grant: false
|
|
20270
|
-
})
|
|
20271
|
-
);
|
|
20272
|
-
} else {
|
|
20273
|
-
fail(message);
|
|
20274
|
-
}
|
|
20275
|
-
return 1;
|
|
20276
|
-
}
|
|
20277
|
-
function reportFailedOutcome(failure, hint, opts) {
|
|
20278
|
-
if (opts.json) {
|
|
20279
|
-
out(
|
|
20280
|
-
JSON.stringify({
|
|
20281
|
-
error: { code: failure.code, message: failure.message ?? null, status: null, context: null },
|
|
20282
|
-
hint: hint?.message ?? null,
|
|
20283
|
-
needs_grant: hint?.needsGrant ?? false,
|
|
20284
|
-
...opts.extra
|
|
20285
|
-
})
|
|
20286
|
-
);
|
|
20287
|
-
} else {
|
|
20288
|
-
fail(hint?.message ?? opts.fallback);
|
|
20289
|
-
}
|
|
20290
|
-
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
20291
|
-
}
|
|
20292
|
-
function isTransientPollError(err) {
|
|
20293
|
-
if (!(err instanceof ApiError)) return true;
|
|
20294
|
-
return err.status === 0 || err.status === 429 || err.status >= 500;
|
|
20295
|
-
}
|
|
20296
|
-
function reportRefusal(err, subject, opts) {
|
|
20297
|
-
if (!(err instanceof ApiError)) {
|
|
20298
|
-
const message = err?.message ?? String(err);
|
|
20299
|
-
if (opts.json) {
|
|
20300
|
-
out(
|
|
20301
|
-
JSON.stringify({
|
|
20302
|
-
error: { code: "CLI_ERROR", message: message.replace(ANSI, ""), status: null, context: null },
|
|
20303
|
-
hint: null,
|
|
20304
|
-
needs_grant: false
|
|
20305
|
-
})
|
|
20306
|
-
);
|
|
20307
|
-
} else {
|
|
20308
|
-
fail(`${opts.action} failed: ${message}`);
|
|
20309
|
-
}
|
|
20310
|
-
return 1;
|
|
20311
|
-
}
|
|
20312
|
-
const hint = err.status === 401 ? { message: "Your login has expired. Run: dashwise login", needsGrant: false } : refusalHint(err.code, err.context, subject);
|
|
20313
|
-
if (opts.json) {
|
|
20314
|
-
out(
|
|
20315
|
-
JSON.stringify({
|
|
20316
|
-
error: { code: err.code, message: err.message, status: err.status, context: err.context ?? null },
|
|
20317
|
-
hint: hint?.message ?? null,
|
|
20318
|
-
needs_grant: hint?.needsGrant ?? false
|
|
20319
|
-
})
|
|
20320
|
-
);
|
|
20321
|
-
} else if (hint) {
|
|
20322
|
-
fail(hint.message);
|
|
20323
|
-
} else {
|
|
20324
|
-
fail(`${opts.action} failed (${err.code}): ${err.message}`);
|
|
20325
|
-
}
|
|
20326
|
-
return hint?.needsGrant ? EXIT_NEEDS_GRANT : 1;
|
|
20327
|
-
}
|
|
20328
|
-
|
|
20329
|
-
// src/commands/module/release.ts
|
|
20330
|
-
var FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
20331
|
-
var positiveId = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
20332
|
-
function isRepositoryBuild(value, moduleId, buildId) {
|
|
20333
|
-
if (!value || typeof value !== "object") return false;
|
|
20334
|
-
const build = value;
|
|
20335
|
-
return positiveId(build.id) && build.module_id === moduleId && (buildId === void 0 || build.id === buildId) && build.source === "repo" && typeof build.commit_sha === "string" && FULL_SHA.test(build.commit_sha) && ["queued", "building", "succeeded", "failed"].includes(build.status ?? "");
|
|
20336
|
-
}
|
|
20337
|
-
async function moduleReleaseCommand(slugArg, options) {
|
|
20338
|
-
const json = options.json;
|
|
20339
|
-
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
20340
|
-
let slug = slugArg;
|
|
20341
|
-
if (!slug) {
|
|
20342
|
-
try {
|
|
20343
|
-
const localSlug = readManifest(options.dir)?.module?.slug;
|
|
20344
|
-
if (typeof localSlug === "string" && localSlug.length > 0) slug = localSlug;
|
|
20345
|
-
} catch {
|
|
20346
|
-
}
|
|
20347
|
-
}
|
|
20348
|
-
if (!slug) return usage2("Pass a module slug or run from a directory with a valid module.json.");
|
|
20349
|
-
const resuming = options.build !== void 0;
|
|
20350
|
-
if (resuming && !positiveId(options.build)) return usage2("--build expects a positive build id.");
|
|
20351
|
-
if (resuming && (options.commitSha !== void 0 || options.bump !== void 0 || options.noWait)) {
|
|
20352
|
-
return usage2("--build only reads an existing release; do not combine it with --commit-sha, --bump or --no-wait.");
|
|
20353
|
-
}
|
|
20354
|
-
if (!resuming && (!options.commitSha || !FULL_SHA.test(options.commitSha))) {
|
|
20355
|
-
return usage2("--commit-sha requires the full 40-character SHA already pushed to the linked repository.");
|
|
20356
|
-
}
|
|
20357
|
-
const bump = options.bump ?? "minor";
|
|
20358
|
-
if (!["patch", "minor", "major"].includes(bump)) return usage2("--bump expects patch, minor or major.");
|
|
20359
|
-
const timeoutMs = (options.timeoutSec ?? 360) * 1e3;
|
|
20360
|
-
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) return usage2("--timeout expects a positive number of seconds.");
|
|
20361
|
-
const cfg = readConfig();
|
|
20362
|
-
if (!isLoggedIn() || !cfg) return reportCliError("Not logged in. Run `dashwise login` first.", { json });
|
|
20363
|
-
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
20364
|
-
let moduleId;
|
|
20365
|
-
try {
|
|
20366
|
-
const mod = await getModuleBySlug(slug, auth);
|
|
20367
|
-
moduleId = mod.id;
|
|
20368
|
-
slug = mod.slug;
|
|
20369
|
-
} catch (err) {
|
|
20370
|
-
return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
|
|
20371
|
-
}
|
|
20372
|
-
if (!resuming && !options.yes) {
|
|
20373
|
-
if (json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
20374
|
-
return usage2("Release creates a version. Pass --yes to confirm in non-interactive or --json mode.");
|
|
20375
|
-
}
|
|
20376
|
-
const answer = await select({
|
|
20377
|
-
message: `Release ${slug} at ${options.commitSha} with a ${bump} bump?`,
|
|
20378
|
-
options: [{ value: "cancel", label: "Cancel" }, { value: "release", label: "Release this revision" }],
|
|
20379
|
-
initialValue: "cancel"
|
|
20380
|
-
});
|
|
20381
|
-
if (isCancel(answer) || answer !== "release") {
|
|
20382
|
-
reportCliError("Aborted.", { json });
|
|
20383
|
-
return 130;
|
|
20788
|
+
const answer = await select({
|
|
20789
|
+
message: `Release ${slug} at ${options.commitSha} with a ${bump} bump?`,
|
|
20790
|
+
options: [{ value: "cancel", label: "Cancel" }, { value: "release", label: "Release this revision" }],
|
|
20791
|
+
initialValue: "cancel"
|
|
20792
|
+
});
|
|
20793
|
+
if (isCancel(answer) || answer !== "release") {
|
|
20794
|
+
reportCliError("Aborted.", { json });
|
|
20795
|
+
return 130;
|
|
20384
20796
|
}
|
|
20385
20797
|
}
|
|
20386
20798
|
let buildId = options.build;
|
|
@@ -20575,7 +20987,7 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20575
20987
|
);
|
|
20576
20988
|
return 0;
|
|
20577
20989
|
}
|
|
20578
|
-
const runtimeProvisioned =
|
|
20990
|
+
const runtimeProvisioned = isRuntimeProvisioned(row);
|
|
20579
20991
|
const state = runtimeProvisioned && row.public_url ? "live" : runtimeProvisioned ? "deployed" : "not_deployed";
|
|
20580
20992
|
const installedVersion = row.module_version?.version ?? null;
|
|
20581
20993
|
const updateAvailable = isUpdateAvailable(installedVersion, latestVersion);
|
|
@@ -20592,7 +21004,9 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20592
21004
|
latest_version: latestVersion,
|
|
20593
21005
|
update_available: updateAvailable,
|
|
20594
21006
|
runtime_provisioned: runtimeProvisioned,
|
|
20595
|
-
|
|
21007
|
+
// The raw runtime endpoint is not printed any more: it reaches the
|
|
21008
|
+
// module's server code past the platform's doors. `public_url` is
|
|
21009
|
+
// the address to open.
|
|
20596
21010
|
public_url: row.public_url,
|
|
20597
21011
|
state,
|
|
20598
21012
|
// C-09: per-slot data-plane readiness for THIS install. `null`
|
|
@@ -20618,7 +21032,7 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20618
21032
|
);
|
|
20619
21033
|
kv(
|
|
20620
21034
|
"Runtime:",
|
|
20621
|
-
runtimeProvisioned ?
|
|
21035
|
+
runtimeProvisioned ? "provisioned" : "not provisioned"
|
|
20622
21036
|
);
|
|
20623
21037
|
kv("Public URL:", row.public_url ?? dim("(none)"));
|
|
20624
21038
|
kv(
|
|
@@ -20658,6 +21072,133 @@ function usesJson(readiness) {
|
|
|
20658
21072
|
};
|
|
20659
21073
|
}
|
|
20660
21074
|
|
|
21075
|
+
// src/commands/module/installations.ts
|
|
21076
|
+
init_api_client();
|
|
21077
|
+
init_config();
|
|
21078
|
+
init_output();
|
|
21079
|
+
var AUDIENCE2 = {
|
|
21080
|
+
company: "company",
|
|
21081
|
+
teams: "Team Spaces",
|
|
21082
|
+
people: "named people"
|
|
21083
|
+
};
|
|
21084
|
+
var givesRole = (roleKey) => typeof roleKey === "string" && roleKey !== "none";
|
|
21085
|
+
function governanceAudience(row) {
|
|
21086
|
+
if (!row.rules) return null;
|
|
21087
|
+
if (givesRole(row.rules.company)) return AUDIENCE2.company;
|
|
21088
|
+
if (row.rules.teams.some((t) => t.live === true && givesRole(t.role_key))) return AUDIENCE2.teams;
|
|
21089
|
+
return AUDIENCE2.people;
|
|
21090
|
+
}
|
|
21091
|
+
function toListEntry(row) {
|
|
21092
|
+
return {
|
|
21093
|
+
id: row.id,
|
|
21094
|
+
module_slug: row.module?.slug ?? null,
|
|
21095
|
+
name: row.name ?? row.module?.name ?? null,
|
|
21096
|
+
audience: row.reach?.kind ? AUDIENCE2[row.reach.kind] ?? row.reach.kind : null,
|
|
21097
|
+
version: row.module_version?.version ?? null,
|
|
21098
|
+
held: row.held === true,
|
|
21099
|
+
development: isDevInstallKind(row.kind),
|
|
21100
|
+
public_url: row.public_url,
|
|
21101
|
+
admin_view_only: false
|
|
21102
|
+
};
|
|
21103
|
+
}
|
|
21104
|
+
async function moduleInstallationsCommand(slug, options) {
|
|
21105
|
+
if (!isLoggedIn()) {
|
|
21106
|
+
fail(`Not logged in. Run ${code("dashwise login")} first.`);
|
|
21107
|
+
return 1;
|
|
21108
|
+
}
|
|
21109
|
+
const cfg = readConfig();
|
|
21110
|
+
if (!cfg) {
|
|
21111
|
+
fail(`CLI config missing. Run ${code("dashwise login")}.`);
|
|
21112
|
+
return 1;
|
|
21113
|
+
}
|
|
21114
|
+
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
21115
|
+
let workspace2;
|
|
21116
|
+
try {
|
|
21117
|
+
workspace2 = await resolveWorkspaceWithFallback({
|
|
21118
|
+
userFlag: options.workspace,
|
|
21119
|
+
cachedWorkspaceId: slug ? readModuleEntry(slug)?.workspaceId : void 0,
|
|
21120
|
+
configDefault: cfg.workspaceSlug,
|
|
21121
|
+
apiUrl: resolveApiUrl(),
|
|
21122
|
+
apiToken: cfg.token
|
|
21123
|
+
});
|
|
21124
|
+
} catch (err) {
|
|
21125
|
+
fail(`Company selection failed: ${err.message}`);
|
|
21126
|
+
return 1;
|
|
21127
|
+
}
|
|
21128
|
+
let rows;
|
|
21129
|
+
try {
|
|
21130
|
+
rows = await listWorkspaceInstallations(workspace2.id, auth);
|
|
21131
|
+
} catch (err) {
|
|
21132
|
+
fail(`Could not read the installations in ${code(workspace2.slug)}: ${err.message}`);
|
|
21133
|
+
return 1;
|
|
21134
|
+
}
|
|
21135
|
+
const governance = [];
|
|
21136
|
+
try {
|
|
21137
|
+
const govRows = await apiRequest(
|
|
21138
|
+
"GET",
|
|
21139
|
+
`/api/modules/workspaces/${workspace2.id}/installations/governance`,
|
|
21140
|
+
void 0,
|
|
21141
|
+
auth
|
|
21142
|
+
);
|
|
21143
|
+
const seen = new Set(rows.map((row) => row.id));
|
|
21144
|
+
const slugOf = new Map(rows.map((row) => [row.module_id, row.module?.slug ?? null]));
|
|
21145
|
+
let slugModuleId;
|
|
21146
|
+
if (slug) {
|
|
21147
|
+
slugModuleId = rows.find((row) => row.module?.slug === slug)?.module_id;
|
|
21148
|
+
if (slugModuleId === void 0) {
|
|
21149
|
+
try {
|
|
21150
|
+
slugModuleId = (await getModuleBySlug(slug, auth)).id;
|
|
21151
|
+
} catch {
|
|
21152
|
+
}
|
|
21153
|
+
}
|
|
21154
|
+
}
|
|
21155
|
+
for (const g of govRows) {
|
|
21156
|
+
if (g.trashed || seen.has(g.installation_id)) continue;
|
|
21157
|
+
if (slug && g.module_id !== slugModuleId) continue;
|
|
21158
|
+
governance.push({
|
|
21159
|
+
id: g.installation_id,
|
|
21160
|
+
module_slug: slugOf.get(g.module_id) ?? (slug && g.module_id === slugModuleId ? slug : null),
|
|
21161
|
+
name: g.name || null,
|
|
21162
|
+
audience: governanceAudience(g),
|
|
21163
|
+
version: null,
|
|
21164
|
+
held: g.held === true,
|
|
21165
|
+
development: null,
|
|
21166
|
+
public_url: null,
|
|
21167
|
+
admin_view_only: true
|
|
21168
|
+
});
|
|
21169
|
+
}
|
|
21170
|
+
} catch (err) {
|
|
21171
|
+
if (!(err instanceof ApiError && (err.status === 403 || err.status === 404))) {
|
|
21172
|
+
fail(`Could not read the company's full installation list: ${err.message}`);
|
|
21173
|
+
return 1;
|
|
21174
|
+
}
|
|
21175
|
+
}
|
|
21176
|
+
const entries = rows.filter((row) => !slug || row.module?.slug === slug).map(toListEntry).concat(governance).filter((entry) => options.dev || !entry.development).sort((a, b) => (a.module_slug ?? "").localeCompare(b.module_slug ?? "") || a.id - b.id);
|
|
21177
|
+
if (options.json) {
|
|
21178
|
+
out(JSON.stringify({ workspace: workspace2.slug, installations: entries }));
|
|
21179
|
+
return 0;
|
|
21180
|
+
}
|
|
21181
|
+
if (entries.length === 0) {
|
|
21182
|
+
info(
|
|
21183
|
+
slug ? `No installations of ${code(slug)} you can see in ${code(workspace2.slug)}.` : `No installations you can see in ${code(workspace2.slug)}.`
|
|
21184
|
+
);
|
|
21185
|
+
return 0;
|
|
21186
|
+
}
|
|
21187
|
+
for (const e of entries) {
|
|
21188
|
+
const tags = [
|
|
21189
|
+
e.audience,
|
|
21190
|
+
e.version ? `v${e.version}` : null,
|
|
21191
|
+
e.held ? "held" : null,
|
|
21192
|
+
e.development ? "development" : null,
|
|
21193
|
+
e.admin_view_only ? "admin view, not open to you" : null
|
|
21194
|
+
].filter(Boolean);
|
|
21195
|
+
out(
|
|
21196
|
+
`#${e.id} ${e.module_slug ?? "?"} ${e.name ? `"${e.name}"` : ""} ${dim(tags.join(", "))}${e.public_url ? ` ${e.public_url}` : ""}`.replace(/ {3,}/g, " ")
|
|
21197
|
+
);
|
|
21198
|
+
}
|
|
21199
|
+
return 0;
|
|
21200
|
+
}
|
|
21201
|
+
|
|
20661
21202
|
// src/commands/module/uninstall.ts
|
|
20662
21203
|
init_api_client();
|
|
20663
21204
|
init_config();
|
|
@@ -20828,29 +21369,301 @@ function surfaceUninstallError(err, slug, workspaceSlug) {
|
|
|
20828
21369
|
return 1;
|
|
20829
21370
|
}
|
|
20830
21371
|
|
|
21372
|
+
// src/commands/module/invites.ts
|
|
21373
|
+
init_api_client();
|
|
21374
|
+
init_config();
|
|
21375
|
+
init_output();
|
|
21376
|
+
init_permission_hint();
|
|
21377
|
+
var positiveId2 = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
21378
|
+
function session(json) {
|
|
21379
|
+
const cfg = readConfig();
|
|
21380
|
+
if (!isLoggedIn() || !cfg) return reportCliError(`Not logged in. Run ${code("dashwise login")} first.`, { json });
|
|
21381
|
+
return {
|
|
21382
|
+
auth: { baseUrl: resolveApiUrl(), auth: { token: cfg.token } },
|
|
21383
|
+
token: cfg.token,
|
|
21384
|
+
workspaceSlug: cfg.workspaceSlug
|
|
21385
|
+
};
|
|
21386
|
+
}
|
|
21387
|
+
async function resolveModule(slug, s, json) {
|
|
21388
|
+
try {
|
|
21389
|
+
const mod = await getModuleBySlug(slug, s.auth);
|
|
21390
|
+
return { id: mod.id, slug: mod.slug };
|
|
21391
|
+
} catch (err) {
|
|
21392
|
+
return reportRefusal(err, { module: slug }, { json, action: "Resolving the module" });
|
|
21393
|
+
}
|
|
21394
|
+
}
|
|
21395
|
+
async function confirm(question, confirmLabel, opts) {
|
|
21396
|
+
if (opts.yes) return null;
|
|
21397
|
+
if (opts.json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
21398
|
+
return reportCliError("This changes an invitation. Pass --yes to confirm in non-interactive or --json mode.", {
|
|
21399
|
+
json: opts.json,
|
|
21400
|
+
code: "USAGE"
|
|
21401
|
+
});
|
|
21402
|
+
}
|
|
21403
|
+
const answer = await select({
|
|
21404
|
+
message: question,
|
|
21405
|
+
options: [
|
|
21406
|
+
{ value: "cancel", label: "Cancel" },
|
|
21407
|
+
{ value: "go", label: confirmLabel }
|
|
21408
|
+
],
|
|
21409
|
+
initialValue: "cancel"
|
|
21410
|
+
});
|
|
21411
|
+
if (isCancel(answer) || answer !== "go") {
|
|
21412
|
+
reportCliError("Aborted.", {});
|
|
21413
|
+
return 130;
|
|
21414
|
+
}
|
|
21415
|
+
return null;
|
|
21416
|
+
}
|
|
21417
|
+
function companyLabel(row) {
|
|
21418
|
+
return row.company_name ? `${row.company_name} (company #${row.workspace_id})` : `company #${row.workspace_id}`;
|
|
21419
|
+
}
|
|
21420
|
+
async function moduleInvitesListCommand(slug, options) {
|
|
21421
|
+
const { json } = options;
|
|
21422
|
+
const s = session(json);
|
|
21423
|
+
if (typeof s === "number") return s;
|
|
21424
|
+
const mod = await resolveModule(slug, s, json);
|
|
21425
|
+
if (typeof mod === "number") return mod;
|
|
21426
|
+
let rows;
|
|
21427
|
+
try {
|
|
21428
|
+
rows = await apiRequest("GET", `/api/modules/${mod.id}/invites`, void 0, s.auth);
|
|
21429
|
+
} catch (err) {
|
|
21430
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Reading the invitations" });
|
|
21431
|
+
}
|
|
21432
|
+
if (json) {
|
|
21433
|
+
out(JSON.stringify({ module: mod.slug, invites: rows }));
|
|
21434
|
+
return 0;
|
|
21435
|
+
}
|
|
21436
|
+
if (rows.length === 0) {
|
|
21437
|
+
info(`No open invitations for ${code(mod.slug)}.`);
|
|
21438
|
+
return 0;
|
|
21439
|
+
}
|
|
21440
|
+
for (const row of rows) {
|
|
21441
|
+
const note = row.note ? ` ${dim(`"${row.note}"`)}` : "";
|
|
21442
|
+
out(`#${row.id} ${companyLabel(row)} ${dim(row.invited_at.slice(0, 10))}${note}`);
|
|
21443
|
+
}
|
|
21444
|
+
return 0;
|
|
21445
|
+
}
|
|
21446
|
+
async function moduleInvitesSendCommand(slug, options) {
|
|
21447
|
+
const { json } = options;
|
|
21448
|
+
const usage2 = (message) => reportCliError(message, { json, code: "USAGE" });
|
|
21449
|
+
if (!options.to) return usage2("Pass --to <company id> to name the company to invite.");
|
|
21450
|
+
const s = session(json);
|
|
21451
|
+
if (typeof s === "number") return s;
|
|
21452
|
+
let target;
|
|
21453
|
+
const numeric = /^[1-9][0-9]*$/.test(options.to) ? Number(options.to) : void 0;
|
|
21454
|
+
let mine;
|
|
21455
|
+
try {
|
|
21456
|
+
mine = await apiRequest("GET", "/api/workspaces", void 0, s.auth);
|
|
21457
|
+
} catch (err) {
|
|
21458
|
+
return reportRefusal(err, {}, { json, action: "Reading your companies" });
|
|
21459
|
+
}
|
|
21460
|
+
const own = mine.find((w) => numeric !== void 0 ? w.id === numeric : w.slug === options.to);
|
|
21461
|
+
if (own) {
|
|
21462
|
+
target = { id: own.id, label: `${own.name} (company #${own.id})` };
|
|
21463
|
+
} else if (positiveId2(numeric)) {
|
|
21464
|
+
target = { id: numeric, label: `company #${numeric}` };
|
|
21465
|
+
} else {
|
|
21466
|
+
return usage2(
|
|
21467
|
+
`${options.to} is not one of your companies. For another company, pass its company id (an admin of that company finds it in its settings).`
|
|
21468
|
+
);
|
|
21469
|
+
}
|
|
21470
|
+
const mod = await resolveModule(slug, s, json);
|
|
21471
|
+
if (typeof mod === "number") return mod;
|
|
21472
|
+
const stop = await confirm(
|
|
21473
|
+
[
|
|
21474
|
+
`Invite ${target.label} to install ${mod.slug}?`,
|
|
21475
|
+
`- Admins of that company can then see ${mod.slug} and install it.`,
|
|
21476
|
+
"- Their data stays theirs; you do not see it.",
|
|
21477
|
+
`- Your module's logic and its updates reach their installations.`,
|
|
21478
|
+
"- You see their company name only after one of their admins installs it or asks to.",
|
|
21479
|
+
`- You can withdraw the invitation at any time with ${code(`dashwise module invites revoke ${mod.slug} <invitation id>`)}.`
|
|
21480
|
+
].join("\n"),
|
|
21481
|
+
"Send the invitation",
|
|
21482
|
+
options
|
|
21483
|
+
);
|
|
21484
|
+
if (stop !== null) return stop;
|
|
21485
|
+
let invite;
|
|
21486
|
+
try {
|
|
21487
|
+
const body = await apiRequest(
|
|
21488
|
+
"POST",
|
|
21489
|
+
`/api/modules/${mod.id}/invites`,
|
|
21490
|
+
{ workspaceId: target.id, ...options.note !== void 0 ? { note: options.note } : {} },
|
|
21491
|
+
s.auth
|
|
21492
|
+
);
|
|
21493
|
+
invite = body.invite;
|
|
21494
|
+
} catch (err) {
|
|
21495
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Sending the invitation" });
|
|
21496
|
+
}
|
|
21497
|
+
if (json) out(JSON.stringify({ module: mod.slug, invite }));
|
|
21498
|
+
else success(`Invited ${target.label} to install ${mod.slug} (invitation #${invite.id}).`);
|
|
21499
|
+
return 0;
|
|
21500
|
+
}
|
|
21501
|
+
async function moduleInvitesRevokeCommand(slug, inviteId, options) {
|
|
21502
|
+
const { json } = options;
|
|
21503
|
+
if (!positiveId2(inviteId)) {
|
|
21504
|
+
return reportCliError(`Pass the invitation id. List them with ${code(`dashwise module invites list ${slug}`)}.`, {
|
|
21505
|
+
json,
|
|
21506
|
+
code: "USAGE"
|
|
21507
|
+
});
|
|
21508
|
+
}
|
|
21509
|
+
const s = session(json);
|
|
21510
|
+
if (typeof s === "number") return s;
|
|
21511
|
+
const mod = await resolveModule(slug, s, json);
|
|
21512
|
+
if (typeof mod === "number") return mod;
|
|
21513
|
+
const stop = await confirm(
|
|
21514
|
+
[
|
|
21515
|
+
`Withdraw invitation #${inviteId} for ${mod.slug}?`,
|
|
21516
|
+
`- Installations made with it stay, but they no longer receive updates of ${mod.slug}.`,
|
|
21517
|
+
"- No new installation can start from it."
|
|
21518
|
+
].join("\n"),
|
|
21519
|
+
"Withdraw the invitation",
|
|
21520
|
+
options
|
|
21521
|
+
);
|
|
21522
|
+
if (stop !== null) return stop;
|
|
21523
|
+
try {
|
|
21524
|
+
await apiRequest("DELETE", `/api/modules/${mod.id}/invites/${inviteId}`, void 0, s.auth);
|
|
21525
|
+
} catch (err) {
|
|
21526
|
+
return reportRefusal(err, { module: mod.slug }, { json, action: "Withdrawing the invitation" });
|
|
21527
|
+
}
|
|
21528
|
+
if (json) out(JSON.stringify({ module: mod.slug, revoked: inviteId }));
|
|
21529
|
+
else success(`Withdrew invitation #${inviteId} for ${mod.slug}.`);
|
|
21530
|
+
return 0;
|
|
21531
|
+
}
|
|
21532
|
+
async function resolveOwnCompany(s, flag, opts) {
|
|
21533
|
+
const { json } = opts;
|
|
21534
|
+
let mine;
|
|
21535
|
+
try {
|
|
21536
|
+
mine = await apiRequest("GET", "/api/workspaces", void 0, s.auth);
|
|
21537
|
+
} catch (err) {
|
|
21538
|
+
return reportRefusal(err, {}, { json, action: "Reading your companies" });
|
|
21539
|
+
}
|
|
21540
|
+
const list = mine.map((w) => `${w.slug} (company #${w.id})`).join(", ");
|
|
21541
|
+
const wanted = flag ?? s.workspaceSlug;
|
|
21542
|
+
if (wanted !== void 0) {
|
|
21543
|
+
const found = mine.find((w) => w.slug === wanted || String(w.id) === wanted);
|
|
21544
|
+
if (found) return found;
|
|
21545
|
+
return reportCliError(`${wanted} is not one of your companies. Your companies: ${list}.`, { json, code: "USAGE" });
|
|
21546
|
+
}
|
|
21547
|
+
if (mine.length === 1) return mine[0];
|
|
21548
|
+
if (mine.length === 0) return reportCliError("Your account belongs to no company yet.", { json });
|
|
21549
|
+
if (!opts.interactive || json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
21550
|
+
return reportCliError(`Choose the company with --workspace <slug or id>. Your companies: ${list}.`, {
|
|
21551
|
+
json,
|
|
21552
|
+
code: "USAGE"
|
|
21553
|
+
});
|
|
21554
|
+
}
|
|
21555
|
+
const choice = await select({
|
|
21556
|
+
message: "Which company?",
|
|
21557
|
+
options: mine.map((w) => ({ value: String(w.id), label: w.name, hint: w.slug }))
|
|
21558
|
+
});
|
|
21559
|
+
const picked = isCancel(choice) ? void 0 : mine.find((w) => String(w.id) === choice);
|
|
21560
|
+
if (!picked) {
|
|
21561
|
+
reportCliError("Aborted.", {});
|
|
21562
|
+
return 130;
|
|
21563
|
+
}
|
|
21564
|
+
return picked;
|
|
21565
|
+
}
|
|
21566
|
+
async function moduleInvitesReceivedCommand(options) {
|
|
21567
|
+
const { json } = options;
|
|
21568
|
+
const s = session(json);
|
|
21569
|
+
if (typeof s === "number") return s;
|
|
21570
|
+
const company = await resolveOwnCompany(s, options.workspace, { json, interactive: true });
|
|
21571
|
+
if (typeof company === "number") return company;
|
|
21572
|
+
let rows;
|
|
21573
|
+
try {
|
|
21574
|
+
rows = await apiRequest(
|
|
21575
|
+
"GET",
|
|
21576
|
+
`/api/modules/workspaces/${company.id}/invites`,
|
|
21577
|
+
void 0,
|
|
21578
|
+
s.auth
|
|
21579
|
+
);
|
|
21580
|
+
} catch (err) {
|
|
21581
|
+
return reportRefusal(err, {}, { json, action: "Reading the invitations" });
|
|
21582
|
+
}
|
|
21583
|
+
if (json) {
|
|
21584
|
+
out(JSON.stringify({ workspace: company.slug, invites: rows }));
|
|
21585
|
+
return 0;
|
|
21586
|
+
}
|
|
21587
|
+
if (rows.length === 0) {
|
|
21588
|
+
info(`No open invitations for ${code(company.slug)}.`);
|
|
21589
|
+
return 0;
|
|
21590
|
+
}
|
|
21591
|
+
for (const row of rows) {
|
|
21592
|
+
const note = row.note ? ` ${dim(`"${row.note}"`)}` : "";
|
|
21593
|
+
out(
|
|
21594
|
+
`#${row.id} ${row.module.slug} ${dim(`v${row.module.current_version}`)} from ${row.from_company.name} ${dim(row.invited_at.slice(0, 10))}${note}`
|
|
21595
|
+
);
|
|
21596
|
+
}
|
|
21597
|
+
const inCompany = `--workspace ${company.slug}`;
|
|
21598
|
+
info(
|
|
21599
|
+
`Install one with ${code(`dashwise module install <slug> ${inCompany}`)}, or decline it with ${code(`dashwise module invites decline <id> ${inCompany}`)}.`
|
|
21600
|
+
);
|
|
21601
|
+
return 0;
|
|
21602
|
+
}
|
|
21603
|
+
async function moduleInvitesDeclineCommand(inviteId, options) {
|
|
21604
|
+
const { json } = options;
|
|
21605
|
+
if (!positiveId2(inviteId)) {
|
|
21606
|
+
return reportCliError(`Pass the invitation id. List them with ${code("dashwise module invites received")}.`, {
|
|
21607
|
+
json,
|
|
21608
|
+
code: "USAGE"
|
|
21609
|
+
});
|
|
21610
|
+
}
|
|
21611
|
+
const s = session(json);
|
|
21612
|
+
if (typeof s === "number") return s;
|
|
21613
|
+
const company = await resolveOwnCompany(s, options.workspace, { json, interactive: !options.yes });
|
|
21614
|
+
if (typeof company === "number") return company;
|
|
21615
|
+
const stop = await confirm(
|
|
21616
|
+
`Decline invitation #${inviteId} for ${company.slug}? The module's company may invite you again.`,
|
|
21617
|
+
"Decline the invitation",
|
|
21618
|
+
options
|
|
21619
|
+
);
|
|
21620
|
+
if (stop !== null) return stop;
|
|
21621
|
+
try {
|
|
21622
|
+
await apiRequest(
|
|
21623
|
+
"POST",
|
|
21624
|
+
`/api/modules/workspaces/${company.id}/invites/${inviteId}/decline`,
|
|
21625
|
+
void 0,
|
|
21626
|
+
s.auth
|
|
21627
|
+
);
|
|
21628
|
+
} catch (err) {
|
|
21629
|
+
return reportRefusal(err, {}, { json, action: "Declining the invitation" });
|
|
21630
|
+
}
|
|
21631
|
+
if (json) out(JSON.stringify({ workspace: company.slug, declined: inviteId }));
|
|
21632
|
+
else success(`Declined invitation #${inviteId}.`);
|
|
21633
|
+
return 0;
|
|
21634
|
+
}
|
|
21635
|
+
|
|
20831
21636
|
// src/commands/module/pull-data.ts
|
|
20832
21637
|
init_api_client();
|
|
21638
|
+
init_permission_hint();
|
|
20833
21639
|
init_output();
|
|
20834
21640
|
async function modulePullDataCommand(options) {
|
|
21641
|
+
const report = (err) => reportRefusal(err, {}, { ...options, stderr: true, action: "Pull data" });
|
|
21642
|
+
const contextError = (message) => report(
|
|
21643
|
+
new ApiError(
|
|
21644
|
+
0,
|
|
21645
|
+
{
|
|
21646
|
+
code: "VALIDATION_ERROR",
|
|
21647
|
+
message,
|
|
21648
|
+
context: { reason: "module_context_required" }
|
|
21649
|
+
},
|
|
21650
|
+
message
|
|
21651
|
+
)
|
|
21652
|
+
);
|
|
20835
21653
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
20836
21654
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
20837
21655
|
if (!existsSync(manifestPath2)) {
|
|
20838
|
-
|
|
20839
|
-
`No ${code("module.json")} found in ${code(projectRoot)}. Run \`dashwise module init <slug>\` first.`
|
|
20840
|
-
);
|
|
20841
|
-
return 1;
|
|
21656
|
+
return contextError(`No module.json found in ${projectRoot}.`);
|
|
20842
21657
|
}
|
|
20843
21658
|
let manifest;
|
|
20844
21659
|
try {
|
|
20845
21660
|
manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
20846
21661
|
} catch (err) {
|
|
20847
|
-
|
|
20848
|
-
return 1;
|
|
21662
|
+
return contextError(`Could not parse module.json: ${err.message}`);
|
|
20849
21663
|
}
|
|
20850
21664
|
const slug = manifest.module?.slug;
|
|
20851
21665
|
if (!slug) {
|
|
20852
|
-
|
|
20853
|
-
return 1;
|
|
21666
|
+
return contextError("module.json is missing module.slug.");
|
|
20854
21667
|
}
|
|
20855
21668
|
let mod;
|
|
20856
21669
|
try {
|
|
@@ -20859,16 +21672,7 @@ async function modulePullDataCommand(options) {
|
|
|
20859
21672
|
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
20860
21673
|
);
|
|
20861
21674
|
} catch (err) {
|
|
20862
|
-
|
|
20863
|
-
fail(`Module ${code(slug)} not found on the backend.`);
|
|
20864
|
-
return 1;
|
|
20865
|
-
}
|
|
20866
|
-
if (err instanceof ApiError && err.status === 401) {
|
|
20867
|
-
fail("Not logged in. Run `dashwise login` first.");
|
|
20868
|
-
return 1;
|
|
20869
|
-
}
|
|
20870
|
-
fail(`Could not resolve module: ${err.message}`);
|
|
20871
|
-
return 1;
|
|
21675
|
+
return report(err);
|
|
20872
21676
|
}
|
|
20873
21677
|
const limit = options.limit ?? 100;
|
|
20874
21678
|
let res;
|
|
@@ -20878,18 +21682,15 @@ async function modulePullDataCommand(options) {
|
|
|
20878
21682
|
`/api/modules/${mod.id}/own-data?limit=${limit}`
|
|
20879
21683
|
);
|
|
20880
21684
|
} catch (err) {
|
|
20881
|
-
|
|
20882
|
-
fail(`Invalid request: ${err.message}`);
|
|
20883
|
-
return 1;
|
|
20884
|
-
}
|
|
20885
|
-
fail(`Could not pull data: ${err.message}`);
|
|
20886
|
-
return 1;
|
|
21685
|
+
return report(err);
|
|
20887
21686
|
}
|
|
20888
21687
|
const outPath = options.out ? resolve(options.out) : join(projectRoot, "dashwise-data.json");
|
|
20889
21688
|
writeFileSync(outPath, JSON.stringify(res, null, 2) + "\n", "utf-8");
|
|
20890
21689
|
let totalRows = 0;
|
|
20891
21690
|
for (const t of res.tables) totalRows += t.rows.length;
|
|
20892
|
-
success(
|
|
21691
|
+
success(
|
|
21692
|
+
`Wrote ${totalRows} row${totalRows === 1 ? "" : "s"} across ${res.tables.length} table${res.tables.length === 1 ? "" : "s"} to ${code(outPath)}`
|
|
21693
|
+
);
|
|
20893
21694
|
for (const t of res.tables) {
|
|
20894
21695
|
if (t.total_rows > t.rows.length) {
|
|
20895
21696
|
warn(
|
|
@@ -20898,7 +21699,7 @@ async function modulePullDataCommand(options) {
|
|
|
20898
21699
|
}
|
|
20899
21700
|
}
|
|
20900
21701
|
if (res.tables.length === 0) {
|
|
20901
|
-
info(dim("No
|
|
21702
|
+
info(dim("No tables available."));
|
|
20902
21703
|
}
|
|
20903
21704
|
return 0;
|
|
20904
21705
|
}
|
|
@@ -23509,16 +24310,43 @@ init_config();
|
|
|
23509
24310
|
init_dotenv_local();
|
|
23510
24311
|
init_manifest_io();
|
|
23511
24312
|
init_output();
|
|
24313
|
+
init_api_client();
|
|
24314
|
+
init_permission_hint();
|
|
23512
24315
|
init_install_target();
|
|
23513
|
-
init_dev();
|
|
23514
24316
|
async function queryCommand(options) {
|
|
23515
24317
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
23516
24318
|
let manifestSlug;
|
|
23517
24319
|
try {
|
|
23518
24320
|
manifestSlug = readManifestOrThrow(projectRoot).module?.slug;
|
|
23519
24321
|
} catch (err) {
|
|
23520
|
-
|
|
23521
|
-
|
|
24322
|
+
return reportRefusal(
|
|
24323
|
+
new ApiError(
|
|
24324
|
+
0,
|
|
24325
|
+
{
|
|
24326
|
+
code: "VALIDATION_ERROR",
|
|
24327
|
+
message: err.message,
|
|
24328
|
+
context: { reason: "module_context_required" }
|
|
24329
|
+
},
|
|
24330
|
+
"Module context required."
|
|
24331
|
+
),
|
|
24332
|
+
{},
|
|
24333
|
+
{ ...options, stderr: true, action: "Query" }
|
|
24334
|
+
);
|
|
24335
|
+
}
|
|
24336
|
+
if (!manifestSlug) {
|
|
24337
|
+
return reportRefusal(
|
|
24338
|
+
new ApiError(
|
|
24339
|
+
0,
|
|
24340
|
+
{
|
|
24341
|
+
code: "VALIDATION_ERROR",
|
|
24342
|
+
message: "module.json is missing module.slug.",
|
|
24343
|
+
context: { reason: "module_context_required" }
|
|
24344
|
+
},
|
|
24345
|
+
"Module context required."
|
|
24346
|
+
),
|
|
24347
|
+
{},
|
|
24348
|
+
{ ...options, stderr: true, action: "Query" }
|
|
24349
|
+
);
|
|
23522
24350
|
}
|
|
23523
24351
|
const generatedPath = join(
|
|
23524
24352
|
projectRoot,
|
|
@@ -23532,8 +24360,10 @@ async function queryCommand(options) {
|
|
|
23532
24360
|
);
|
|
23533
24361
|
return 1;
|
|
23534
24362
|
}
|
|
23535
|
-
const
|
|
24363
|
+
const keyFromProcessEnv = Boolean(process.env.DASHWISE_API_KEY);
|
|
23536
24364
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
24365
|
+
let moduleKey = keyFromProcessEnv ? "env" : process.env.DASHWISE_API_KEY ? "env_file" : "saved";
|
|
24366
|
+
let keySource = keyFromProcessEnv ? "environment" : ".env.local";
|
|
23537
24367
|
const cfg = (() => {
|
|
23538
24368
|
try {
|
|
23539
24369
|
return readConfig();
|
|
@@ -23542,46 +24372,47 @@ async function queryCommand(options) {
|
|
|
23542
24372
|
}
|
|
23543
24373
|
})();
|
|
23544
24374
|
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
if (!process.env.DASHWISE_API_KEY && manifestSlug) {
|
|
24375
|
+
const entry = readModuleEntry(manifestSlug);
|
|
24376
|
+
if (!process.env.DASHWISE_API_KEY) {
|
|
23548
24377
|
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
23549
24378
|
fail(
|
|
23550
|
-
`No key for
|
|
24379
|
+
`No key for installation #${options.install}: the cached ${code("dwk_")} key of ${code(manifestSlug)} belongs to installation #${entry.installationId}. Nothing was run.`
|
|
23551
24380
|
);
|
|
23552
24381
|
return 1;
|
|
23553
24382
|
}
|
|
23554
|
-
if (cfg) {
|
|
23555
|
-
const healed = await resolveModuleApiKey(
|
|
23556
|
-
manifestSlug,
|
|
23557
|
-
entry,
|
|
23558
|
-
apiUrl,
|
|
23559
|
-
cfg.token
|
|
23560
|
-
);
|
|
23561
|
-
if (healed) entry = healed;
|
|
23562
|
-
}
|
|
23563
24383
|
if (entry?.apiKey) {
|
|
23564
24384
|
process.env.DASHWISE_API_KEY = entry.apiKey;
|
|
24385
|
+
moduleKey = "saved";
|
|
23565
24386
|
keySource = "cached";
|
|
23566
24387
|
if (!process.env.DASHWISE_API_URL) {
|
|
23567
24388
|
process.env.DASHWISE_API_URL = apiUrl;
|
|
23568
24389
|
}
|
|
23569
24390
|
}
|
|
23570
24391
|
}
|
|
23571
|
-
if (process.env.DASHWISE_API_KEY) {
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
24392
|
+
if (!process.env.DASHWISE_API_KEY) {
|
|
24393
|
+
return reportRefusal(
|
|
24394
|
+
new ApiError(
|
|
24395
|
+
0,
|
|
24396
|
+
{
|
|
24397
|
+
code: "UNAUTHENTICATED",
|
|
24398
|
+
context: { reason: "module_api_credential_missing" },
|
|
24399
|
+
message: "No module API key is configured."
|
|
24400
|
+
},
|
|
24401
|
+
"No module API key is configured."
|
|
24402
|
+
),
|
|
24403
|
+
{},
|
|
24404
|
+
{ ...options, stderr: true, action: "Query" }
|
|
24405
|
+
);
|
|
23584
24406
|
}
|
|
24407
|
+
const target = await nameKeyTarget({
|
|
24408
|
+
apiKey: process.env.DASHWISE_API_KEY,
|
|
24409
|
+
keySource,
|
|
24410
|
+
entry,
|
|
24411
|
+
cfg,
|
|
24412
|
+
apiUrl,
|
|
24413
|
+
...options.install !== void 0 ? { install: options.install } : {}
|
|
24414
|
+
});
|
|
24415
|
+
if (target !== 0) return target;
|
|
23585
24416
|
const modes = [
|
|
23586
24417
|
options.inline !== void 0 ? "inline" : null,
|
|
23587
24418
|
options.file !== void 0 ? "file" : null,
|
|
@@ -23611,7 +24442,7 @@ async function queryCommand(options) {
|
|
|
23611
24442
|
const stream = options.stream === true;
|
|
23612
24443
|
if (options.repl) return runRepl(generatedPath);
|
|
23613
24444
|
if (options.file !== void 0)
|
|
23614
|
-
return runFile(options.file, projectRoot, options.json === true, explain, stream);
|
|
24445
|
+
return runFile(options.file, projectRoot, options.json === true, explain, stream, moduleKey);
|
|
23615
24446
|
if (options.inline !== void 0)
|
|
23616
24447
|
return runInline(
|
|
23617
24448
|
options.inline,
|
|
@@ -23619,11 +24450,12 @@ async function queryCommand(options) {
|
|
|
23619
24450
|
generatedPath,
|
|
23620
24451
|
options.json === true,
|
|
23621
24452
|
explain,
|
|
23622
|
-
stream
|
|
24453
|
+
stream,
|
|
24454
|
+
moduleKey
|
|
23623
24455
|
);
|
|
23624
24456
|
return 1;
|
|
23625
24457
|
}
|
|
23626
|
-
async function runInline(expression, projectRoot, generatedPath, jsonCompact, explain, stream) {
|
|
24458
|
+
async function runInline(expression, projectRoot, generatedPath, jsonCompact, explain, stream, moduleKey) {
|
|
23627
24459
|
let entryUrl;
|
|
23628
24460
|
try {
|
|
23629
24461
|
entryUrl = generatedEntryUrl(generatedPath);
|
|
@@ -23644,7 +24476,7 @@ async function runInline(expression, projectRoot, generatedPath, jsonCompact, ex
|
|
|
23644
24476
|
].join("\n");
|
|
23645
24477
|
writeFileSync(tmpFile, source, "utf-8");
|
|
23646
24478
|
try {
|
|
23647
|
-
return await importAndPrint(tmpFile, projectRoot, jsonCompact, explain, stream);
|
|
24479
|
+
return await importAndPrint(tmpFile, projectRoot, jsonCompact, explain, stream, moduleKey);
|
|
23648
24480
|
} finally {
|
|
23649
24481
|
try {
|
|
23650
24482
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -23652,7 +24484,7 @@ async function runInline(expression, projectRoot, generatedPath, jsonCompact, ex
|
|
|
23652
24484
|
}
|
|
23653
24485
|
}
|
|
23654
24486
|
}
|
|
23655
|
-
async function runFile(file, projectRoot, jsonCompact, explain, stream) {
|
|
24487
|
+
async function runFile(file, projectRoot, jsonCompact, explain, stream, moduleKey) {
|
|
23656
24488
|
const abs = resolve(projectRoot, file);
|
|
23657
24489
|
if (!existsSync(abs)) {
|
|
23658
24490
|
fail(`File not found: ${code(abs)}`);
|
|
@@ -23664,7 +24496,7 @@ async function runFile(file, projectRoot, jsonCompact, explain, stream) {
|
|
|
23664
24496
|
);
|
|
23665
24497
|
return 1;
|
|
23666
24498
|
}
|
|
23667
|
-
return importAndPrint(abs, projectRoot, jsonCompact, explain, stream);
|
|
24499
|
+
return importAndPrint(abs, projectRoot, jsonCompact, explain, stream, moduleKey);
|
|
23668
24500
|
}
|
|
23669
24501
|
async function runRepl(generatedPath) {
|
|
23670
24502
|
let generated;
|
|
@@ -23703,7 +24535,7 @@ async function runRepl(generatedPath) {
|
|
|
23703
24535
|
});
|
|
23704
24536
|
});
|
|
23705
24537
|
}
|
|
23706
|
-
async function importAndPrint(file, projectRoot, jsonCompact, explain, stream) {
|
|
24538
|
+
async function importAndPrint(file, projectRoot, jsonCompact, explain, stream, moduleKey) {
|
|
23707
24539
|
const originalCwd = process.cwd();
|
|
23708
24540
|
try {
|
|
23709
24541
|
process.chdir(projectRoot);
|
|
@@ -23739,11 +24571,11 @@ async function importAndPrint(file, projectRoot, jsonCompact, explain, stream) {
|
|
|
23739
24571
|
}
|
|
23740
24572
|
return 0;
|
|
23741
24573
|
} catch (err) {
|
|
23742
|
-
|
|
24574
|
+
const exit = reportRefusal(err, {}, { json: jsonCompact, stderr: true, action: "Query", moduleKey });
|
|
23743
24575
|
if (process.env.DASHWISE_DEBUG) {
|
|
23744
24576
|
console.error(err);
|
|
23745
24577
|
}
|
|
23746
|
-
return
|
|
24578
|
+
return exit;
|
|
23747
24579
|
} finally {
|
|
23748
24580
|
process.chdir(originalCwd);
|
|
23749
24581
|
}
|
|
@@ -24444,6 +25276,7 @@ init_api_client();
|
|
|
24444
25276
|
init_config();
|
|
24445
25277
|
init_manifest_io();
|
|
24446
25278
|
init_output();
|
|
25279
|
+
init_permission_hint();
|
|
24447
25280
|
var PLATFORM_DEFAULTS = ["manifest", "on", "off"];
|
|
24448
25281
|
var BUMPS = ["patch", "minor", "major"];
|
|
24449
25282
|
function parseRollout(raw) {
|
|
@@ -24667,6 +25500,7 @@ init_api_client();
|
|
|
24667
25500
|
init_config();
|
|
24668
25501
|
init_manifest_io();
|
|
24669
25502
|
init_output();
|
|
25503
|
+
init_permission_hint();
|
|
24670
25504
|
var TERMINAL_OPERATION = /* @__PURE__ */ new Set(["succeeded", "failed", "superseded"]);
|
|
24671
25505
|
async function moduleRuntimeShowCommand(slugArg, options) {
|
|
24672
25506
|
const auth = authOrFail(options.json);
|
|
@@ -24775,7 +25609,7 @@ async function moduleRuntimeSetCommand(slugArg, options) {
|
|
|
24775
25609
|
const slug = before.module.slug;
|
|
24776
25610
|
const subject = { module: slug, installationId: before.installation_id };
|
|
24777
25611
|
if (streaming !== void 0 && !options.yes && !options.json && process.stdin.isTTY) {
|
|
24778
|
-
const ok = await confirm({
|
|
25612
|
+
const ok = await confirm$1({
|
|
24779
25613
|
message: `Turn streaming ${streaming === "RESPONSE_STREAM" ? "on" : "off"} for ${slug} (install #${before.installation_id})? The switch takes about a minute, and pages may come back empty while it runs.`
|
|
24780
25614
|
});
|
|
24781
25615
|
if (isCancel(ok) || ok !== true) {
|
|
@@ -24963,10 +25797,11 @@ function sleep5(ms) {
|
|
|
24963
25797
|
|
|
24964
25798
|
// src/program.ts
|
|
24965
25799
|
init_output();
|
|
25800
|
+
init_permission_hint();
|
|
24966
25801
|
var program = new Command();
|
|
24967
25802
|
program.name("dashwise").description("Dashwise CLI \u2014 log in, author modules, publish to the marketplace.").version(getVersion(), "-v, --version", "Show the CLI version + exit").option(
|
|
24968
25803
|
"--api-url <url>",
|
|
24969
|
-
`Override Dashwise backend URL (default: $DASHWISE_API_URL, else your saved login, else ${
|
|
25804
|
+
`Override Dashwise backend URL (default: $DASHWISE_API_URL, else your saved login, else ${DEFAULT_API_URL}).`
|
|
24970
25805
|
);
|
|
24971
25806
|
program.hook("preAction", () => {
|
|
24972
25807
|
const apiUrl = program.opts().apiUrl?.trim();
|
|
@@ -24985,7 +25820,7 @@ program.command("login").description("Sign in via the device-code flow.").option
|
|
|
24985
25820
|
process.exitCode = exit;
|
|
24986
25821
|
});
|
|
24987
25822
|
program.command("doctor").description(
|
|
24988
|
-
"Check your setup:
|
|
25823
|
+
"Check your setup: target URL, login, connection, Node, CLI and SDK versions, and inside a module folder module.json, the repository link and the cloud setup files. Each item says what to do. Exits 1 when something fails."
|
|
24989
25824
|
).option("--dir <path>", "Module folder to check (default: current directory).").option("--json", "Print one JSON object { ok, checks } instead of the report.").action(async (cmdOpts) => {
|
|
24990
25825
|
process.exitCode = await doctorCommand(
|
|
24991
25826
|
{
|
|
@@ -25288,8 +26123,9 @@ moduleCmd.command("diff").description(
|
|
|
25288
26123
|
});
|
|
25289
26124
|
}
|
|
25290
26125
|
);
|
|
25291
|
-
moduleCmd.command("pull-data").description("Export the module's own-table rows to a local JSON file (for fixtures, local dev seeding).").option("--dir <path>", "Project root (default: current directory).").option("--limit <n>", "Max rows per table (1-1000, default 100).", parseIntOption).option("--out <path>", "Output file path (default: ./dashwise-data.json).").action(async (cmdOpts) => {
|
|
26126
|
+
moduleCmd.command("pull-data").description("Export the module's own-table rows to a local JSON file (for fixtures, local dev seeding).").option("--dir <path>", "Project root (default: current directory).").option("--limit <n>", "Max rows per table (1-1000, default 100).", parseIntOption).option("--out <path>", "Output file path (default: ./dashwise-data.json).").option("--json", "Emit structured errors on stderr.").action(async (cmdOpts) => {
|
|
25292
26127
|
process.exitCode = await modulePullDataCommand({
|
|
26128
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25293
26129
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25294
26130
|
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
25295
26131
|
...cmdOpts.out !== void 0 ? { out: cmdOpts.out } : {}
|
|
@@ -25655,8 +26491,15 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
25655
26491
|
}
|
|
25656
26492
|
);
|
|
25657
26493
|
moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description(
|
|
25658
|
-
"Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently: an upgrade first prints what it does to the
|
|
25659
|
-
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option(
|
|
26494
|
+
"Install (deploy) a published module into a workspace \u2014 provisions the runtime and prints the public URL. Upgrades/repairs an existing production install idempotently: an upgrade first prints what it does to the installation's data (tables and fields it moves to the Trash or converts, with their counts), asks once, and sends the preview's drift_hash. A move that loses data is accepted only by a person: the one question in a terminal, or --accept-data-loss <drift_hash> with the hash the preview printed. --yes never accepts data loss; with no terminal, or inside an AI agent session, a lossy move is refused and the refusal prints the re-run line. When only a dev install exists it asks whether to install alongside it (--keep-dev-install) or replace it (--replace-dev-install), rather than silently landing on the dev row. Exits 3 \u2014 \"deployed, setup pending\" \u2014 when a required `uses` slot is still unbound: the deploy succeeded but the module throws on its first data call until an admin binds it."
|
|
26495
|
+
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--version <semver>", "Install a specific published version (default: latest).").option(
|
|
26496
|
+
"--force",
|
|
26497
|
+
"Upgrade even when it breaks consumer modules (UPGRADE_BREAKS_CONSUMERS): shows which modules it disconnects and what it changes, asks to confirm (--yes confirms disconnections, never data loss), then upgrades exactly that plan."
|
|
26498
|
+
).option(
|
|
26499
|
+
"--installation <id>",
|
|
26500
|
+
"Update exactly this production installation (needed when several exist, or to update a held one).",
|
|
26501
|
+
parsePositiveIdOption
|
|
26502
|
+
).option(
|
|
25660
26503
|
"--keep-dev-install",
|
|
25661
26504
|
"Install production ALONGSIDE the workspace's kind=local dev install, leaving local dev working. Non-destructive. Backends >= 2026-07-15 keep a separate live-install slot per kind group, so the two coexist."
|
|
25662
26505
|
).option(
|
|
@@ -25668,6 +26511,7 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
25668
26511
|
).option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
25669
26512
|
async (slug, cmdOpts) => {
|
|
25670
26513
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
26514
|
+
...cmdOpts.installation !== void 0 ? { installation: cmdOpts.installation } : {},
|
|
25671
26515
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
25672
26516
|
...cmdOpts.version !== void 0 ? { version: cmdOpts.version } : {},
|
|
25673
26517
|
...cmdOpts.force !== void 0 ? { force: cmdOpts.force } : {},
|
|
@@ -25695,6 +26539,63 @@ moduleCmd.command("uninstall [slug]").description(
|
|
|
25695
26539
|
});
|
|
25696
26540
|
}
|
|
25697
26541
|
);
|
|
26542
|
+
var invitesCmd = moduleCmd.command("invites").description(
|
|
26543
|
+
"Install invitations: send, list and withdraw them for your module, or see and decline the ones your company received."
|
|
26544
|
+
);
|
|
26545
|
+
var inviteIdArg = (value) => value !== void 0 && /^[1-9][0-9]*$/.test(value) ? Number(value) : void 0;
|
|
26546
|
+
invitesCmd.command("list <slug>").description("List the open install invitations for your module.").option("--json", "Emit machine-readable JSON.").action(async (slug, cmdOpts) => {
|
|
26547
|
+
process.exitCode = await moduleInvitesListCommand(slug, {
|
|
26548
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26549
|
+
});
|
|
26550
|
+
});
|
|
26551
|
+
invitesCmd.command("send <slug>").description(
|
|
26552
|
+
"Invite another company to install your module. Needs admin rights in the module's company. Asks before sending; pass --yes in scripts."
|
|
26553
|
+
).option("--to <company>", "The company id to invite, or the slug of a company you belong to.").option("--note <text>", "A short note shown to the invited company (max 1000 characters).").option("-y, --yes", "Send without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(
|
|
26554
|
+
async (slug, cmdOpts) => {
|
|
26555
|
+
process.exitCode = await moduleInvitesSendCommand(slug, {
|
|
26556
|
+
...cmdOpts.to !== void 0 ? { to: cmdOpts.to } : {},
|
|
26557
|
+
...cmdOpts.note !== void 0 ? { note: cmdOpts.note } : {},
|
|
26558
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26559
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26560
|
+
});
|
|
26561
|
+
}
|
|
26562
|
+
);
|
|
26563
|
+
invitesCmd.command("revoke <slug> <invite-id>").description(
|
|
26564
|
+
"Withdraw an install invitation. Installations made before stay. Asks first; pass --yes in scripts."
|
|
26565
|
+
).option("-y, --yes", "Withdraw without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(async (slug, inviteId, cmdOpts) => {
|
|
26566
|
+
process.exitCode = await moduleInvitesRevokeCommand(slug, inviteIdArg(inviteId), {
|
|
26567
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26568
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26569
|
+
});
|
|
26570
|
+
});
|
|
26571
|
+
invitesCmd.command("received").description("List the open install invitations your company received (company admins).").option("--workspace <slug-or-id>", "Company (default: config default or interactive picker).").option("--json", "Emit machine-readable JSON.").action(async (cmdOpts) => {
|
|
26572
|
+
process.exitCode = await moduleInvitesReceivedCommand({
|
|
26573
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26574
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26575
|
+
});
|
|
26576
|
+
});
|
|
26577
|
+
invitesCmd.command("decline <invite-id>").description(
|
|
26578
|
+
"Decline an install invitation your company received (company admins). Asks first; pass --yes in scripts."
|
|
26579
|
+
).option("--workspace <slug-or-id>", "Company (default: config default or interactive picker).").option("-y, --yes", "Decline without asking (required in non-interactive or --json mode).").option("--json", "Emit machine-readable JSON.").action(
|
|
26580
|
+
async (inviteId, cmdOpts) => {
|
|
26581
|
+
process.exitCode = await moduleInvitesDeclineCommand(inviteIdArg(inviteId), {
|
|
26582
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26583
|
+
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {},
|
|
26584
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26585
|
+
});
|
|
26586
|
+
}
|
|
26587
|
+
);
|
|
26588
|
+
moduleCmd.command("installations [slug]").description(
|
|
26589
|
+
"List the installations in a company that you can see: id, module, name, audience, version, held state and public address. With a slug, only that module. Read-only."
|
|
26590
|
+
).option("--workspace <slug-or-id>", "Company (default: cached module company, config default, or interactive picker).").option("--dev", "Include development installations.").option("--json", "Emit machine-readable JSON.").action(
|
|
26591
|
+
async (slug, cmdOpts) => {
|
|
26592
|
+
process.exitCode = await moduleInstallationsCommand(slug, {
|
|
26593
|
+
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26594
|
+
...cmdOpts.dev !== void 0 ? { dev: cmdOpts.dev } : {},
|
|
26595
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {}
|
|
26596
|
+
});
|
|
26597
|
+
}
|
|
26598
|
+
);
|
|
25698
26599
|
moduleCmd.command("status [slug]").description(
|
|
25699
26600
|
"Show a module's deploy state in a workspace: install kind, installed vs latest published version, runtime provisioning, and the public URL."
|
|
25700
26601
|
).option("--workspace <slug-or-id>", "Target workspace (default: cached module workspace, config default, or interactive picker).").option("--dir <path>", "Project root for the module.json slug lookup (default: current directory).").option("--json", "Emit machine-readable JSON.").action(
|
|
@@ -25889,9 +26790,10 @@ var schemaCmd = program.command("schema").description(
|
|
|
25889
26790
|
);
|
|
25890
26791
|
schemaCmd.command("push").description(
|
|
25891
26792
|
"Apply the LOCAL module.json to the dev install via PUT /api/modules/:id/draft/manifest (greenfield or delta). Destructive removals (tables/fields) are previewed and require confirmation unless --yes. Afterwards the canonical manifest is synced back with merge semantics (local-only rbac/indexes blocks survive)."
|
|
25892
|
-
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").option("-y, --yes", "Skip the destructive-change confirmation (CI / scripted runs).").action(async (cmdOpts) => {
|
|
26793
|
+
).option("--dir <path>", "Project root containing `module.json` (default: current directory).").option("-y, --yes", "Skip the destructive-change confirmation (CI / scripted runs).").option("--json", "Emit structured errors on stderr.").action(async (cmdOpts) => {
|
|
25893
26794
|
const { schemaPushCommand: schemaPushCommand2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
25894
26795
|
process.exitCode = await schemaPushCommand2({
|
|
26796
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25895
26797
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25896
26798
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {}
|
|
25897
26799
|
});
|
|
@@ -25972,7 +26874,13 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
25972
26874
|
}
|
|
25973
26875
|
);
|
|
25974
26876
|
function getVersion() {
|
|
25975
|
-
return "0.
|
|
26877
|
+
return "0.28.0";
|
|
26878
|
+
}
|
|
26879
|
+
function parsePositiveIdOption(value) {
|
|
26880
|
+
if (!/^[1-9][0-9]*$/.test(value.trim())) {
|
|
26881
|
+
throw new Error(`Expected an installation id (a positive whole number), got '${value}'`);
|
|
26882
|
+
}
|
|
26883
|
+
return Number(value.trim());
|
|
25976
26884
|
}
|
|
25977
26885
|
function parseIntOption(value) {
|
|
25978
26886
|
const n = Number.parseInt(value, 10);
|