@dashai/cli 0.27.0 → 0.28.1
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 +1159 -712
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
package/dist/bin.js
CHANGED
|
@@ -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_]*$/;
|
|
@@ -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\` /
|
|
@@ -12909,13 +12990,15 @@ function companyApiHost(apiUrl) {
|
|
|
12909
12990
|
return null;
|
|
12910
12991
|
}
|
|
12911
12992
|
}
|
|
12993
|
+
function docsApiHost(apiUrl) {
|
|
12994
|
+
return companyApiHost(apiUrl) ?? new URL(DEFAULT_API_URL).host;
|
|
12995
|
+
}
|
|
12912
12996
|
function apiHostLine(apiUrl) {
|
|
12913
|
-
const host =
|
|
12914
|
-
return
|
|
12997
|
+
const host = docsApiHost(apiUrl);
|
|
12998
|
+
return `\`${host}\` (this company's Dashwise API; the company uses Dashwise at ${HOSTED_API_HOSTS[host] ?? host})`;
|
|
12915
12999
|
}
|
|
12916
13000
|
function codexEnvironmentInstructions(apiUrl) {
|
|
12917
|
-
const
|
|
12918
|
-
const apiUrlValue = host ? `https://${host}` : "https://<the company API host above>";
|
|
13001
|
+
const apiUrlValue = `https://${docsApiHost(apiUrl)}`;
|
|
12919
13002
|
return `## Codex cloud environment
|
|
12920
13003
|
|
|
12921
13004
|
When the person asks you to set up the Codex cloud environment for this
|
|
@@ -12962,14 +13045,13 @@ Publishing is their click.
|
|
|
12962
13045
|
`;
|
|
12963
13046
|
}
|
|
12964
13047
|
function claudeCloudNote(apiUrl) {
|
|
12965
|
-
const host = companyApiHost(apiUrl);
|
|
12966
13048
|
return `## Claude cloud
|
|
12967
13049
|
|
|
12968
13050
|
Claude Code on the web has no environment setup chat. It reads this repository
|
|
12969
13051
|
and \`CLAUDE.md\`, and \`.claude/settings.json\` runs \`scripts/cloud-setup.sh\`
|
|
12970
13052
|
when a cloud session starts. Node 22 or 24 both work. If
|
|
12971
13053
|
the environment has a network setting, allow the same domains as for Codex:
|
|
12972
|
-
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} and
|
|
13054
|
+
${CLOUD_ALLOWED_HOSTS.map((h) => `\`${h}\``).join(", ")} and \`${docsApiHost(apiUrl)}\`. No secrets.
|
|
12973
13055
|
`;
|
|
12974
13056
|
}
|
|
12975
13057
|
function agentsMd(opts) {
|
|
@@ -13213,15 +13295,17 @@ remain human actions. No Dashwise token is needed for
|
|
|
13213
13295
|
repository code delivery.
|
|
13214
13296
|
`;
|
|
13215
13297
|
}
|
|
13216
|
-
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;
|
|
13217
13299
|
var init_scaffold = __esm({
|
|
13218
13300
|
"src/lib/scaffold.ts"() {
|
|
13301
|
+
init_config();
|
|
13219
13302
|
init_sdk_version();
|
|
13220
13303
|
init_simple();
|
|
13221
13304
|
init_custom();
|
|
13222
13305
|
NODE_ENGINES = "22.x || 24.x";
|
|
13223
13306
|
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
13224
13307
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
13308
|
+
GEIST_DEP_RANGE = "^1.7.2";
|
|
13225
13309
|
MODULE_SLUG_REGEX = /^[a-z]([a-z0-9-]*[a-z0-9])?$/;
|
|
13226
13310
|
CLOUD_ALLOWED_HOSTS = [
|
|
13227
13311
|
"registry.npmjs.org",
|
|
@@ -14054,214 +14138,547 @@ var init_diff_check = __esm({
|
|
|
14054
14138
|
}
|
|
14055
14139
|
});
|
|
14056
14140
|
|
|
14057
|
-
// src/lib/
|
|
14058
|
-
function
|
|
14059
|
-
|
|
14060
|
-
if (!Array.isArray(tables)) return [];
|
|
14061
|
-
const warnings = [];
|
|
14062
|
-
for (const table of tables) {
|
|
14063
|
-
if (typeof table?.slug !== "string" || !Array.isArray(table.fields)) continue;
|
|
14064
|
-
for (const field of table.fields) {
|
|
14065
|
-
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14066
|
-
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14067
|
-
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14068
|
-
warnings.push({
|
|
14069
|
-
table: table.slug,
|
|
14070
|
-
field: field.slug,
|
|
14071
|
-
type: field.type,
|
|
14072
|
-
message: `${table.slug}.${field.slug} is a ${field.type} field named like a reference. If it points at a row in Dashwise, use a link_row field: plain ids break silently when data is copied to another installation. Ignore this for ids from outside systems.`
|
|
14073
|
-
});
|
|
14074
|
-
}
|
|
14075
|
-
}
|
|
14076
|
-
return warnings;
|
|
14077
|
-
}
|
|
14078
|
-
var REFERENCE_NAME, PLAIN_TYPES;
|
|
14079
|
-
var init_manifest_reference_lint = __esm({
|
|
14080
|
-
"src/lib/manifest-reference-lint.ts"() {
|
|
14081
|
-
REFERENCE_NAME = /(?:_id|_ref|Id|Ref)$/;
|
|
14082
|
-
PLAIN_TYPES = /* @__PURE__ */ new Set(["number", "text", "autonumber"]);
|
|
14083
|
-
}
|
|
14084
|
-
});
|
|
14085
|
-
function loadSchemaCommandContext(opts) {
|
|
14086
|
-
const projectRoot = resolve(opts.dir ?? process.cwd());
|
|
14087
|
-
const manifestPath2 = join(projectRoot, "module.json");
|
|
14088
|
-
const manifest = readManifestOrThrow(projectRoot);
|
|
14089
|
-
const manifestRawBytes = readFileSync(manifestPath2, "utf-8");
|
|
14090
|
-
const slug = manifest.module?.slug;
|
|
14091
|
-
if (!slug) {
|
|
14092
|
-
throw new Error(
|
|
14093
|
-
`module.json at ${manifestPath2} has no module.slug \u2014 the manifest is malformed.`
|
|
14094
|
-
);
|
|
14095
|
-
}
|
|
14096
|
-
const cfg = readConfig();
|
|
14097
|
-
if (!cfg) {
|
|
14098
|
-
throw new Error("Not logged in. Run `dashwise login` first.");
|
|
14099
|
-
}
|
|
14100
|
-
const entry = readModuleEntry(slug);
|
|
14101
|
-
if (!entry) {
|
|
14102
|
-
throw new Error(
|
|
14103
|
-
`No dev install cached for module "${slug}". Run \`dashwise init ${slug}\` to provision one (existing files are preserved).`
|
|
14104
|
-
);
|
|
14105
|
-
}
|
|
14106
|
-
const token = resolveSchemaAuthToken(entry, slug);
|
|
14107
|
-
return {
|
|
14108
|
-
projectRoot,
|
|
14109
|
-
manifestPath: manifestPath2,
|
|
14110
|
-
manifest,
|
|
14111
|
-
manifestRawBytes,
|
|
14112
|
-
slug,
|
|
14113
|
-
cfg,
|
|
14114
|
-
entry,
|
|
14115
|
-
requestOptions: {
|
|
14116
|
-
baseUrl: resolveApiUrl(),
|
|
14117
|
-
auth: { token }
|
|
14118
|
-
}
|
|
14119
|
-
};
|
|
14120
|
-
}
|
|
14121
|
-
function resolveSchemaAuthToken(entry, slug) {
|
|
14122
|
-
if (entry.apiKey) return entry.apiKey;
|
|
14123
|
-
throw new Error(
|
|
14124
|
-
`The cached credential for "${slug}" is a legacy runtime token, which the schema endpoints no longer accept. Run \`dashwise dev\` once (it mints a dwk_ API key and updates your config), or \`dashwise init ${slug}\` to re-provision (existing files are preserved).`
|
|
14125
|
-
);
|
|
14141
|
+
// src/lib/permission-hint.ts
|
|
14142
|
+
function str(v) {
|
|
14143
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
14126
14144
|
}
|
|
14127
|
-
|
|
14128
|
-
"
|
|
14129
|
-
init_config();
|
|
14130
|
-
init_manifest_io();
|
|
14131
|
-
}
|
|
14132
|
-
});
|
|
14133
|
-
function hydrateEnvFromDotenvLocal(projectRoot) {
|
|
14134
|
-
const envFile = join(projectRoot, ".env.local");
|
|
14135
|
-
if (!existsSync(envFile)) return;
|
|
14136
|
-
const raw = readFileSync(envFile, "utf-8");
|
|
14137
|
-
for (const line of raw.split(/\r?\n/)) {
|
|
14138
|
-
const trimmed = line.trim();
|
|
14139
|
-
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
14140
|
-
const eq = trimmed.indexOf("=");
|
|
14141
|
-
if (eq <= 0) continue;
|
|
14142
|
-
const key = trimmed.slice(0, eq).trim();
|
|
14143
|
-
let value = trimmed.slice(eq + 1).trim();
|
|
14144
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
14145
|
-
value = value.slice(1, -1);
|
|
14146
|
-
}
|
|
14147
|
-
if (process.env[key] === void 0) {
|
|
14148
|
-
process.env[key] = value;
|
|
14149
|
-
}
|
|
14150
|
-
}
|
|
14145
|
+
function num(v) {
|
|
14146
|
+
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
14151
14147
|
}
|
|
14152
|
-
|
|
14153
|
-
|
|
14154
|
-
|
|
14155
|
-
});
|
|
14156
|
-
|
|
14157
|
-
// src/lib/install-target.ts
|
|
14158
|
-
function keyDisplayPrefix(apiKey) {
|
|
14159
|
-
if (!apiKey.startsWith(KEY_SCHEME)) return null;
|
|
14160
|
-
const prefix = apiKey.slice(KEY_SCHEME.length, KEY_SCHEME.length + KEY_PREFIX_CHARS);
|
|
14161
|
-
return prefix.length === KEY_PREFIX_CHARS ? prefix : null;
|
|
14148
|
+
function featureFromPermission(permission) {
|
|
14149
|
+
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
14150
|
+
return m?.[1];
|
|
14162
14151
|
}
|
|
14163
|
-
|
|
14164
|
-
const
|
|
14165
|
-
if (
|
|
14166
|
-
|
|
14167
|
-
return api_keys.some(
|
|
14168
|
-
(k) => k.installation_id === installationId && k.key_prefix === prefix && (k.status === void 0 || k.status === "active") && k.revoked_at === null && (k.expires_at === null || new Date(k.expires_at) > now)
|
|
14169
|
-
);
|
|
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);
|
|
14170
14156
|
}
|
|
14171
|
-
function
|
|
14172
|
-
|
|
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}.`;
|
|
14173
14160
|
}
|
|
14174
|
-
|
|
14175
|
-
const
|
|
14176
|
-
const
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
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
|
+
};
|
|
14187
14180
|
}
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
info(formatInstallTarget(entry.installationId));
|
|
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
|
+
};
|
|
14194
14186
|
}
|
|
14195
|
-
return
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
fail(
|
|
14200
|
-
`${code("--install")} looks the install up with your CLI login, and there is none. Run ${code("dashwise login")} first. Nothing was run.`
|
|
14201
|
-
);
|
|
14202
|
-
return 1;
|
|
14203
|
-
}
|
|
14204
|
-
let belongs;
|
|
14205
|
-
try {
|
|
14206
|
-
belongs = await keyBelongsToInstall(apiKey, install, request);
|
|
14207
|
-
} catch (err) {
|
|
14208
|
-
fail(
|
|
14209
|
-
`Could not read the keys of install #${install} to confirm ${source} (${describe(err)}). Refusing to run against an unconfirmed install. Nothing was run.`
|
|
14210
|
-
);
|
|
14211
|
-
return 1;
|
|
14212
|
-
}
|
|
14213
|
-
const cachedDev = entry?.installationId === install ? "the cached dev install, " : "";
|
|
14214
|
-
if (!belongs) {
|
|
14215
|
-
const other = entry && entry.installationId !== install && entry.apiKey === apiKey ? ` It is the cached key of install #${entry.installationId}.` : "";
|
|
14216
|
-
fail(`${source} is not a live key of install #${install}.${other} Nothing was run.`);
|
|
14217
|
-
info(
|
|
14218
|
-
`Use a key of install #${install} (mint one with ${code(`dashwise api-keys create <name> --install ${install}`)}), or drop ${code("--install")} to run against the key's own install.`
|
|
14219
|
-
);
|
|
14220
|
-
return 1;
|
|
14221
|
-
}
|
|
14222
|
-
info(formatInstallTarget(install, `${cachedDev}key checked`));
|
|
14223
|
-
return 0;
|
|
14224
|
-
}
|
|
14225
|
-
function describe(err) {
|
|
14226
|
-
if (err instanceof ApiError) return `${err.code || err.status} ${err.message}`;
|
|
14227
|
-
return err.message;
|
|
14228
|
-
}
|
|
14229
|
-
var KEY_PREFIX_CHARS, KEY_SCHEME, LOOKUP_TIMEOUT_MS;
|
|
14230
|
-
var init_install_target = __esm({
|
|
14231
|
-
"src/lib/install-target.ts"() {
|
|
14232
|
-
init_api_client();
|
|
14233
|
-
init_output();
|
|
14234
|
-
KEY_PREFIX_CHARS = 8;
|
|
14235
|
-
KEY_SCHEME = "dwk_";
|
|
14236
|
-
LOOKUP_TIMEOUT_MS = 1500;
|
|
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
|
+
};
|
|
14237
14191
|
}
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
runCommand: () => runCommand
|
|
14244
|
-
});
|
|
14245
|
-
async function runCommand(cmdArgs, options) {
|
|
14246
|
-
if (!cmdArgs || cmdArgs.length === 0) {
|
|
14247
|
-
fail(
|
|
14248
|
-
`Usage: ${code("dashwise run -- <command\u2026>")} (e.g. ${code("dashwise run -- node seed.mjs")})`
|
|
14249
|
-
);
|
|
14250
|
-
return 1;
|
|
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
|
+
};
|
|
14251
14197
|
}
|
|
14252
|
-
if (
|
|
14253
|
-
|
|
14254
|
-
|
|
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
|
+
};
|
|
14255
14203
|
}
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
|
|
14261
|
-
fail(err.message);
|
|
14262
|
-
return 1;
|
|
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
|
+
};
|
|
14263
14209
|
}
|
|
14264
|
-
|
|
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
|
+
|
|
14474
|
+
// src/lib/manifest-reference-lint.ts
|
|
14475
|
+
function referenceFieldWarnings(manifest) {
|
|
14476
|
+
const tables = manifest?.owns?.tables;
|
|
14477
|
+
if (!Array.isArray(tables)) return [];
|
|
14478
|
+
const warnings = [];
|
|
14479
|
+
for (const table of tables) {
|
|
14480
|
+
if (typeof table?.slug !== "string" || !Array.isArray(table.fields)) continue;
|
|
14481
|
+
for (const field of table.fields) {
|
|
14482
|
+
if (typeof field?.slug !== "string" || typeof field.type !== "string") continue;
|
|
14483
|
+
if (field.slug === "id" || !REFERENCE_NAME.test(field.slug)) continue;
|
|
14484
|
+
if (!PLAIN_TYPES.has(field.type)) continue;
|
|
14485
|
+
warnings.push({
|
|
14486
|
+
table: table.slug,
|
|
14487
|
+
field: field.slug,
|
|
14488
|
+
type: field.type,
|
|
14489
|
+
message: `${table.slug}.${field.slug} is a ${field.type} field named like a reference. If it points at a row in Dashwise, use a link_row field: plain ids break silently when data is copied to another installation. Ignore this for ids from outside systems.`
|
|
14490
|
+
});
|
|
14491
|
+
}
|
|
14492
|
+
}
|
|
14493
|
+
return warnings;
|
|
14494
|
+
}
|
|
14495
|
+
var REFERENCE_NAME, PLAIN_TYPES;
|
|
14496
|
+
var init_manifest_reference_lint = __esm({
|
|
14497
|
+
"src/lib/manifest-reference-lint.ts"() {
|
|
14498
|
+
REFERENCE_NAME = /(?:_id|_ref|Id|Ref)$/;
|
|
14499
|
+
PLAIN_TYPES = /* @__PURE__ */ new Set(["number", "text", "autonumber"]);
|
|
14500
|
+
}
|
|
14501
|
+
});
|
|
14502
|
+
function loadSchemaCommandContext(opts) {
|
|
14503
|
+
const projectRoot = resolve(opts.dir ?? process.cwd());
|
|
14504
|
+
const manifestPath2 = join(projectRoot, "module.json");
|
|
14505
|
+
const manifest = readManifestOrThrow(projectRoot);
|
|
14506
|
+
const manifestRawBytes = readFileSync(manifestPath2, "utf-8");
|
|
14507
|
+
const slug = manifest.module?.slug;
|
|
14508
|
+
if (!slug) {
|
|
14509
|
+
throw new Error(
|
|
14510
|
+
`module.json at ${manifestPath2} has no module.slug \u2014 the manifest is malformed.`
|
|
14511
|
+
);
|
|
14512
|
+
}
|
|
14513
|
+
const cfg = readConfig();
|
|
14514
|
+
if (!cfg) {
|
|
14515
|
+
throw new Error("Not logged in. Run `dashwise login` first.");
|
|
14516
|
+
}
|
|
14517
|
+
const entry = readModuleEntry(slug);
|
|
14518
|
+
if (!entry) {
|
|
14519
|
+
throw new Error(
|
|
14520
|
+
`No dev install cached for module "${slug}". Run \`dashwise init ${slug}\` to provision one (existing files are preserved).`
|
|
14521
|
+
);
|
|
14522
|
+
}
|
|
14523
|
+
const token = resolveSchemaAuthToken(entry, slug);
|
|
14524
|
+
return {
|
|
14525
|
+
projectRoot,
|
|
14526
|
+
manifestPath: manifestPath2,
|
|
14527
|
+
manifest,
|
|
14528
|
+
manifestRawBytes,
|
|
14529
|
+
slug,
|
|
14530
|
+
cfg,
|
|
14531
|
+
entry,
|
|
14532
|
+
requestOptions: {
|
|
14533
|
+
baseUrl: resolveApiUrl(),
|
|
14534
|
+
auth: { token }
|
|
14535
|
+
}
|
|
14536
|
+
};
|
|
14537
|
+
}
|
|
14538
|
+
function resolveSchemaAuthToken(entry, slug) {
|
|
14539
|
+
if (entry.apiKey) return entry.apiKey;
|
|
14540
|
+
throw new Error(
|
|
14541
|
+
`The cached credential for "${slug}" is a legacy runtime token, which the schema endpoints no longer accept. Run \`dashwise dev\` once (it mints a dwk_ API key and updates your config), or \`dashwise init ${slug}\` to re-provision (existing files are preserved).`
|
|
14542
|
+
);
|
|
14543
|
+
}
|
|
14544
|
+
var init_schema_command_context = __esm({
|
|
14545
|
+
"src/lib/schema-command-context.ts"() {
|
|
14546
|
+
init_config();
|
|
14547
|
+
init_manifest_io();
|
|
14548
|
+
}
|
|
14549
|
+
});
|
|
14550
|
+
function hydrateEnvFromDotenvLocal(projectRoot) {
|
|
14551
|
+
const envFile = join(projectRoot, ".env.local");
|
|
14552
|
+
if (!existsSync(envFile)) return;
|
|
14553
|
+
const raw = readFileSync(envFile, "utf-8");
|
|
14554
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
14555
|
+
const trimmed = line.trim();
|
|
14556
|
+
if (trimmed.length === 0 || trimmed.startsWith("#")) continue;
|
|
14557
|
+
const eq = trimmed.indexOf("=");
|
|
14558
|
+
if (eq <= 0) continue;
|
|
14559
|
+
const key = trimmed.slice(0, eq).trim();
|
|
14560
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
14561
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
14562
|
+
value = value.slice(1, -1);
|
|
14563
|
+
}
|
|
14564
|
+
if (process.env[key] === void 0) {
|
|
14565
|
+
process.env[key] = value;
|
|
14566
|
+
}
|
|
14567
|
+
}
|
|
14568
|
+
}
|
|
14569
|
+
var init_dotenv_local = __esm({
|
|
14570
|
+
"src/lib/dotenv-local.ts"() {
|
|
14571
|
+
}
|
|
14572
|
+
});
|
|
14573
|
+
|
|
14574
|
+
// src/lib/install-target.ts
|
|
14575
|
+
function keyDisplayPrefix(apiKey) {
|
|
14576
|
+
if (!apiKey.startsWith(KEY_SCHEME)) return null;
|
|
14577
|
+
const prefix = apiKey.slice(KEY_SCHEME.length, KEY_SCHEME.length + KEY_PREFIX_CHARS);
|
|
14578
|
+
return prefix.length === KEY_PREFIX_CHARS ? prefix : null;
|
|
14579
|
+
}
|
|
14580
|
+
async function keyBelongsToInstall(apiKey, installationId, options, now = /* @__PURE__ */ new Date()) {
|
|
14581
|
+
const prefix = keyDisplayPrefix(apiKey);
|
|
14582
|
+
if (prefix === null) return false;
|
|
14583
|
+
const { api_keys } = await listApiKeys(installationId, options);
|
|
14584
|
+
return api_keys.some(
|
|
14585
|
+
(k) => k.installation_id === installationId && k.key_prefix === prefix && (k.status === void 0 || k.status === "active") && k.revoked_at === null && (k.expires_at === null || new Date(k.expires_at) > now)
|
|
14586
|
+
);
|
|
14587
|
+
}
|
|
14588
|
+
function formatInstallTarget(id, detail) {
|
|
14589
|
+
return detail ? `\u2192 install #${id} (${detail})` : `\u2192 install #${id}`;
|
|
14590
|
+
}
|
|
14591
|
+
async function nameKeyTarget(args) {
|
|
14592
|
+
const { apiKey, keySource, entry, cfg, apiUrl, install } = args;
|
|
14593
|
+
const request = cfg ? { baseUrl: apiUrl, auth: { token: cfg.token }, signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) } : null;
|
|
14594
|
+
if (install === void 0) {
|
|
14595
|
+
if (keySource !== "cached" || !entry) {
|
|
14596
|
+
info(
|
|
14597
|
+
`\u2192 install not checked: ${code("DASHWISE_API_KEY")} comes from ${keySource === "environment" ? "the environment" : code(".env.local")}. Pass ${code("--install <id>")} to name its target and refuse a mismatch.`
|
|
14598
|
+
);
|
|
14599
|
+
return 0;
|
|
14600
|
+
}
|
|
14601
|
+
if (!request || entry.workspaceId === void 0) {
|
|
14602
|
+
info(formatInstallTarget(entry.installationId, "the cached dev install"));
|
|
14603
|
+
return 0;
|
|
14604
|
+
}
|
|
14605
|
+
try {
|
|
14606
|
+
const workspaces = await apiRequest("GET", "/api/workspaces", void 0, request);
|
|
14607
|
+
const ws = workspaces.find((w) => w.id === entry.workspaceId);
|
|
14608
|
+
info(formatInstallTarget(entry.installationId, `${ws ? ws.slug : `workspace #${entry.workspaceId}`}, local`));
|
|
14609
|
+
} catch {
|
|
14610
|
+
info(formatInstallTarget(entry.installationId));
|
|
14611
|
+
}
|
|
14612
|
+
return 0;
|
|
14613
|
+
}
|
|
14614
|
+
const source = keySource === "cached" ? "the cached key" : `${code("DASHWISE_API_KEY")} (from ${keySource === "environment" ? "the environment" : code(".env.local")})`;
|
|
14615
|
+
if (!request) {
|
|
14616
|
+
fail(
|
|
14617
|
+
`${code("--install")} looks the install up with your CLI login, and there is none. Run ${code("dashwise login")} first. Nothing was run.`
|
|
14618
|
+
);
|
|
14619
|
+
return 1;
|
|
14620
|
+
}
|
|
14621
|
+
let belongs;
|
|
14622
|
+
try {
|
|
14623
|
+
belongs = await keyBelongsToInstall(apiKey, install, request);
|
|
14624
|
+
} catch (err) {
|
|
14625
|
+
fail(
|
|
14626
|
+
`Could not read the keys of install #${install} to confirm ${source} (${describe(err)}). Refusing to run against an unconfirmed install. Nothing was run.`
|
|
14627
|
+
);
|
|
14628
|
+
return 1;
|
|
14629
|
+
}
|
|
14630
|
+
const cachedDev = entry?.installationId === install ? "the cached dev install, " : "";
|
|
14631
|
+
if (!belongs) {
|
|
14632
|
+
const other = entry && entry.installationId !== install && entry.apiKey === apiKey ? ` It is the cached key of install #${entry.installationId}.` : "";
|
|
14633
|
+
fail(`${source} is not a live key of install #${install}.${other} Nothing was run.`);
|
|
14634
|
+
info(
|
|
14635
|
+
`Use a key of install #${install} (mint one with ${code(`dashwise api-keys create <name> --install ${install}`)}), or drop ${code("--install")} to run against the key's own install.`
|
|
14636
|
+
);
|
|
14637
|
+
return 1;
|
|
14638
|
+
}
|
|
14639
|
+
info(formatInstallTarget(install, `${cachedDev}key checked`));
|
|
14640
|
+
return 0;
|
|
14641
|
+
}
|
|
14642
|
+
function describe(err) {
|
|
14643
|
+
if (err instanceof ApiError) return `${err.code || err.status} ${err.message}`;
|
|
14644
|
+
return err.message;
|
|
14645
|
+
}
|
|
14646
|
+
var KEY_PREFIX_CHARS, KEY_SCHEME, LOOKUP_TIMEOUT_MS;
|
|
14647
|
+
var init_install_target = __esm({
|
|
14648
|
+
"src/lib/install-target.ts"() {
|
|
14649
|
+
init_api_client();
|
|
14650
|
+
init_output();
|
|
14651
|
+
KEY_PREFIX_CHARS = 8;
|
|
14652
|
+
KEY_SCHEME = "dwk_";
|
|
14653
|
+
LOOKUP_TIMEOUT_MS = 1500;
|
|
14654
|
+
}
|
|
14655
|
+
});
|
|
14656
|
+
|
|
14657
|
+
// src/commands/run.ts
|
|
14658
|
+
var run_exports = {};
|
|
14659
|
+
__export(run_exports, {
|
|
14660
|
+
runCommand: () => runCommand
|
|
14661
|
+
});
|
|
14662
|
+
async function runCommand(cmdArgs, options) {
|
|
14663
|
+
if (!cmdArgs || cmdArgs.length === 0) {
|
|
14664
|
+
fail(
|
|
14665
|
+
`Usage: ${code("dashwise run -- <command\u2026>")} (e.g. ${code("dashwise run -- node seed.mjs")})`
|
|
14666
|
+
);
|
|
14667
|
+
return 1;
|
|
14668
|
+
}
|
|
14669
|
+
if (options.install !== void 0 && (!Number.isInteger(options.install) || options.install <= 0)) {
|
|
14670
|
+
fail(`--install expects a positive integer installation id (got "${options.install}").`);
|
|
14671
|
+
return 1;
|
|
14672
|
+
}
|
|
14673
|
+
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
14674
|
+
let manifestSlug;
|
|
14675
|
+
try {
|
|
14676
|
+
manifestSlug = readManifestOrThrow(projectRoot).module?.slug;
|
|
14677
|
+
} catch (err) {
|
|
14678
|
+
fail(err.message);
|
|
14679
|
+
return 1;
|
|
14680
|
+
}
|
|
14681
|
+
if (!manifestSlug) {
|
|
14265
14682
|
fail(`module.json has no ${code("module.slug")} \u2014 the manifest is malformed.`);
|
|
14266
14683
|
return 1;
|
|
14267
14684
|
}
|
|
@@ -14530,48 +14947,58 @@ __export(push_exports, {
|
|
|
14530
14947
|
schemaPushCommand: () => schemaPushCommand
|
|
14531
14948
|
});
|
|
14532
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
|
+
);
|
|
14533
14962
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
14534
14963
|
let manifest;
|
|
14535
14964
|
try {
|
|
14536
14965
|
manifest = readManifestOrThrow(projectRoot);
|
|
14537
14966
|
} catch (err) {
|
|
14538
|
-
|
|
14539
|
-
return 1;
|
|
14967
|
+
return contextError(err.message);
|
|
14540
14968
|
}
|
|
14541
14969
|
const slug = manifest.module?.slug;
|
|
14542
14970
|
if (!slug) {
|
|
14543
|
-
|
|
14544
|
-
return 1;
|
|
14971
|
+
return contextError("module.json has no module.slug.");
|
|
14545
14972
|
}
|
|
14546
14973
|
const cfg = readConfig();
|
|
14547
14974
|
if (!cfg) {
|
|
14548
|
-
|
|
14549
|
-
|
|
14975
|
+
return report(
|
|
14976
|
+
new ApiError(401, { code: "UNAUTHENTICATED", message: "Not logged in." }, "Not logged in.")
|
|
14977
|
+
);
|
|
14550
14978
|
}
|
|
14551
14979
|
const auth = { baseUrl: resolveApiUrl(), auth: { token: cfg.token } };
|
|
14552
14980
|
let moduleId;
|
|
14553
14981
|
try {
|
|
14554
14982
|
moduleId = (await getModuleBySlug(slug, auth)).id;
|
|
14555
14983
|
} catch (err) {
|
|
14556
|
-
|
|
14557
|
-
|
|
14558
|
-
|
|
14559
|
-
|
|
14560
|
-
|
|
14561
|
-
}
|
|
14562
|
-
fail(`Could not resolve module ${code(slug)}: ${err.message}`);
|
|
14563
|
-
return 1;
|
|
14984
|
+
return reportRefusal(
|
|
14985
|
+
err,
|
|
14986
|
+
{ module: slug },
|
|
14987
|
+
{ ...options, stderr: true, action: "Resolve module" }
|
|
14988
|
+
);
|
|
14564
14989
|
}
|
|
14565
14990
|
const entry = readModuleEntry(slug);
|
|
14566
14991
|
const dwkAuth = entry?.apiKey ? { baseUrl: resolveApiUrl(), auth: { token: entry.apiKey } } : null;
|
|
14567
14992
|
let remote = null;
|
|
14568
14993
|
let previewUnavailableReason = null;
|
|
14994
|
+
let previewError = null;
|
|
14569
14995
|
if (!dwkAuth) {
|
|
14570
|
-
previewUnavailableReason = `no dev-install
|
|
14996
|
+
previewUnavailableReason = `no dev-install dwk_ key is cached for ${slug} in auth.json`;
|
|
14571
14997
|
} else {
|
|
14572
14998
|
try {
|
|
14573
14999
|
remote = (await getInstallationSchema(dwkAuth)).manifest;
|
|
14574
15000
|
} catch (err) {
|
|
15001
|
+
previewError = err;
|
|
14575
15002
|
const detail = err instanceof ApiError ? `${err.code} ${err.status}: ${err.message}` : err.message;
|
|
14576
15003
|
previewUnavailableReason = `reading the dev-install manifest failed (${detail}) \u2014 the cached key may be expired or revoked`;
|
|
14577
15004
|
}
|
|
@@ -14611,15 +15038,32 @@ async function schemaPushCommand(options) {
|
|
|
14611
15038
|
}
|
|
14612
15039
|
}
|
|
14613
15040
|
} else {
|
|
14614
|
-
|
|
14615
|
-
|
|
15041
|
+
if (!options.json) {
|
|
15042
|
+
warn(
|
|
15043
|
+
`Cannot preview what this push would change: ${previewUnavailableReason}.
|
|
14616
15044
|
Without the preview there is no way to know whether the push REMOVES tables/fields (live DDL on the dev install).`
|
|
14617
|
-
|
|
15045
|
+
);
|
|
15046
|
+
}
|
|
14618
15047
|
if (!options.yes) {
|
|
14619
|
-
|
|
14620
|
-
|
|
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
|
+
)
|
|
14621
15066
|
);
|
|
14622
|
-
return 1;
|
|
14623
15067
|
}
|
|
14624
15068
|
info(
|
|
14625
15069
|
dim(
|
|
@@ -14636,7 +15080,7 @@ async function schemaPushCommand(options) {
|
|
|
14636
15080
|
auth
|
|
14637
15081
|
);
|
|
14638
15082
|
} catch (err) {
|
|
14639
|
-
return surfaceSchemaPushError(err, slug);
|
|
15083
|
+
return surfaceSchemaPushError(err, slug, options.json);
|
|
14640
15084
|
}
|
|
14641
15085
|
printApplySummary(applied);
|
|
14642
15086
|
for (const w of referenceFieldWarnings(manifest)) warn(w.message);
|
|
@@ -14713,21 +15157,23 @@ function strProp(v, key) {
|
|
|
14713
15157
|
}
|
|
14714
15158
|
return void 0;
|
|
14715
15159
|
}
|
|
14716
|
-
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
|
+
);
|
|
14717
15168
|
if (err instanceof ApiError) {
|
|
14718
15169
|
switch (err.code) {
|
|
14719
15170
|
case "MANIFEST_INVALID":
|
|
14720
15171
|
fail(`Manifest rejected by the backend validator: ${err.message}`);
|
|
14721
15172
|
for (const issue of extractIssues2(err.context)) {
|
|
14722
|
-
|
|
15173
|
+
process.stderr.write(` ${dim("-")} ${issue}
|
|
15174
|
+
`);
|
|
14723
15175
|
}
|
|
14724
15176
|
return 1;
|
|
14725
|
-
case "DRAFT_INSTALLATION_MISSING":
|
|
14726
|
-
fail(
|
|
14727
|
-
`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.
|
|
14728
|
-
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.`
|
|
14729
|
-
);
|
|
14730
|
-
return 1;
|
|
14731
15177
|
case "UNIQUE_INDEX_BACKFILL_CONFLICT":
|
|
14732
15178
|
fail(
|
|
14733
15179
|
`Cannot create a UNIQUE index \u2014 existing live rows already violate it: ${err.message}
|
|
@@ -14748,14 +15194,15 @@ function surfaceSchemaPushError(err, slug) {
|
|
|
14748
15194
|
case "FORBIDDEN":
|
|
14749
15195
|
case "NOT_WORKSPACE_MEMBER":
|
|
14750
15196
|
fail(
|
|
14751
|
-
`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.`
|
|
14752
15198
|
);
|
|
14753
15199
|
return 1;
|
|
14754
15200
|
default:
|
|
14755
15201
|
if (err.status === 400) {
|
|
14756
15202
|
fail(`Manifest rejected: ${err.message}`);
|
|
14757
15203
|
for (const issue of extractIssues2(err.context)) {
|
|
14758
|
-
|
|
15204
|
+
process.stderr.write(` ${dim("-")} ${issue}
|
|
15205
|
+
`);
|
|
14759
15206
|
}
|
|
14760
15207
|
return 1;
|
|
14761
15208
|
}
|
|
@@ -14787,6 +15234,7 @@ var init_push = __esm({
|
|
|
14787
15234
|
init_config();
|
|
14788
15235
|
init_manifest_io();
|
|
14789
15236
|
init_manifest_merge();
|
|
15237
|
+
init_permission_hint();
|
|
14790
15238
|
init_output();
|
|
14791
15239
|
init_manifest_reference_lint();
|
|
14792
15240
|
}
|
|
@@ -15963,29 +16411,11 @@ async function runDoctorChecks(dir, deps) {
|
|
|
15963
16411
|
return checks;
|
|
15964
16412
|
}
|
|
15965
16413
|
function targetCheck(apiUrl) {
|
|
15966
|
-
const source = process.env.DASHWISE_API_URL?.trim() ? "from DASHWISE_API_URL" : "from your saved login";
|
|
15967
|
-
if (sameOrigin(apiUrl, KNOWN_API_URLS.prod)) {
|
|
15968
|
-
return {
|
|
15969
|
-
id: "target",
|
|
15970
|
-
label: "Target",
|
|
15971
|
-
status: "warn",
|
|
15972
|
-
detail: `PRODUCTION (${apiUrl}, ${source}). Commands here change real company data.`,
|
|
15973
|
-
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}`)}.`
|
|
15974
|
-
};
|
|
15975
|
-
}
|
|
15976
|
-
if (sameOrigin(apiUrl, KNOWN_API_URLS.dev)) {
|
|
15977
|
-
return {
|
|
15978
|
-
id: "target",
|
|
15979
|
-
label: "Target",
|
|
15980
|
-
status: "ok",
|
|
15981
|
-
detail: `dev (${apiUrl}, ${source})`
|
|
15982
|
-
};
|
|
15983
|
-
}
|
|
15984
16414
|
return {
|
|
15985
16415
|
id: "target",
|
|
15986
16416
|
label: "Target",
|
|
15987
16417
|
status: "ok",
|
|
15988
|
-
detail:
|
|
16418
|
+
detail: apiUrl
|
|
15989
16419
|
};
|
|
15990
16420
|
}
|
|
15991
16421
|
async function connectionOnly(apiUrl) {
|
|
@@ -16166,13 +16596,6 @@ function sameRepo(a, b) {
|
|
|
16166
16596
|
const ka = repoKey(a);
|
|
16167
16597
|
return ka !== null && ka === repoKey(b);
|
|
16168
16598
|
}
|
|
16169
|
-
function sameOrigin(a, b) {
|
|
16170
|
-
try {
|
|
16171
|
-
return new URL(a).origin === new URL(b).origin;
|
|
16172
|
-
} catch {
|
|
16173
|
-
return false;
|
|
16174
|
-
}
|
|
16175
|
-
}
|
|
16176
16599
|
function safeReadConfig2() {
|
|
16177
16600
|
try {
|
|
16178
16601
|
return readConfig();
|
|
@@ -17937,7 +18360,9 @@ function convertModule(opts) {
|
|
|
17937
18360
|
"app/layout.jsx",
|
|
17938
18361
|
"src/app/layout.tsx"
|
|
17939
18362
|
].find((f) => existsSync(join(dir, f)));
|
|
18363
|
+
let layoutCreated = false;
|
|
17940
18364
|
if (!layoutFile) {
|
|
18365
|
+
layoutCreated = true;
|
|
17941
18366
|
writeIfMissing("app/layout.tsx", appLayoutCustomTsx(sopts));
|
|
17942
18367
|
} else if (!readFileSync(join(dir, layoutFile), "utf-8").includes("AuthProvider")) {
|
|
17943
18368
|
warnings.push(
|
|
@@ -17953,10 +18378,13 @@ function convertModule(opts) {
|
|
|
17953
18378
|
`${nextConfigFile}: add \`output: 'standalone'\` \u2014 required for the module runtime bundle.`
|
|
17954
18379
|
);
|
|
17955
18380
|
}
|
|
17956
|
-
|
|
17957
|
-
|
|
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 };
|
|
17958
18386
|
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
17959
|
-
created.push(
|
|
18387
|
+
created.push(`package.json (added ${Object.keys(addDeps).join(", ")})`);
|
|
17960
18388
|
}
|
|
17961
18389
|
const outboundApis = scanOutboundHosts(dir);
|
|
17962
18390
|
const runtimeBlockAdded = normalizeManifest(join(dir, "module.json"), {
|
|
@@ -18780,13 +19208,13 @@ init_api_client();
|
|
|
18780
19208
|
function redactedConsumer(redacted) {
|
|
18781
19209
|
switch (redacted) {
|
|
18782
19210
|
case "private_copy":
|
|
18783
|
-
return "someone's private
|
|
19211
|
+
return "someone's private installation";
|
|
18784
19212
|
case "other_team_copy":
|
|
18785
|
-
return "another team's
|
|
19213
|
+
return "another team's installation";
|
|
18786
19214
|
case "development_copy":
|
|
18787
|
-
return "a development
|
|
19215
|
+
return "a development installation you cannot see";
|
|
18788
19216
|
default:
|
|
18789
|
-
return "
|
|
19217
|
+
return "an installation you cannot see";
|
|
18790
19218
|
}
|
|
18791
19219
|
}
|
|
18792
19220
|
var WIDEN_WORDS = {
|
|
@@ -18804,7 +19232,7 @@ var WIDEN_WORDS = {
|
|
|
18804
19232
|
function describeWidening(widening) {
|
|
18805
19233
|
return widening.map((w) => WIDEN_WORDS[w] ?? w).join(", ");
|
|
18806
19234
|
}
|
|
18807
|
-
async function
|
|
19235
|
+
async function getUpgradePreview2(installationId, version, options = {}) {
|
|
18808
19236
|
return apiRequest(
|
|
18809
19237
|
"GET",
|
|
18810
19238
|
`/api/installations/${installationId}/upgrade-preview?version=${encodeURIComponent(version)}`,
|
|
@@ -18871,6 +19299,9 @@ function selectInstallsByKind(installs, moduleId) {
|
|
|
18871
19299
|
function isRealRuntimeUrl(url) {
|
|
18872
19300
|
return !!url && /^https:\/\//i.test(url);
|
|
18873
19301
|
}
|
|
19302
|
+
function isRuntimeProvisioned(row) {
|
|
19303
|
+
return row.runtime_https ?? isRealRuntimeUrl(row.runtime_url);
|
|
19304
|
+
}
|
|
18874
19305
|
function isDraftVersion(v) {
|
|
18875
19306
|
return !v || v.includes("draft");
|
|
18876
19307
|
}
|
|
@@ -18912,8 +19343,73 @@ async function resolveWorkspaceWithFallback(opts) {
|
|
|
18912
19343
|
}
|
|
18913
19344
|
|
|
18914
19345
|
// src/commands/module/install.ts
|
|
18915
|
-
init_output();
|
|
18916
|
-
init_connect_status();
|
|
19346
|
+
init_output();
|
|
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
|
|
18917
19413
|
async function moduleInstallCommand(slugArg, options) {
|
|
18918
19414
|
const slug = resolveSlug(slugArg, options.dir);
|
|
18919
19415
|
if (!slug) {
|
|
@@ -18971,14 +19467,27 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
18971
19467
|
const installs = await listWorkspaceInstallations(workspace2.id, auth);
|
|
18972
19468
|
const byKind = selectInstallsByKind(installs, mod.id);
|
|
18973
19469
|
const production = byKind.all.filter((row2) => !isDevInstallKind(row2.kind));
|
|
18974
|
-
if (production.length > 1) {
|
|
18975
|
-
fail(
|
|
18976
|
-
"Several production installations exist for this module in this company. Select the installation to update in the app. Nothing was changed."
|
|
18977
|
-
);
|
|
18978
|
-
return 1;
|
|
18979
|
-
}
|
|
18980
|
-
existing = production[0];
|
|
18981
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
|
+
}
|
|
18982
19491
|
} catch (err) {
|
|
18983
19492
|
fail(
|
|
18984
19493
|
`Could not read workspace installations: ${err.message}`
|
|
@@ -19025,7 +19534,7 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19025
19534
|
);
|
|
19026
19535
|
} else {
|
|
19027
19536
|
info(
|
|
19028
|
-
`Existing production install
|
|
19537
|
+
`Existing production install ${describeInstallation(existing)}${installedVersion2 ? ` at ${code(`v${installedVersion2}`)}` : ""} \u2014 this will upgrade it to ${code(`v${targetVersion}`)}.`
|
|
19029
19538
|
);
|
|
19030
19539
|
}
|
|
19031
19540
|
}
|
|
@@ -19033,15 +19542,19 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19033
19542
|
let lossy = false;
|
|
19034
19543
|
let lossAccepted = false;
|
|
19035
19544
|
if (existing) {
|
|
19036
|
-
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);
|
|
19037
19546
|
const previewed = await previewUpgrade(existing.id, targetVersion, options, auth, rerun, devInstallToReplace?.id);
|
|
19038
19547
|
if (typeof previewed === "number") return previewed;
|
|
19039
19548
|
preview = previewed.preview;
|
|
19040
19549
|
lossy = previewed.lossy;
|
|
19041
19550
|
lossAccepted = previewed.accepted;
|
|
19042
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
|
+
}
|
|
19043
19556
|
const askForLoss = lossy && !lossAccepted;
|
|
19044
|
-
if (askForLoss || !options.yes && process.stdout.isTTY && !confirmedViaPicker) {
|
|
19557
|
+
if (askForLoss || !options.yes && process.stdout.isTTY && process.stdin.isTTY && !confirmedViaPicker) {
|
|
19045
19558
|
const lossClause = lossy ? " It moves the data listed above to the Trash or converts it." : "";
|
|
19046
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}`;
|
|
19047
19560
|
const answer = await select({
|
|
@@ -19068,26 +19581,63 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19068
19581
|
}
|
|
19069
19582
|
info("Provisioning the runtime \u2014 this can take a minute\u2026");
|
|
19070
19583
|
let result;
|
|
19071
|
-
|
|
19072
|
-
|
|
19073
|
-
|
|
19074
|
-
|
|
19075
|
-
|
|
19076
|
-
|
|
19077
|
-
|
|
19078
|
-
|
|
19079
|
-
|
|
19080
|
-
|
|
19081
|
-
|
|
19082
|
-
|
|
19083
|
-
|
|
19084
|
-
|
|
19085
|
-
|
|
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
|
+
}
|
|
19086
19635
|
}
|
|
19087
19636
|
if (existing && (isInstallGated(result) || result.installation.id !== existing.id)) {
|
|
19088
19637
|
fail(
|
|
19089
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.`
|
|
19090
19639
|
);
|
|
19640
|
+
if (devInstallReplaced) printDevInstallAftermath(mod.slug, workspace2.slug);
|
|
19091
19641
|
return 1;
|
|
19092
19642
|
}
|
|
19093
19643
|
if (isInstallGated(result)) {
|
|
@@ -19117,7 +19667,7 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19117
19667
|
}
|
|
19118
19668
|
const publicUrl = row?.public_url ?? null;
|
|
19119
19669
|
const installedVersion = row?.module_version?.version ?? targetVersion;
|
|
19120
|
-
const runtimeProvisioned =
|
|
19670
|
+
const runtimeProvisioned = row ? isRuntimeProvisioned(row) : false;
|
|
19121
19671
|
const readiness = await fetchUsesReadiness(result.installation.id, auth);
|
|
19122
19672
|
const setupPending = readiness?.setupPending ?? false;
|
|
19123
19673
|
if (options.json) {
|
|
@@ -19130,8 +19680,8 @@ async function moduleInstallCommand(slugArg, options) {
|
|
|
19130
19680
|
public_url: publicUrl,
|
|
19131
19681
|
verified,
|
|
19132
19682
|
// P5-08: the confirmed preview; `null` for a fresh install (nothing to preview).
|
|
19133
|
-
drift_hash: preview?.drift_hash ?? null,
|
|
19134
|
-
data_loss: preview ? lossy : null,
|
|
19683
|
+
drift_hash: confirmedPlan?.drift_hash ?? preview?.drift_hash ?? null,
|
|
19684
|
+
data_loss: confirmedPlan ? planLosesData(confirmedPlan) : preview ? lossy : null,
|
|
19135
19685
|
// `null` when the binding surface was unreachable; `[]` when the
|
|
19136
19686
|
// module declares no slots. The two are not the same thing.
|
|
19137
19687
|
uses: readiness === null ? null : usesInstallJson(readiness),
|
|
@@ -19230,7 +19780,7 @@ async function resolveDevInstallCollision(existing, slug, workspaceSlug, options
|
|
|
19230
19780
|
info(explanation);
|
|
19231
19781
|
return "replace";
|
|
19232
19782
|
}
|
|
19233
|
-
if (process.stdout.isTTY && !options.yes) {
|
|
19783
|
+
if (process.stdout.isTTY && process.stdin.isTTY && !options.yes) {
|
|
19234
19784
|
info(explanation);
|
|
19235
19785
|
const answer = await select({
|
|
19236
19786
|
message: `How should ${code(slug)} be installed into ${code(workspaceSlug)}?`,
|
|
@@ -19291,6 +19841,62 @@ async function replaceDevInstall(existing, slug, auth) {
|
|
|
19291
19841
|
}
|
|
19292
19842
|
return 0;
|
|
19293
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
|
+
}
|
|
19294
19900
|
function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
19295
19901
|
if (err instanceof ApiError) {
|
|
19296
19902
|
switch (err.code) {
|
|
@@ -19315,8 +19921,8 @@ function surfaceInstallError(err, slug, workspaceSlug, devInstallReplaced) {
|
|
|
19315
19921
|
break;
|
|
19316
19922
|
case "UPGRADE_BREAKS_CONSUMERS":
|
|
19317
19923
|
fail(
|
|
19318
|
-
`Upgrading ${code(slug)} would break modules that
|
|
19319
|
-
|
|
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.`
|
|
19320
19926
|
);
|
|
19321
19927
|
break;
|
|
19322
19928
|
case "FORBIDDEN":
|
|
@@ -19362,10 +19968,26 @@ function usesInstallJson(readiness) {
|
|
|
19362
19968
|
}))
|
|
19363
19969
|
};
|
|
19364
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
|
+
}
|
|
19365
19987
|
async function previewUpgrade(installationId, targetVersion, options, auth, rerun, devInstallToReplaceId) {
|
|
19366
19988
|
let preview;
|
|
19367
19989
|
try {
|
|
19368
|
-
preview = await
|
|
19990
|
+
preview = await getUpgradePreview2(installationId, targetVersion, auth);
|
|
19369
19991
|
} catch (err) {
|
|
19370
19992
|
const why = err instanceof ApiError ? `${err.code || err.status} ${err.message}` : err.message;
|
|
19371
19993
|
const checkNote = options.acceptDataLoss !== void 0 ? `, so ${code("--accept-data-loss")} cannot be checked` : "";
|
|
@@ -19389,7 +20011,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19389
20011
|
const broken = preview.breaks_consumers ?? [];
|
|
19390
20012
|
if (broken.length > 0) {
|
|
19391
20013
|
const names = broken.map(
|
|
19392
|
-
(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)
|
|
19393
20015
|
);
|
|
19394
20016
|
warn(` Breaks ${broken.length} module${broken.length === 1 ? "" : "s"} that read${broken.length === 1 ? "s" : ""} this one: ${names.join(", ")}`);
|
|
19395
20017
|
}
|
|
@@ -19399,7 +20021,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19399
20021
|
}
|
|
19400
20022
|
if (options.acceptDataLoss !== void 0 && options.acceptDataLoss !== preview.drift_hash) {
|
|
19401
20023
|
fail(
|
|
19402
|
-
`${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.`
|
|
19403
20025
|
);
|
|
19404
20026
|
info(`To accept the change listed above, a person re-runs:
|
|
19405
20027
|
${rerun(preview.drift_hash)}`);
|
|
@@ -19409,7 +20031,7 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19409
20031
|
if (lossy && !accepted) {
|
|
19410
20032
|
const yesNote = options.yes ? ` ${code("--yes")} does not accept data loss.` : "";
|
|
19411
20033
|
const marker = agentSessionMarker();
|
|
19412
|
-
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;
|
|
19413
20035
|
if (why) {
|
|
19414
20036
|
const alsoDev = devInstallToReplaceId !== void 0 ? ` and destroys dev install #${devInstallToReplaceId} first` : "";
|
|
19415
20037
|
fail(`This upgrade loses the data listed above${alsoDev}, and ${why}.${yesNote} Refused. Nothing was changed.`);
|
|
@@ -19420,12 +20042,13 @@ async function previewUpgrade(installationId, targetVersion, options, auth, reru
|
|
|
19420
20042
|
}
|
|
19421
20043
|
return { preview, lossy, accepted };
|
|
19422
20044
|
}
|
|
19423
|
-
function rerunLine(slug, workspaceSlug, targetVersion, options, replacingDevInstall) {
|
|
20045
|
+
function rerunLine(slug, workspaceSlug, targetVersion, installationId, options, replacingDevInstall) {
|
|
19424
20046
|
return (hash) => [
|
|
19425
20047
|
"dashwise module install",
|
|
19426
20048
|
slug,
|
|
19427
20049
|
`--workspace ${workspaceSlug}`,
|
|
19428
20050
|
`--version ${targetVersion}`,
|
|
20051
|
+
`--installation ${installationId}`,
|
|
19429
20052
|
...options.force ? ["--force"] : [],
|
|
19430
20053
|
...replacingDevInstall ? ["--replace-dev-install"] : [],
|
|
19431
20054
|
`--accept-data-loss ${hash}`
|
|
@@ -20023,324 +20646,99 @@ async function modulePublishCommand(options) {
|
|
|
20023
20646
|
success(`Build ${code(`#${enq.build_id}`)} queued for ${code(mod.slug)}.`);
|
|
20024
20647
|
info(`Not waiting (${code("--no-wait")}). Re-run publish or poll the build to check.`);
|
|
20025
20648
|
if (options.json) out(JSON.stringify(enq));
|
|
20026
|
-
return 0;
|
|
20027
|
-
}
|
|
20028
|
-
if (!options.json) {
|
|
20029
|
-
info(`Build ${code(`#${enq.build_id}`)} queued \u2014 building on the platform\u2026`);
|
|
20030
|
-
}
|
|
20031
|
-
return pollBuild(enq.build_id, enq.module_id, mod.slug, options);
|
|
20032
|
-
}
|
|
20033
|
-
const result = body;
|
|
20034
|
-
if (!result) {
|
|
20035
|
-
fail("Publish succeeded but the response body was empty or malformed.");
|
|
20036
|
-
return 1;
|
|
20037
|
-
}
|
|
20038
|
-
if (options.json) {
|
|
20039
|
-
out(JSON.stringify(result));
|
|
20040
|
-
return 0;
|
|
20041
|
-
}
|
|
20042
|
-
success(`Published ${code(mod.slug)} ${code(`v${result.version}`)}`);
|
|
20043
|
-
info(`Bundle: ${code(result.bundle_url)}`);
|
|
20044
|
-
info(`Hash: ${dim(result.bundle_hash)}`);
|
|
20045
|
-
info(`Size: ${formatBytes2(result.bytes_uploaded)}`);
|
|
20046
|
-
if (result.warnings.length > 0) {
|
|
20047
|
-
warn(`${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"}:`);
|
|
20048
|
-
for (const w of result.warnings) {
|
|
20049
|
-
out(` ${dim("\u2022")} ${w}`);
|
|
20050
|
-
}
|
|
20051
|
-
}
|
|
20052
|
-
if (process.stdout.isTTY) outro("Done.");
|
|
20053
|
-
return 0;
|
|
20054
|
-
}
|
|
20055
|
-
async function pollBuild(buildId, moduleId, slug, options) {
|
|
20056
|
-
const timeoutMs = (options.timeoutSec ?? 360) * 1e3;
|
|
20057
|
-
const intervalMs = options.pollIntervalMs ?? 4e3;
|
|
20058
|
-
const deadline = Date.now() + timeoutMs;
|
|
20059
|
-
let lastStatus = "";
|
|
20060
|
-
while (Date.now() < deadline) {
|
|
20061
|
-
let build;
|
|
20062
|
-
try {
|
|
20063
|
-
build = await apiRequest(
|
|
20064
|
-
"GET",
|
|
20065
|
-
`/api/modules/${moduleId}/builds/${buildId}`
|
|
20066
|
-
);
|
|
20067
|
-
} catch {
|
|
20068
|
-
await sleep(intervalMs);
|
|
20069
|
-
continue;
|
|
20070
|
-
}
|
|
20071
|
-
if (!options.json && build.status !== lastStatus) {
|
|
20072
|
-
info(`Build ${code(`#${buildId}`)}: ${build.status}`);
|
|
20073
|
-
lastStatus = build.status;
|
|
20074
|
-
}
|
|
20075
|
-
if (build.status === "succeeded") {
|
|
20076
|
-
if (options.json) {
|
|
20077
|
-
out(JSON.stringify(build));
|
|
20078
|
-
} else {
|
|
20079
|
-
success(`Published ${code(slug)} ${code(`v${build.version ?? "?"}`)}`);
|
|
20080
|
-
info(`Build: ${dim(`#${buildId}`)}`);
|
|
20081
|
-
if (process.stdout.isTTY) outro("Done.");
|
|
20082
|
-
}
|
|
20083
|
-
return 0;
|
|
20084
|
-
}
|
|
20085
|
-
if (build.status === "failed") {
|
|
20086
|
-
if (options.json) {
|
|
20087
|
-
out(JSON.stringify(build));
|
|
20088
|
-
} else {
|
|
20089
|
-
fail(
|
|
20090
|
-
`Build ${code(`#${buildId}`)} failed (${build.error_code ?? "BUILD_FAILED"}): ${build.error_message ?? "(no detail)"}`
|
|
20091
|
-
);
|
|
20092
|
-
if (build.log_url) info(`Logs: ${code(build.log_url)}`);
|
|
20093
|
-
info(`Re-run ${code("dashwise module publish")} to try again.`);
|
|
20094
|
-
}
|
|
20095
|
-
return 1;
|
|
20096
|
-
}
|
|
20097
|
-
await sleep(intervalMs);
|
|
20098
|
-
}
|
|
20099
|
-
warn(
|
|
20100
|
-
`Still building after ${options.timeoutSec ?? 360}s \u2014 the build is likely still running. Re-run ${code("dashwise module publish")} to check, or inspect build ${code(`#${buildId}`)}.`
|
|
20101
|
-
);
|
|
20102
|
-
return 0;
|
|
20103
|
-
}
|
|
20104
|
-
function sleep(ms) {
|
|
20105
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
20106
|
-
}
|
|
20107
|
-
function formatBytes2(n) {
|
|
20108
|
-
if (n < 1024) return `${n}B`;
|
|
20109
|
-
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
20110
|
-
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
20111
|
-
}
|
|
20112
|
-
|
|
20113
|
-
// src/commands/module/release.ts
|
|
20114
|
-
init_api_client();
|
|
20115
|
-
init_config();
|
|
20116
|
-
init_manifest_io();
|
|
20117
|
-
init_output();
|
|
20118
|
-
|
|
20119
|
-
// src/lib/permission-hint.ts
|
|
20120
|
-
init_api_client();
|
|
20121
|
-
init_output();
|
|
20122
|
-
var EXIT_NEEDS_GRANT = 3;
|
|
20123
|
-
var EXIT_TIMED_OUT = 4;
|
|
20124
|
-
var REBUILD_BUMP_OPERATOR_ONLY = "REBUILD_BUMP_OPERATOR_ONLY";
|
|
20125
|
-
var FEATURE_SUBJECTS = {
|
|
20126
|
-
module_runtime_streaming: "Streaming",
|
|
20127
|
-
module_rebuild: "Rebuild",
|
|
20128
|
-
module_building: "Building modules",
|
|
20129
|
-
module_runtime_sizing: "Memory size"
|
|
20130
|
-
};
|
|
20131
|
-
var OPTION_SUBJECTS = {
|
|
20132
|
-
streaming: "Streaming",
|
|
20133
|
-
memory_mb: "Memory size",
|
|
20134
|
-
reapply: "Re-applying the runtime",
|
|
20135
|
-
timeout: "The timeout",
|
|
20136
|
-
reserved_concurrency: "Reserved concurrency",
|
|
20137
|
-
log_retention: "Log retention"
|
|
20138
|
-
};
|
|
20139
|
-
var MODULE_RIGHT_CODES = /* @__PURE__ */ new Set([
|
|
20140
|
-
"MODULE_CAPABILITY_DENIED",
|
|
20141
|
-
"PUBLISH_REQUIRES_ADMIN",
|
|
20142
|
-
"NOT_COPY_OWNER"
|
|
20143
|
-
]);
|
|
20144
|
-
function str(v) {
|
|
20145
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
20146
|
-
}
|
|
20147
|
-
function num(v) {
|
|
20148
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
20149
|
-
}
|
|
20150
|
-
function featureFromPermission(permission) {
|
|
20151
|
-
const m = permission ? /^workspace\.feature\.([a-z0-9_]+)\.use$/.exec(permission) : null;
|
|
20152
|
-
return m?.[1];
|
|
20153
|
-
}
|
|
20154
|
-
function featureSubject(ctx) {
|
|
20155
|
-
const feature = str(ctx.feature) ?? featureFromPermission(str(ctx.permission));
|
|
20156
|
-
if (feature && FEATURE_SUBJECTS[feature]) return FEATURE_SUBJECTS[feature];
|
|
20157
|
-
return str(ctx.feature_label) ?? (feature ? `"${feature}"` : void 0);
|
|
20158
|
-
}
|
|
20159
|
-
function needsAdmin(what, module, ask) {
|
|
20160
|
-
const scope = module ? ` for ${module}` : "";
|
|
20161
|
-
return `${what}${scope} needs a Dashwise admin. Ask your Dashwise admin to ${ask}.`;
|
|
20162
|
-
}
|
|
20163
|
-
function refusalHint(code2, context, subject = {}) {
|
|
20164
|
-
const ctx = context ?? {};
|
|
20165
|
-
const module = subject.module ?? str(ctx.module_slug);
|
|
20166
|
-
const installationId = subject.installationId ?? num(ctx.installation_id);
|
|
20167
|
-
const installLabel = installationId !== void 0 ? ` (install #${installationId})` : "";
|
|
20168
|
-
const named = module ?? "this module";
|
|
20169
|
-
switch (code2) {
|
|
20170
|
-
case "FEATURE_DISABLED":
|
|
20171
|
-
case "PERMISSION_DENIED": {
|
|
20172
|
-
const what = featureSubject(ctx);
|
|
20173
|
-
if (!what) {
|
|
20174
|
-
return {
|
|
20175
|
-
message: `You don't have permission to do this${module ? ` for ${module}` : ""}. Ask your Dashwise admin for access.`,
|
|
20176
|
-
needsGrant: true
|
|
20177
|
-
};
|
|
20178
|
-
}
|
|
20179
|
-
return { message: needsAdmin(what, module, "enable it"), needsGrant: true };
|
|
20180
|
-
}
|
|
20181
|
-
case "RUNTIME_OPTION_OPERATOR_ONLY": {
|
|
20182
|
-
const option = str(ctx.option);
|
|
20183
|
-
const what = (option && OPTION_SUBJECTS[option]) ?? (option ? `"${option}"` : "This runtime option");
|
|
20184
|
-
return { message: needsAdmin(what, module, "change it"), needsGrant: true };
|
|
20185
|
-
}
|
|
20186
|
-
case REBUILD_BUMP_OPERATOR_ONLY: {
|
|
20187
|
-
const bump = str(ctx.bump) ?? "minor";
|
|
20188
|
-
return {
|
|
20189
|
-
message: needsAdmin(`A rebuild with a ${bump} version bump`, module, "run it"),
|
|
20190
|
-
needsGrant: true
|
|
20191
|
-
};
|
|
20192
|
-
}
|
|
20193
|
-
case "IMAGE_TAG_EXISTS":
|
|
20194
|
-
return {
|
|
20195
|
-
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.`,
|
|
20196
|
-
needsGrant: true
|
|
20197
|
-
};
|
|
20198
|
-
case "RUNTIME_IMAGE_NOT_ELIGIBLE": {
|
|
20199
|
-
const version = str(ctx.version);
|
|
20200
|
-
const target = installationId !== void 0 ? ` --rollout installs:${installationId}` : "";
|
|
20201
|
-
return {
|
|
20202
|
-
message: `${named}${version ? ` v${version}` : ""} was built before streaming support. Rebuild it and upgrade this install first: dashwise module rebuild${module ? ` ${module}` : ""}${target}`,
|
|
20203
|
-
needsGrant: false
|
|
20204
|
-
};
|
|
20205
|
-
}
|
|
20206
|
-
case "RUNTIME_SWAP_PENDING": {
|
|
20207
|
-
const version = str(ctx.version);
|
|
20208
|
-
return {
|
|
20209
|
-
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.`,
|
|
20210
|
-
needsGrant: false
|
|
20211
|
-
};
|
|
20212
|
-
}
|
|
20213
|
-
case "RUNTIME_OPTION_UNAVAILABLE": {
|
|
20214
|
-
const option = str(ctx.option);
|
|
20215
|
-
const what = (option && OPTION_SUBJECTS[option]) ?? "This runtime option";
|
|
20216
|
-
return { message: `${what} is switched off platform-wide right now.`, needsGrant: false };
|
|
20217
|
-
}
|
|
20218
|
-
case "RUNTIME_CHANGE_IN_PROGRESS": {
|
|
20219
|
-
const op = num(ctx.operation_id);
|
|
20220
|
-
return {
|
|
20221
|
-
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}` : ""}`,
|
|
20222
|
-
needsGrant: false
|
|
20223
|
-
};
|
|
20649
|
+
return 0;
|
|
20224
20650
|
}
|
|
20225
|
-
|
|
20226
|
-
|
|
20227
|
-
const status = str(ctx.status);
|
|
20228
|
-
const which = buildId !== void 0 ? ` (#${buildId}${status ? `, ${status}` : ""})` : "";
|
|
20229
|
-
return {
|
|
20230
|
-
message: `A build of ${named} is already running${which}. Wait for it to finish, then run the rebuild again.`,
|
|
20231
|
-
needsGrant: false
|
|
20232
|
-
};
|
|
20651
|
+
if (!options.json) {
|
|
20652
|
+
info(`Build ${code(`#${enq.build_id}`)} queued \u2014 building on the platform\u2026`);
|
|
20233
20653
|
}
|
|
20234
|
-
|
|
20235
|
-
return {
|
|
20236
|
-
message: `${named} has no build from source to rebuild. Publish it first: dashwise module publish`,
|
|
20237
|
-
needsGrant: false
|
|
20238
|
-
};
|
|
20239
|
-
case "INSTALLATION_NOT_FOUND":
|
|
20240
|
-
return {
|
|
20241
|
-
message: `${installationId !== void 0 ? `Installation #${installationId}` : "The installation"} was not found, or you cannot see it.`,
|
|
20242
|
-
needsGrant: false
|
|
20243
|
-
};
|
|
20244
|
-
case "PUBLISH_RATE_LIMITED":
|
|
20245
|
-
case "PUBLISH_USER_RATE_LIMITED":
|
|
20246
|
-
return {
|
|
20247
|
-
message: `Too many builds of ${named} in the last hour. Try again later.`,
|
|
20248
|
-
needsGrant: false
|
|
20249
|
-
};
|
|
20250
|
-
// Install invitations (`dashwise module invites`, Südbau retro 6).
|
|
20251
|
-
case "AUTHOR_ADMIN_REQUIRED":
|
|
20252
|
-
return {
|
|
20253
|
-
message: `Only an admin of the company that owns ${named} can manage its install invitations. Ask that company's admin.`,
|
|
20254
|
-
needsGrant: true
|
|
20255
|
-
};
|
|
20256
|
-
case "COMPANY_ADMIN_REQUIRED":
|
|
20257
|
-
return {
|
|
20258
|
-
message: "Only an admin of this company can see or decline its install invitations. Ask your company's admin.",
|
|
20259
|
-
needsGrant: true
|
|
20260
|
-
};
|
|
20261
|
-
case "MODULE_PRIVATE_SHARE_FIRST":
|
|
20262
|
-
return {
|
|
20263
|
-
message: `${named} is still private. Share it with your company in Dashwise first, then invite another company.`,
|
|
20264
|
-
needsGrant: false
|
|
20265
|
-
};
|
|
20266
|
-
default:
|
|
20267
|
-
if (MODULE_RIGHT_CODES.has(code2)) {
|
|
20268
|
-
return {
|
|
20269
|
-
message: `You can't manage ${named}${installLabel}. Ask your Dashwise admin for access.`,
|
|
20270
|
-
needsGrant: true
|
|
20271
|
-
};
|
|
20272
|
-
}
|
|
20273
|
-
return null;
|
|
20654
|
+
return pollBuild(enq.build_id, enq.module_id, mod.slug, options);
|
|
20274
20655
|
}
|
|
20275
|
-
|
|
20276
|
-
|
|
20277
|
-
|
|
20278
|
-
|
|
20279
|
-
out(
|
|
20280
|
-
JSON.stringify({
|
|
20281
|
-
error: { code: opts.code ?? "CLI_ERROR", message: message.replace(ANSI, ""), status: null, context: null },
|
|
20282
|
-
hint: null,
|
|
20283
|
-
needs_grant: false
|
|
20284
|
-
})
|
|
20285
|
-
);
|
|
20286
|
-
} else {
|
|
20287
|
-
fail(message);
|
|
20656
|
+
const result = body;
|
|
20657
|
+
if (!result) {
|
|
20658
|
+
fail("Publish succeeded but the response body was empty or malformed.");
|
|
20659
|
+
return 1;
|
|
20288
20660
|
}
|
|
20289
|
-
|
|
20290
|
-
|
|
20291
|
-
|
|
20292
|
-
if (opts.json) {
|
|
20293
|
-
out(
|
|
20294
|
-
JSON.stringify({
|
|
20295
|
-
error: { code: failure.code, message: failure.message ?? null, status: null, context: null },
|
|
20296
|
-
hint: hint?.message ?? null,
|
|
20297
|
-
needs_grant: hint?.needsGrant ?? false,
|
|
20298
|
-
...opts.extra
|
|
20299
|
-
})
|
|
20300
|
-
);
|
|
20301
|
-
} else {
|
|
20302
|
-
fail(hint?.message ?? opts.fallback);
|
|
20661
|
+
if (options.json) {
|
|
20662
|
+
out(JSON.stringify(result));
|
|
20663
|
+
return 0;
|
|
20303
20664
|
}
|
|
20304
|
-
|
|
20305
|
-
}
|
|
20306
|
-
|
|
20307
|
-
|
|
20308
|
-
|
|
20665
|
+
success(`Published ${code(mod.slug)} ${code(`v${result.version}`)}`);
|
|
20666
|
+
info(`Bundle: ${code(result.bundle_url)}`);
|
|
20667
|
+
info(`Hash: ${dim(result.bundle_hash)}`);
|
|
20668
|
+
info(`Size: ${formatBytes2(result.bytes_uploaded)}`);
|
|
20669
|
+
if (result.warnings.length > 0) {
|
|
20670
|
+
warn(`${result.warnings.length} warning${result.warnings.length === 1 ? "" : "s"}:`);
|
|
20671
|
+
for (const w of result.warnings) {
|
|
20672
|
+
out(` ${dim("\u2022")} ${w}`);
|
|
20673
|
+
}
|
|
20674
|
+
}
|
|
20675
|
+
if (process.stdout.isTTY) outro("Done.");
|
|
20676
|
+
return 0;
|
|
20309
20677
|
}
|
|
20310
|
-
function
|
|
20311
|
-
|
|
20312
|
-
|
|
20313
|
-
|
|
20314
|
-
|
|
20315
|
-
|
|
20316
|
-
|
|
20317
|
-
|
|
20318
|
-
|
|
20319
|
-
|
|
20678
|
+
async function pollBuild(buildId, moduleId, slug, options) {
|
|
20679
|
+
const timeoutMs = (options.timeoutSec ?? 360) * 1e3;
|
|
20680
|
+
const intervalMs = options.pollIntervalMs ?? 4e3;
|
|
20681
|
+
const deadline = Date.now() + timeoutMs;
|
|
20682
|
+
let lastStatus = "";
|
|
20683
|
+
while (Date.now() < deadline) {
|
|
20684
|
+
let build;
|
|
20685
|
+
try {
|
|
20686
|
+
build = await apiRequest(
|
|
20687
|
+
"GET",
|
|
20688
|
+
`/api/modules/${moduleId}/builds/${buildId}`
|
|
20320
20689
|
);
|
|
20321
|
-
}
|
|
20322
|
-
|
|
20690
|
+
} catch {
|
|
20691
|
+
await sleep(intervalMs);
|
|
20692
|
+
continue;
|
|
20323
20693
|
}
|
|
20324
|
-
|
|
20325
|
-
|
|
20326
|
-
|
|
20327
|
-
|
|
20328
|
-
|
|
20329
|
-
|
|
20330
|
-
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
|
|
20334
|
-
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
|
|
20338
|
-
|
|
20694
|
+
if (!options.json && build.status !== lastStatus) {
|
|
20695
|
+
info(`Build ${code(`#${buildId}`)}: ${build.status}`);
|
|
20696
|
+
lastStatus = build.status;
|
|
20697
|
+
}
|
|
20698
|
+
if (build.status === "succeeded") {
|
|
20699
|
+
if (options.json) {
|
|
20700
|
+
out(JSON.stringify(build));
|
|
20701
|
+
} else {
|
|
20702
|
+
success(`Published ${code(slug)} ${code(`v${build.version ?? "?"}`)}`);
|
|
20703
|
+
info(`Build: ${dim(`#${buildId}`)}`);
|
|
20704
|
+
if (process.stdout.isTTY) outro("Done.");
|
|
20705
|
+
}
|
|
20706
|
+
return 0;
|
|
20707
|
+
}
|
|
20708
|
+
if (build.status === "failed") {
|
|
20709
|
+
if (options.json) {
|
|
20710
|
+
out(JSON.stringify(build));
|
|
20711
|
+
} else {
|
|
20712
|
+
fail(
|
|
20713
|
+
`Build ${code(`#${buildId}`)} failed (${build.error_code ?? "BUILD_FAILED"}): ${build.error_message ?? "(no detail)"}`
|
|
20714
|
+
);
|
|
20715
|
+
if (build.log_url) info(`Logs: ${code(build.log_url)}`);
|
|
20716
|
+
info(`Re-run ${code("dashwise module publish")} to try again.`);
|
|
20717
|
+
}
|
|
20718
|
+
return 1;
|
|
20719
|
+
}
|
|
20720
|
+
await sleep(intervalMs);
|
|
20339
20721
|
}
|
|
20340
|
-
|
|
20722
|
+
warn(
|
|
20723
|
+
`Still building after ${options.timeoutSec ?? 360}s \u2014 the build is likely still running. Re-run ${code("dashwise module publish")} to check, or inspect build ${code(`#${buildId}`)}.`
|
|
20724
|
+
);
|
|
20725
|
+
return 0;
|
|
20726
|
+
}
|
|
20727
|
+
function sleep(ms) {
|
|
20728
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
20729
|
+
}
|
|
20730
|
+
function formatBytes2(n) {
|
|
20731
|
+
if (n < 1024) return `${n}B`;
|
|
20732
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)}KB`;
|
|
20733
|
+
return `${(n / 1024 / 1024).toFixed(1)}MB`;
|
|
20341
20734
|
}
|
|
20342
20735
|
|
|
20343
20736
|
// src/commands/module/release.ts
|
|
20737
|
+
init_api_client();
|
|
20738
|
+
init_config();
|
|
20739
|
+
init_manifest_io();
|
|
20740
|
+
init_output();
|
|
20741
|
+
init_permission_hint();
|
|
20344
20742
|
var FULL_SHA = /^[0-9a-f]{40}$/i;
|
|
20345
20743
|
var positiveId = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
20346
20744
|
function isRepositoryBuild(value, moduleId, buildId) {
|
|
@@ -20589,7 +20987,7 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20589
20987
|
);
|
|
20590
20988
|
return 0;
|
|
20591
20989
|
}
|
|
20592
|
-
const runtimeProvisioned =
|
|
20990
|
+
const runtimeProvisioned = isRuntimeProvisioned(row);
|
|
20593
20991
|
const state = runtimeProvisioned && row.public_url ? "live" : runtimeProvisioned ? "deployed" : "not_deployed";
|
|
20594
20992
|
const installedVersion = row.module_version?.version ?? null;
|
|
20595
20993
|
const updateAvailable = isUpdateAvailable(installedVersion, latestVersion);
|
|
@@ -20606,7 +21004,9 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20606
21004
|
latest_version: latestVersion,
|
|
20607
21005
|
update_available: updateAvailable,
|
|
20608
21006
|
runtime_provisioned: runtimeProvisioned,
|
|
20609
|
-
|
|
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.
|
|
20610
21010
|
public_url: row.public_url,
|
|
20611
21011
|
state,
|
|
20612
21012
|
// C-09: per-slot data-plane readiness for THIS install. `null`
|
|
@@ -20632,7 +21032,7 @@ async function moduleStatusCommand(slugArg, options) {
|
|
|
20632
21032
|
);
|
|
20633
21033
|
kv(
|
|
20634
21034
|
"Runtime:",
|
|
20635
|
-
runtimeProvisioned ?
|
|
21035
|
+
runtimeProvisioned ? "provisioned" : "not provisioned"
|
|
20636
21036
|
);
|
|
20637
21037
|
kv("Public URL:", row.public_url ?? dim("(none)"));
|
|
20638
21038
|
kv(
|
|
@@ -20676,7 +21076,7 @@ function usesJson(readiness) {
|
|
|
20676
21076
|
init_api_client();
|
|
20677
21077
|
init_config();
|
|
20678
21078
|
init_output();
|
|
20679
|
-
var
|
|
21079
|
+
var AUDIENCE2 = {
|
|
20680
21080
|
company: "company",
|
|
20681
21081
|
teams: "Team Spaces",
|
|
20682
21082
|
people: "named people"
|
|
@@ -20684,16 +21084,16 @@ var AUDIENCE = {
|
|
|
20684
21084
|
var givesRole = (roleKey) => typeof roleKey === "string" && roleKey !== "none";
|
|
20685
21085
|
function governanceAudience(row) {
|
|
20686
21086
|
if (!row.rules) return null;
|
|
20687
|
-
if (givesRole(row.rules.company)) return
|
|
20688
|
-
if (row.rules.teams.some((t) => t.live === true && givesRole(t.role_key))) return
|
|
20689
|
-
return
|
|
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;
|
|
20690
21090
|
}
|
|
20691
21091
|
function toListEntry(row) {
|
|
20692
21092
|
return {
|
|
20693
21093
|
id: row.id,
|
|
20694
21094
|
module_slug: row.module?.slug ?? null,
|
|
20695
21095
|
name: row.name ?? row.module?.name ?? null,
|
|
20696
|
-
audience: row.reach?.kind ?
|
|
21096
|
+
audience: row.reach?.kind ? AUDIENCE2[row.reach.kind] ?? row.reach.kind : null,
|
|
20697
21097
|
version: row.module_version?.version ?? null,
|
|
20698
21098
|
held: row.held === true,
|
|
20699
21099
|
development: isDevInstallKind(row.kind),
|
|
@@ -20973,6 +21373,7 @@ function surfaceUninstallError(err, slug, workspaceSlug) {
|
|
|
20973
21373
|
init_api_client();
|
|
20974
21374
|
init_config();
|
|
20975
21375
|
init_output();
|
|
21376
|
+
init_permission_hint();
|
|
20976
21377
|
var positiveId2 = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0;
|
|
20977
21378
|
function session(json) {
|
|
20978
21379
|
const cfg = readConfig();
|
|
@@ -21234,27 +21635,35 @@ async function moduleInvitesDeclineCommand(inviteId, options) {
|
|
|
21234
21635
|
|
|
21235
21636
|
// src/commands/module/pull-data.ts
|
|
21236
21637
|
init_api_client();
|
|
21638
|
+
init_permission_hint();
|
|
21237
21639
|
init_output();
|
|
21238
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
|
+
);
|
|
21239
21653
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
21240
21654
|
const manifestPath2 = join(projectRoot, "module.json");
|
|
21241
21655
|
if (!existsSync(manifestPath2)) {
|
|
21242
|
-
|
|
21243
|
-
`No ${code("module.json")} found in ${code(projectRoot)}. Run \`dashwise module init <slug>\` first.`
|
|
21244
|
-
);
|
|
21245
|
-
return 1;
|
|
21656
|
+
return contextError(`No module.json found in ${projectRoot}.`);
|
|
21246
21657
|
}
|
|
21247
21658
|
let manifest;
|
|
21248
21659
|
try {
|
|
21249
21660
|
manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
21250
21661
|
} catch (err) {
|
|
21251
|
-
|
|
21252
|
-
return 1;
|
|
21662
|
+
return contextError(`Could not parse module.json: ${err.message}`);
|
|
21253
21663
|
}
|
|
21254
21664
|
const slug = manifest.module?.slug;
|
|
21255
21665
|
if (!slug) {
|
|
21256
|
-
|
|
21257
|
-
return 1;
|
|
21666
|
+
return contextError("module.json is missing module.slug.");
|
|
21258
21667
|
}
|
|
21259
21668
|
let mod;
|
|
21260
21669
|
try {
|
|
@@ -21263,16 +21672,7 @@ async function modulePullDataCommand(options) {
|
|
|
21263
21672
|
`/api/modules/by-slug/${encodeURIComponent(slug)}`
|
|
21264
21673
|
);
|
|
21265
21674
|
} catch (err) {
|
|
21266
|
-
|
|
21267
|
-
fail(`Module ${code(slug)} not found on the backend.`);
|
|
21268
|
-
return 1;
|
|
21269
|
-
}
|
|
21270
|
-
if (err instanceof ApiError && err.status === 401) {
|
|
21271
|
-
fail("Not logged in. Run `dashwise login` first.");
|
|
21272
|
-
return 1;
|
|
21273
|
-
}
|
|
21274
|
-
fail(`Could not resolve module: ${err.message}`);
|
|
21275
|
-
return 1;
|
|
21675
|
+
return report(err);
|
|
21276
21676
|
}
|
|
21277
21677
|
const limit = options.limit ?? 100;
|
|
21278
21678
|
let res;
|
|
@@ -21282,18 +21682,15 @@ async function modulePullDataCommand(options) {
|
|
|
21282
21682
|
`/api/modules/${mod.id}/own-data?limit=${limit}`
|
|
21283
21683
|
);
|
|
21284
21684
|
} catch (err) {
|
|
21285
|
-
|
|
21286
|
-
fail(`Invalid request: ${err.message}`);
|
|
21287
|
-
return 1;
|
|
21288
|
-
}
|
|
21289
|
-
fail(`Could not pull data: ${err.message}`);
|
|
21290
|
-
return 1;
|
|
21685
|
+
return report(err);
|
|
21291
21686
|
}
|
|
21292
21687
|
const outPath = options.out ? resolve(options.out) : join(projectRoot, "dashwise-data.json");
|
|
21293
21688
|
writeFileSync(outPath, JSON.stringify(res, null, 2) + "\n", "utf-8");
|
|
21294
21689
|
let totalRows = 0;
|
|
21295
21690
|
for (const t of res.tables) totalRows += t.rows.length;
|
|
21296
|
-
success(
|
|
21691
|
+
success(
|
|
21692
|
+
`Wrote ${totalRows} row${totalRows === 1 ? "" : "s"} across ${res.tables.length} table${res.tables.length === 1 ? "" : "s"} to ${code(outPath)}`
|
|
21693
|
+
);
|
|
21297
21694
|
for (const t of res.tables) {
|
|
21298
21695
|
if (t.total_rows > t.rows.length) {
|
|
21299
21696
|
warn(
|
|
@@ -21302,7 +21699,7 @@ async function modulePullDataCommand(options) {
|
|
|
21302
21699
|
}
|
|
21303
21700
|
}
|
|
21304
21701
|
if (res.tables.length === 0) {
|
|
21305
|
-
info(dim("No
|
|
21702
|
+
info(dim("No tables available."));
|
|
21306
21703
|
}
|
|
21307
21704
|
return 0;
|
|
21308
21705
|
}
|
|
@@ -23913,16 +24310,43 @@ init_config();
|
|
|
23913
24310
|
init_dotenv_local();
|
|
23914
24311
|
init_manifest_io();
|
|
23915
24312
|
init_output();
|
|
24313
|
+
init_api_client();
|
|
24314
|
+
init_permission_hint();
|
|
23916
24315
|
init_install_target();
|
|
23917
|
-
init_dev();
|
|
23918
24316
|
async function queryCommand(options) {
|
|
23919
24317
|
const projectRoot = resolve(options.dir ?? process.cwd());
|
|
23920
24318
|
let manifestSlug;
|
|
23921
24319
|
try {
|
|
23922
24320
|
manifestSlug = readManifestOrThrow(projectRoot).module?.slug;
|
|
23923
24321
|
} catch (err) {
|
|
23924
|
-
|
|
23925
|
-
|
|
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
|
+
);
|
|
23926
24350
|
}
|
|
23927
24351
|
const generatedPath = join(
|
|
23928
24352
|
projectRoot,
|
|
@@ -23936,8 +24360,10 @@ async function queryCommand(options) {
|
|
|
23936
24360
|
);
|
|
23937
24361
|
return 1;
|
|
23938
24362
|
}
|
|
23939
|
-
const
|
|
24363
|
+
const keyFromProcessEnv = Boolean(process.env.DASHWISE_API_KEY);
|
|
23940
24364
|
hydrateEnvFromDotenvLocal(projectRoot);
|
|
24365
|
+
let moduleKey = keyFromProcessEnv ? "env" : process.env.DASHWISE_API_KEY ? "env_file" : "saved";
|
|
24366
|
+
let keySource = keyFromProcessEnv ? "environment" : ".env.local";
|
|
23941
24367
|
const cfg = (() => {
|
|
23942
24368
|
try {
|
|
23943
24369
|
return readConfig();
|
|
@@ -23946,46 +24372,47 @@ async function queryCommand(options) {
|
|
|
23946
24372
|
}
|
|
23947
24373
|
})();
|
|
23948
24374
|
const apiUrl = process.env.DASHWISE_API_URL ?? cfg?.apiUrl ?? "http://localhost:3000";
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
if (!process.env.DASHWISE_API_KEY && manifestSlug) {
|
|
24375
|
+
const entry = readModuleEntry(manifestSlug);
|
|
24376
|
+
if (!process.env.DASHWISE_API_KEY) {
|
|
23952
24377
|
if (entry && options.install !== void 0 && entry.installationId !== options.install) {
|
|
23953
24378
|
fail(
|
|
23954
|
-
`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.`
|
|
23955
24380
|
);
|
|
23956
24381
|
return 1;
|
|
23957
24382
|
}
|
|
23958
|
-
if (cfg) {
|
|
23959
|
-
const healed = await resolveModuleApiKey(
|
|
23960
|
-
manifestSlug,
|
|
23961
|
-
entry,
|
|
23962
|
-
apiUrl,
|
|
23963
|
-
cfg.token
|
|
23964
|
-
);
|
|
23965
|
-
if (healed) entry = healed;
|
|
23966
|
-
}
|
|
23967
24383
|
if (entry?.apiKey) {
|
|
23968
24384
|
process.env.DASHWISE_API_KEY = entry.apiKey;
|
|
24385
|
+
moduleKey = "saved";
|
|
23969
24386
|
keySource = "cached";
|
|
23970
24387
|
if (!process.env.DASHWISE_API_URL) {
|
|
23971
24388
|
process.env.DASHWISE_API_URL = apiUrl;
|
|
23972
24389
|
}
|
|
23973
24390
|
}
|
|
23974
24391
|
}
|
|
23975
|
-
if (process.env.DASHWISE_API_KEY) {
|
|
23976
|
-
|
|
23977
|
-
|
|
23978
|
-
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
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
|
+
);
|
|
23988
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;
|
|
23989
24416
|
const modes = [
|
|
23990
24417
|
options.inline !== void 0 ? "inline" : null,
|
|
23991
24418
|
options.file !== void 0 ? "file" : null,
|
|
@@ -24015,7 +24442,7 @@ async function queryCommand(options) {
|
|
|
24015
24442
|
const stream = options.stream === true;
|
|
24016
24443
|
if (options.repl) return runRepl(generatedPath);
|
|
24017
24444
|
if (options.file !== void 0)
|
|
24018
|
-
return runFile(options.file, projectRoot, options.json === true, explain, stream);
|
|
24445
|
+
return runFile(options.file, projectRoot, options.json === true, explain, stream, moduleKey);
|
|
24019
24446
|
if (options.inline !== void 0)
|
|
24020
24447
|
return runInline(
|
|
24021
24448
|
options.inline,
|
|
@@ -24023,11 +24450,12 @@ async function queryCommand(options) {
|
|
|
24023
24450
|
generatedPath,
|
|
24024
24451
|
options.json === true,
|
|
24025
24452
|
explain,
|
|
24026
|
-
stream
|
|
24453
|
+
stream,
|
|
24454
|
+
moduleKey
|
|
24027
24455
|
);
|
|
24028
24456
|
return 1;
|
|
24029
24457
|
}
|
|
24030
|
-
async function runInline(expression, projectRoot, generatedPath, jsonCompact, explain, stream) {
|
|
24458
|
+
async function runInline(expression, projectRoot, generatedPath, jsonCompact, explain, stream, moduleKey) {
|
|
24031
24459
|
let entryUrl;
|
|
24032
24460
|
try {
|
|
24033
24461
|
entryUrl = generatedEntryUrl(generatedPath);
|
|
@@ -24048,7 +24476,7 @@ async function runInline(expression, projectRoot, generatedPath, jsonCompact, ex
|
|
|
24048
24476
|
].join("\n");
|
|
24049
24477
|
writeFileSync(tmpFile, source, "utf-8");
|
|
24050
24478
|
try {
|
|
24051
|
-
return await importAndPrint(tmpFile, projectRoot, jsonCompact, explain, stream);
|
|
24479
|
+
return await importAndPrint(tmpFile, projectRoot, jsonCompact, explain, stream, moduleKey);
|
|
24052
24480
|
} finally {
|
|
24053
24481
|
try {
|
|
24054
24482
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -24056,7 +24484,7 @@ async function runInline(expression, projectRoot, generatedPath, jsonCompact, ex
|
|
|
24056
24484
|
}
|
|
24057
24485
|
}
|
|
24058
24486
|
}
|
|
24059
|
-
async function runFile(file, projectRoot, jsonCompact, explain, stream) {
|
|
24487
|
+
async function runFile(file, projectRoot, jsonCompact, explain, stream, moduleKey) {
|
|
24060
24488
|
const abs = resolve(projectRoot, file);
|
|
24061
24489
|
if (!existsSync(abs)) {
|
|
24062
24490
|
fail(`File not found: ${code(abs)}`);
|
|
@@ -24068,7 +24496,7 @@ async function runFile(file, projectRoot, jsonCompact, explain, stream) {
|
|
|
24068
24496
|
);
|
|
24069
24497
|
return 1;
|
|
24070
24498
|
}
|
|
24071
|
-
return importAndPrint(abs, projectRoot, jsonCompact, explain, stream);
|
|
24499
|
+
return importAndPrint(abs, projectRoot, jsonCompact, explain, stream, moduleKey);
|
|
24072
24500
|
}
|
|
24073
24501
|
async function runRepl(generatedPath) {
|
|
24074
24502
|
let generated;
|
|
@@ -24107,7 +24535,7 @@ async function runRepl(generatedPath) {
|
|
|
24107
24535
|
});
|
|
24108
24536
|
});
|
|
24109
24537
|
}
|
|
24110
|
-
async function importAndPrint(file, projectRoot, jsonCompact, explain, stream) {
|
|
24538
|
+
async function importAndPrint(file, projectRoot, jsonCompact, explain, stream, moduleKey) {
|
|
24111
24539
|
const originalCwd = process.cwd();
|
|
24112
24540
|
try {
|
|
24113
24541
|
process.chdir(projectRoot);
|
|
@@ -24143,11 +24571,11 @@ async function importAndPrint(file, projectRoot, jsonCompact, explain, stream) {
|
|
|
24143
24571
|
}
|
|
24144
24572
|
return 0;
|
|
24145
24573
|
} catch (err) {
|
|
24146
|
-
|
|
24574
|
+
const exit = reportRefusal(err, {}, { json: jsonCompact, stderr: true, action: "Query", moduleKey });
|
|
24147
24575
|
if (process.env.DASHWISE_DEBUG) {
|
|
24148
24576
|
console.error(err);
|
|
24149
24577
|
}
|
|
24150
|
-
return
|
|
24578
|
+
return exit;
|
|
24151
24579
|
} finally {
|
|
24152
24580
|
process.chdir(originalCwd);
|
|
24153
24581
|
}
|
|
@@ -24848,6 +25276,7 @@ init_api_client();
|
|
|
24848
25276
|
init_config();
|
|
24849
25277
|
init_manifest_io();
|
|
24850
25278
|
init_output();
|
|
25279
|
+
init_permission_hint();
|
|
24851
25280
|
var PLATFORM_DEFAULTS = ["manifest", "on", "off"];
|
|
24852
25281
|
var BUMPS = ["patch", "minor", "major"];
|
|
24853
25282
|
function parseRollout(raw) {
|
|
@@ -25071,6 +25500,7 @@ init_api_client();
|
|
|
25071
25500
|
init_config();
|
|
25072
25501
|
init_manifest_io();
|
|
25073
25502
|
init_output();
|
|
25503
|
+
init_permission_hint();
|
|
25074
25504
|
var TERMINAL_OPERATION = /* @__PURE__ */ new Set(["succeeded", "failed", "superseded"]);
|
|
25075
25505
|
async function moduleRuntimeShowCommand(slugArg, options) {
|
|
25076
25506
|
const auth = authOrFail(options.json);
|
|
@@ -25367,10 +25797,11 @@ function sleep5(ms) {
|
|
|
25367
25797
|
|
|
25368
25798
|
// src/program.ts
|
|
25369
25799
|
init_output();
|
|
25800
|
+
init_permission_hint();
|
|
25370
25801
|
var program = new Command();
|
|
25371
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(
|
|
25372
25803
|
"--api-url <url>",
|
|
25373
|
-
`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}).`
|
|
25374
25805
|
);
|
|
25375
25806
|
program.hook("preAction", () => {
|
|
25376
25807
|
const apiUrl = program.opts().apiUrl?.trim();
|
|
@@ -25389,7 +25820,7 @@ program.command("login").description("Sign in via the device-code flow.").option
|
|
|
25389
25820
|
process.exitCode = exit;
|
|
25390
25821
|
});
|
|
25391
25822
|
program.command("doctor").description(
|
|
25392
|
-
"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."
|
|
25393
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) => {
|
|
25394
25825
|
process.exitCode = await doctorCommand(
|
|
25395
25826
|
{
|
|
@@ -25692,8 +26123,9 @@ moduleCmd.command("diff").description(
|
|
|
25692
26123
|
});
|
|
25693
26124
|
}
|
|
25694
26125
|
);
|
|
25695
|
-
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) => {
|
|
25696
26127
|
process.exitCode = await modulePullDataCommand({
|
|
26128
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
25697
26129
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
25698
26130
|
...cmdOpts.limit !== void 0 ? { limit: cmdOpts.limit } : {},
|
|
25699
26131
|
...cmdOpts.out !== void 0 ? { out: cmdOpts.out } : {}
|
|
@@ -26059,8 +26491,15 @@ moduleCmd.command("publish").description("Pack the current directory + upload it
|
|
|
26059
26491
|
}
|
|
26060
26492
|
);
|
|
26061
26493
|
moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description(
|
|
26062
|
-
"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
|
|
26063
|
-
).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(
|
|
26064
26503
|
"--keep-dev-install",
|
|
26065
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."
|
|
26066
26505
|
).option(
|
|
@@ -26072,6 +26511,7 @@ moduleCmd.command("install [slug]").alias("deploy").alias("upgrade").description
|
|
|
26072
26511
|
).option("--json", "Emit the final result as machine-readable JSON.").action(
|
|
26073
26512
|
async (slug, cmdOpts) => {
|
|
26074
26513
|
process.exitCode = await moduleInstallCommand(slug, {
|
|
26514
|
+
...cmdOpts.installation !== void 0 ? { installation: cmdOpts.installation } : {},
|
|
26075
26515
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
26076
26516
|
...cmdOpts.version !== void 0 ? { version: cmdOpts.version } : {},
|
|
26077
26517
|
...cmdOpts.force !== void 0 ? { force: cmdOpts.force } : {},
|
|
@@ -26350,9 +26790,10 @@ var schemaCmd = program.command("schema").description(
|
|
|
26350
26790
|
);
|
|
26351
26791
|
schemaCmd.command("push").description(
|
|
26352
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)."
|
|
26353
|
-
).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) => {
|
|
26354
26794
|
const { schemaPushCommand: schemaPushCommand2 } = await Promise.resolve().then(() => (init_push(), push_exports));
|
|
26355
26795
|
process.exitCode = await schemaPushCommand2({
|
|
26796
|
+
...cmdOpts.json !== void 0 ? { json: cmdOpts.json } : {},
|
|
26356
26797
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
26357
26798
|
...cmdOpts.yes !== void 0 ? { yes: cmdOpts.yes } : {}
|
|
26358
26799
|
});
|
|
@@ -26433,7 +26874,13 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
26433
26874
|
}
|
|
26434
26875
|
);
|
|
26435
26876
|
function getVersion() {
|
|
26436
|
-
return "0.
|
|
26877
|
+
return "0.28.1";
|
|
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());
|
|
26437
26884
|
}
|
|
26438
26885
|
function parseIntOption(value) {
|
|
26439
26886
|
const n = Number.parseInt(value, 10);
|