@dashai/cli 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -6
- package/dist/bin.js +382 -18
- package/dist/bin.js.map +1 -1
- package/package.json +3 -4
package/dist/bin.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync, watch, readdirSync, chmodSync, renameSync, mkdtempSync, statSync } from 'fs';
|
|
3
3
|
import { tmpdir, platform, homedir } from 'os';
|
|
4
|
-
import { resolve, join, dirname, relative } from 'path';
|
|
4
|
+
import { resolve, join, dirname, basename, relative } from 'path';
|
|
5
5
|
import pc from 'picocolors';
|
|
6
6
|
import { intro, log, select, isCancel, cancel, spinner, outro, text, multiselect, confirm } from '@clack/prompts';
|
|
7
|
-
import { Command } from 'commander';
|
|
7
|
+
import { Command, Option } from 'commander';
|
|
8
8
|
import { spawn, spawnSync } from 'child_process';
|
|
9
9
|
import open from 'open';
|
|
10
10
|
import * as tar from 'tar';
|
|
@@ -394,6 +394,9 @@ var init_output = __esm({
|
|
|
394
394
|
});
|
|
395
395
|
|
|
396
396
|
// src/lib/scaffold.ts
|
|
397
|
+
function isDeployTarget(value) {
|
|
398
|
+
return DEPLOY_TARGETS.includes(value);
|
|
399
|
+
}
|
|
397
400
|
function manifestJson(opts) {
|
|
398
401
|
const kind = opts.kind ?? "hand_authored";
|
|
399
402
|
const manifest = {
|
|
@@ -1017,6 +1020,7 @@ function appApiAuthSignInTs() {
|
|
|
1017
1020
|
// the DashWise FE \`/login?return_to=...\` automatically \u2014 you don't
|
|
1018
1021
|
// need to handle the "user not logged in" case here.
|
|
1019
1022
|
import { NextResponse } from 'next/server';
|
|
1023
|
+
import { getPublicOrigin } from '@/lib/request-origin';
|
|
1020
1024
|
|
|
1021
1025
|
export function GET(request: Request) {
|
|
1022
1026
|
const url = new URL(request.url);
|
|
@@ -1042,8 +1046,10 @@ export function GET(request: Request) {
|
|
|
1042
1046
|
|
|
1043
1047
|
// Absolute URL for return_to so the backend knows which host to
|
|
1044
1048
|
// bounce back to (the callback path is fixed at /api/auth/callback
|
|
1045
|
-
// by SDK convention).
|
|
1046
|
-
|
|
1049
|
+
// by SDK convention). \`getPublicOrigin\` reads \`X-Forwarded-Host\`
|
|
1050
|
+
// when present so the redirect URL points at the public domain,
|
|
1051
|
+
// not the internal Node origin behind reverse proxies.
|
|
1052
|
+
const moduleOrigin = getPublicOrigin(request);
|
|
1047
1053
|
const absoluteReturnTo = \`\${moduleOrigin}\${returnTo}\`;
|
|
1048
1054
|
|
|
1049
1055
|
let ssoStart;
|
|
@@ -1077,12 +1083,17 @@ function appApiAuthCallbackTs() {
|
|
|
1077
1083
|
// (e.g. \`access_denied\`, \`server_error\`, \`invalid_request\`).
|
|
1078
1084
|
import { NextResponse } from 'next/server';
|
|
1079
1085
|
import { exchangeCode } from '@dashai/sdk/auth/server';
|
|
1086
|
+
import { getPublicOrigin } from '@/lib/request-origin';
|
|
1080
1087
|
|
|
1081
1088
|
export async function GET(request: Request) {
|
|
1082
1089
|
const url = new URL(request.url);
|
|
1090
|
+
// Resolve the public origin once \u2014 all redirects below are
|
|
1091
|
+
// back to this module, and behind a reverse proxy url.origin is
|
|
1092
|
+
// the internal Node origin (localhost:<PORT>), not the public host.
|
|
1093
|
+
const publicOrigin = getPublicOrigin(request);
|
|
1083
1094
|
const error = url.searchParams.get('error');
|
|
1084
1095
|
if (error) {
|
|
1085
|
-
const target = new URL('/sign-in-error',
|
|
1096
|
+
const target = new URL('/sign-in-error', publicOrigin);
|
|
1086
1097
|
target.searchParams.set('code', error);
|
|
1087
1098
|
const description = url.searchParams.get('error_description');
|
|
1088
1099
|
if (description) target.searchParams.set('description', description);
|
|
@@ -1091,19 +1102,19 @@ export async function GET(request: Request) {
|
|
|
1091
1102
|
const code = url.searchParams.get('code');
|
|
1092
1103
|
const returnTo = url.searchParams.get('return_to') ?? '/';
|
|
1093
1104
|
if (!code) {
|
|
1094
|
-
const target = new URL('/sign-in-error',
|
|
1105
|
+
const target = new URL('/sign-in-error', publicOrigin);
|
|
1095
1106
|
target.searchParams.set('code', 'missing_code');
|
|
1096
1107
|
return NextResponse.redirect(target);
|
|
1097
1108
|
}
|
|
1098
1109
|
try {
|
|
1099
1110
|
await exchangeCode(code); // sets the dashwise.session cookie
|
|
1100
1111
|
} catch (err) {
|
|
1101
|
-
const target = new URL('/sign-in-error',
|
|
1112
|
+
const target = new URL('/sign-in-error', publicOrigin);
|
|
1102
1113
|
target.searchParams.set('code', 'exchange_failed');
|
|
1103
1114
|
target.searchParams.set('description', (err as Error).message);
|
|
1104
1115
|
return NextResponse.redirect(target);
|
|
1105
1116
|
}
|
|
1106
|
-
return NextResponse.redirect(new URL(returnTo,
|
|
1117
|
+
return NextResponse.redirect(new URL(returnTo, publicOrigin));
|
|
1107
1118
|
}
|
|
1108
1119
|
`;
|
|
1109
1120
|
}
|
|
@@ -1116,10 +1127,14 @@ function appApiAuthSignOutTs() {
|
|
|
1116
1127
|
// to follow with GET (not re-POST), landing on the public '/' page.
|
|
1117
1128
|
import { NextResponse } from 'next/server';
|
|
1118
1129
|
import { signOut } from '@dashai/sdk/auth/server';
|
|
1130
|
+
import { getPublicOrigin } from '@/lib/request-origin';
|
|
1119
1131
|
|
|
1120
1132
|
export async function POST(request: Request) {
|
|
1121
1133
|
await signOut();
|
|
1122
|
-
|
|
1134
|
+
// Build the redirect against the public origin so we don't land
|
|
1135
|
+
// the user on \`http://localhost:<PORT>/\` when running behind a
|
|
1136
|
+
// reverse proxy (Railway/Vercel/Fly).
|
|
1137
|
+
return NextResponse.redirect(new URL('/', getPublicOrigin(request)), 303);
|
|
1123
1138
|
}
|
|
1124
1139
|
`;
|
|
1125
1140
|
}
|
|
@@ -1293,6 +1308,36 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
1293
1308
|
}
|
|
1294
1309
|
`;
|
|
1295
1310
|
}
|
|
1311
|
+
function libRequestOriginTs() {
|
|
1312
|
+
return `/**
|
|
1313
|
+
* Resolve the **public** origin of an incoming request.
|
|
1314
|
+
*
|
|
1315
|
+
* Behind a reverse proxy (Railway, Vercel, Fly, Cloudflare, an
|
|
1316
|
+
* nginx in front of a Node container, etc.) \`new URL(request.url)\`
|
|
1317
|
+
* reports the *internal* origin the Node process is bound to \u2014
|
|
1318
|
+
* usually \`http://localhost:<PORT>\` \u2014 not the public URL the
|
|
1319
|
+
* browser hit. The proxy preserves the public origin in
|
|
1320
|
+
* \`X-Forwarded-*\` headers; we read those first.
|
|
1321
|
+
*
|
|
1322
|
+
* Falls back to \`new URL(request.url).origin\` for local dev where
|
|
1323
|
+
* there's no proxy in front of Next.js.
|
|
1324
|
+
*
|
|
1325
|
+
* Both \`x-forwarded-proto\` and \`x-forwarded-host\` can be
|
|
1326
|
+
* comma-separated when multiple proxies chain; the leftmost entry
|
|
1327
|
+
* is the original client-facing one per RFC 7239 conventions.
|
|
1328
|
+
*/
|
|
1329
|
+
export function getPublicOrigin(request: Request): string {
|
|
1330
|
+
const forwardedProto = request.headers.get('x-forwarded-proto');
|
|
1331
|
+
const forwardedHost = request.headers.get('x-forwarded-host');
|
|
1332
|
+
if (forwardedProto && forwardedHost) {
|
|
1333
|
+
const proto = forwardedProto.split(',')[0].trim();
|
|
1334
|
+
const host = forwardedHost.split(',')[0].trim();
|
|
1335
|
+
return \`\${proto}://\${host}\`;
|
|
1336
|
+
}
|
|
1337
|
+
return new URL(request.url).origin;
|
|
1338
|
+
}
|
|
1339
|
+
`;
|
|
1340
|
+
}
|
|
1296
1341
|
function componentsUiButtonTsx() {
|
|
1297
1342
|
return `import * as React from "react";
|
|
1298
1343
|
import { cva, type VariantProps } from "class-variance-authority";
|
|
@@ -1750,9 +1795,63 @@ Format (reverse chronological, newest first):
|
|
|
1750
1795
|
schema mutation.
|
|
1751
1796
|
`;
|
|
1752
1797
|
}
|
|
1753
|
-
|
|
1798
|
+
function railwayJson(_opts) {
|
|
1799
|
+
return JSON.stringify(
|
|
1800
|
+
{
|
|
1801
|
+
$schema: "https://railway.com/railway.schema.json",
|
|
1802
|
+
build: {
|
|
1803
|
+
builder: "NIXPACKS"
|
|
1804
|
+
},
|
|
1805
|
+
deploy: {
|
|
1806
|
+
restartPolicyType: "ON_FAILURE",
|
|
1807
|
+
restartPolicyMaxRetries: 3
|
|
1808
|
+
}
|
|
1809
|
+
},
|
|
1810
|
+
null,
|
|
1811
|
+
2
|
|
1812
|
+
) + "\n";
|
|
1813
|
+
}
|
|
1814
|
+
function vercelJson(_opts) {
|
|
1815
|
+
return JSON.stringify(
|
|
1816
|
+
{
|
|
1817
|
+
$schema: "https://openapi.vercel.sh/vercel.json",
|
|
1818
|
+
framework: "nextjs"
|
|
1819
|
+
},
|
|
1820
|
+
null,
|
|
1821
|
+
2
|
|
1822
|
+
) + "\n";
|
|
1823
|
+
}
|
|
1824
|
+
function flyToml(opts) {
|
|
1825
|
+
return `# fly.toml \u2014 generated by \`dashwise init --deploy-target fly\`.
|
|
1826
|
+
# Run \`fly launch --no-deploy --copy-config\` to register this app with
|
|
1827
|
+
# Fly; the launcher will read this file and prompt for anything missing.
|
|
1828
|
+
#
|
|
1829
|
+
# Edit \`app\` if the slug is already taken globally on fly.io.
|
|
1830
|
+
# Edit \`primary_region\` for non-US-East deploys (https://fly.io/docs/reference/regions/).
|
|
1831
|
+
|
|
1832
|
+
app = "${opts.slug}"
|
|
1833
|
+
primary_region = "iad"
|
|
1834
|
+
|
|
1835
|
+
[build]
|
|
1836
|
+
|
|
1837
|
+
[http_service]
|
|
1838
|
+
internal_port = 3000
|
|
1839
|
+
force_https = true
|
|
1840
|
+
auto_stop_machines = "stop"
|
|
1841
|
+
auto_start_machines = true
|
|
1842
|
+
min_machines_running = 0
|
|
1843
|
+
processes = ["app"]
|
|
1844
|
+
|
|
1845
|
+
[[vm]]
|
|
1846
|
+
cpu_kind = "shared"
|
|
1847
|
+
cpus = 1
|
|
1848
|
+
memory_mb = 256
|
|
1849
|
+
`;
|
|
1850
|
+
}
|
|
1851
|
+
var DEPLOY_TARGETS, SCAFFOLD_DEFAULT_API_URL;
|
|
1754
1852
|
var init_scaffold = __esm({
|
|
1755
1853
|
"src/lib/scaffold.ts"() {
|
|
1854
|
+
DEPLOY_TARGETS = ["railway", "vercel", "fly"];
|
|
1756
1855
|
SCAFFOLD_DEFAULT_API_URL = "http://localhost:3000";
|
|
1757
1856
|
}
|
|
1758
1857
|
});
|
|
@@ -1854,6 +1953,12 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
1854
1953
|
);
|
|
1855
1954
|
return 1;
|
|
1856
1955
|
}
|
|
1956
|
+
if (options.deployTarget !== void 0 && !isDeployTarget(options.deployTarget)) {
|
|
1957
|
+
fail(
|
|
1958
|
+
`Invalid --deploy-target ${code(options.deployTarget)} \u2014 must be one of ${DEPLOY_TARGETS.map((t) => code(t)).join(", ")}.`
|
|
1959
|
+
);
|
|
1960
|
+
return 1;
|
|
1961
|
+
}
|
|
1857
1962
|
const kind = options.simple ? "hand_authored" : "custom";
|
|
1858
1963
|
const scaffoldOpts = {
|
|
1859
1964
|
slug,
|
|
@@ -1886,6 +1991,7 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
1886
1991
|
["postcss.config.mjs", postcssConfigMjs()],
|
|
1887
1992
|
["app/globals.css", globalsCss()],
|
|
1888
1993
|
["lib/utils.ts", libUtilsTs()],
|
|
1994
|
+
["lib/request-origin.ts", libRequestOriginTs()],
|
|
1889
1995
|
["components/theme-provider.tsx", componentsThemeProviderTsx()],
|
|
1890
1996
|
["components/ui/button.tsx", componentsUiButtonTsx()],
|
|
1891
1997
|
["components/ui/card.tsx", componentsUiCardTsx()],
|
|
@@ -1904,6 +2010,19 @@ async function moduleInitCommand(slugArg, options) {
|
|
|
1904
2010
|
["app/page.tsx", appPageTsx(scaffoldOpts)]
|
|
1905
2011
|
);
|
|
1906
2012
|
}
|
|
2013
|
+
if (options.deployTarget !== void 0) {
|
|
2014
|
+
switch (options.deployTarget) {
|
|
2015
|
+
case "railway":
|
|
2016
|
+
files.push(["railway.json", railwayJson()]);
|
|
2017
|
+
break;
|
|
2018
|
+
case "vercel":
|
|
2019
|
+
files.push(["vercel.json", vercelJson()]);
|
|
2020
|
+
break;
|
|
2021
|
+
case "fly":
|
|
2022
|
+
files.push(["fly.toml", flyToml(scaffoldOpts)]);
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1907
2026
|
for (const [relPath, content] of files) {
|
|
1908
2027
|
const absPath = join(dir, relPath);
|
|
1909
2028
|
const parentDir = join(absPath, "..");
|
|
@@ -3104,13 +3223,13 @@ async function resolveDevPort(requested) {
|
|
|
3104
3223
|
}
|
|
3105
3224
|
async function isPortFree(port) {
|
|
3106
3225
|
const { createServer } = await import('net');
|
|
3107
|
-
return new Promise((
|
|
3226
|
+
return new Promise((resolve12) => {
|
|
3108
3227
|
const server = createServer();
|
|
3109
3228
|
server.once("error", () => {
|
|
3110
|
-
|
|
3229
|
+
resolve12(false);
|
|
3111
3230
|
});
|
|
3112
3231
|
server.once("listening", () => {
|
|
3113
|
-
server.close(() =>
|
|
3232
|
+
server.close(() => resolve12(true));
|
|
3114
3233
|
});
|
|
3115
3234
|
server.listen(port, "0.0.0.0");
|
|
3116
3235
|
});
|
|
@@ -3307,7 +3426,7 @@ async function loginCommand(options) {
|
|
|
3307
3426
|
return 0;
|
|
3308
3427
|
}
|
|
3309
3428
|
function sleep(ms) {
|
|
3310
|
-
return new Promise((
|
|
3429
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
3311
3430
|
}
|
|
3312
3431
|
function formatSeconds(seconds) {
|
|
3313
3432
|
if (seconds < 60) return `${seconds}s`;
|
|
@@ -3765,6 +3884,230 @@ function isPlausibleRepoUrl(url) {
|
|
|
3765
3884
|
return /^git@[^:]+:[^/]+\/.+/.test(url);
|
|
3766
3885
|
}
|
|
3767
3886
|
|
|
3887
|
+
// src/commands/module/convert.ts
|
|
3888
|
+
init_scaffold();
|
|
3889
|
+
var SDK_DEP_RANGE = "^0.8.0";
|
|
3890
|
+
var SCAN_DIRS = ["app", "src", "pages", "lib", "components"];
|
|
3891
|
+
var SCAN_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs"];
|
|
3892
|
+
var MAX_SCAN_FILES = 2e3;
|
|
3893
|
+
function deriveSlug(input) {
|
|
3894
|
+
return input.toLowerCase().replace(/^@[^/]+\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
|
|
3895
|
+
}
|
|
3896
|
+
function convertModule(opts) {
|
|
3897
|
+
const dir = resolve(opts.dir);
|
|
3898
|
+
const pkgPath = join(dir, "package.json");
|
|
3899
|
+
if (!existsSync(pkgPath)) {
|
|
3900
|
+
throw new Error(
|
|
3901
|
+
"No package.json found \u2014 run `dashwise module convert` inside a Next.js project."
|
|
3902
|
+
);
|
|
3903
|
+
}
|
|
3904
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
3905
|
+
const nextConfigFile = ["next.config.mjs", "next.config.js", "next.config.ts"].find(
|
|
3906
|
+
(f) => existsSync(join(dir, f))
|
|
3907
|
+
);
|
|
3908
|
+
const looksNext = Boolean(pkg.dependencies?.next || pkg.devDependencies?.next) || nextConfigFile !== void 0 || existsSync(join(dir, "app")) || existsSync(join(dir, "src", "app"));
|
|
3909
|
+
if (!looksNext) {
|
|
3910
|
+
throw new Error(
|
|
3911
|
+
"This does not look like a Next.js app (no `next` dependency, next.config, or app/ directory)."
|
|
3912
|
+
);
|
|
3913
|
+
}
|
|
3914
|
+
const slug = opts.slug ?? deriveSlug(pkg.name ?? basename(dir));
|
|
3915
|
+
if (!isValidSlug(slug)) {
|
|
3916
|
+
throw new Error(
|
|
3917
|
+
`Could not derive a valid module slug ("${slug}"). Pass --slug (must match /^[a-z][a-z0-9_-]*$/).`
|
|
3918
|
+
);
|
|
3919
|
+
}
|
|
3920
|
+
const name = opts.name ?? pkg.name ?? slug;
|
|
3921
|
+
const sopts = {
|
|
3922
|
+
slug,
|
|
3923
|
+
name,
|
|
3924
|
+
kind: "custom",
|
|
3925
|
+
...opts.repositoryUrl ? { repositoryUrl: opts.repositoryUrl } : {}
|
|
3926
|
+
};
|
|
3927
|
+
const created = [];
|
|
3928
|
+
const skipped = [];
|
|
3929
|
+
const warnings = [];
|
|
3930
|
+
const writeIfMissing = (rel, content) => {
|
|
3931
|
+
const p = join(dir, rel);
|
|
3932
|
+
if (existsSync(p)) {
|
|
3933
|
+
skipped.push(rel);
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
3937
|
+
writeFileSync(p, content, "utf-8");
|
|
3938
|
+
created.push(rel);
|
|
3939
|
+
};
|
|
3940
|
+
writeIfMissing("module.json", manifestJson(sopts));
|
|
3941
|
+
writeIfMissing("middleware.ts", middlewareTs());
|
|
3942
|
+
writeIfMissing("app/api/auth/sign-in/route.ts", appApiAuthSignInTs());
|
|
3943
|
+
writeIfMissing("app/api/auth/callback/route.ts", appApiAuthCallbackTs());
|
|
3944
|
+
writeIfMissing("app/api/auth/sign-out/route.ts", appApiAuthSignOutTs());
|
|
3945
|
+
writeIfMissing("app/sign-in-error/page.tsx", appSignInErrorPageTsx());
|
|
3946
|
+
writeIfMissing(".env.local.example", envLocalExample());
|
|
3947
|
+
const layoutFile = [
|
|
3948
|
+
"app/layout.tsx",
|
|
3949
|
+
"app/layout.jsx",
|
|
3950
|
+
"src/app/layout.tsx"
|
|
3951
|
+
].find((f) => existsSync(join(dir, f)));
|
|
3952
|
+
if (!layoutFile) {
|
|
3953
|
+
writeIfMissing("app/layout.tsx", appLayoutCustomTsx(sopts));
|
|
3954
|
+
} else if (!readFileSync(join(dir, layoutFile), "utf-8").includes("AuthProvider")) {
|
|
3955
|
+
warnings.push(
|
|
3956
|
+
`${layoutFile}: wrap your tree in <AuthProvider> from "@dashai/sdk/auth/client" so client components can read the session.`
|
|
3957
|
+
);
|
|
3958
|
+
} else {
|
|
3959
|
+
skipped.push(layoutFile);
|
|
3960
|
+
}
|
|
3961
|
+
if (!nextConfigFile) {
|
|
3962
|
+
writeIfMissing("next.config.mjs", standaloneNextConfig());
|
|
3963
|
+
} else if (!readFileSync(join(dir, nextConfigFile), "utf-8").includes("standalone")) {
|
|
3964
|
+
warnings.push(
|
|
3965
|
+
`${nextConfigFile}: add \`output: 'standalone'\` \u2014 required for the module runtime bundle.`
|
|
3966
|
+
);
|
|
3967
|
+
}
|
|
3968
|
+
if (!pkg.dependencies?.["@dashai/sdk"]) {
|
|
3969
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, "@dashai/sdk": SDK_DEP_RANGE };
|
|
3970
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
3971
|
+
created.push("package.json (added @dashai/sdk)");
|
|
3972
|
+
}
|
|
3973
|
+
const outboundApis = scanOutboundHosts(dir);
|
|
3974
|
+
const runtimeBlockAdded = normalizeManifest(join(dir, "module.json"), {
|
|
3975
|
+
outboundApis
|
|
3976
|
+
});
|
|
3977
|
+
return {
|
|
3978
|
+
slug,
|
|
3979
|
+
name,
|
|
3980
|
+
created,
|
|
3981
|
+
skipped,
|
|
3982
|
+
warnings,
|
|
3983
|
+
outboundApis,
|
|
3984
|
+
runtimeBlockAdded
|
|
3985
|
+
};
|
|
3986
|
+
}
|
|
3987
|
+
function standaloneNextConfig() {
|
|
3988
|
+
return `/** @type {import("next").NextConfig} */
|
|
3989
|
+
const nextConfig = {
|
|
3990
|
+
output: 'standalone',
|
|
3991
|
+
typescript: { ignoreBuildErrors: true },
|
|
3992
|
+
eslint: { ignoreDuringBuilds: true },
|
|
3993
|
+
};
|
|
3994
|
+
export default nextConfig;
|
|
3995
|
+
`;
|
|
3996
|
+
}
|
|
3997
|
+
function normalizeManifest(manifestPath2, opts) {
|
|
3998
|
+
const manifest = JSON.parse(readFileSync(manifestPath2, "utf-8"));
|
|
3999
|
+
let runtimeAdded = false;
|
|
4000
|
+
if (manifest.module_kind === void 0) manifest.module_kind = "custom";
|
|
4001
|
+
let runtime = manifest.runtime;
|
|
4002
|
+
if (manifest.module_kind === "custom" && !runtime) {
|
|
4003
|
+
runtime = {
|
|
4004
|
+
type: "nextjs",
|
|
4005
|
+
kind: "machine",
|
|
4006
|
+
machine_size: "shared-cpu-1x",
|
|
4007
|
+
server_actions: true
|
|
4008
|
+
};
|
|
4009
|
+
manifest.runtime = runtime;
|
|
4010
|
+
runtimeAdded = true;
|
|
4011
|
+
}
|
|
4012
|
+
if (runtime && opts.outboundApis.length > 0) {
|
|
4013
|
+
const existing = Array.isArray(runtime.outbound_apis) ? runtime.outbound_apis : [];
|
|
4014
|
+
runtime.outbound_apis = Array.from(
|
|
4015
|
+
/* @__PURE__ */ new Set([...existing, ...opts.outboundApis])
|
|
4016
|
+
).sort();
|
|
4017
|
+
}
|
|
4018
|
+
writeFileSync(manifestPath2, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
4019
|
+
return runtimeAdded;
|
|
4020
|
+
}
|
|
4021
|
+
function scanOutboundHosts(dir) {
|
|
4022
|
+
const hosts = /* @__PURE__ */ new Set();
|
|
4023
|
+
let scanned = 0;
|
|
4024
|
+
const hostRe = /https:\/\/([a-zA-Z0-9.-]+)/g;
|
|
4025
|
+
const walk2 = (abs) => {
|
|
4026
|
+
if (scanned >= MAX_SCAN_FILES) return;
|
|
4027
|
+
let entries;
|
|
4028
|
+
try {
|
|
4029
|
+
entries = readdirSync(abs);
|
|
4030
|
+
} catch {
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
for (const entry of entries) {
|
|
4034
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
4035
|
+
const p = join(abs, entry);
|
|
4036
|
+
let st;
|
|
4037
|
+
try {
|
|
4038
|
+
st = statSync(p);
|
|
4039
|
+
} catch {
|
|
4040
|
+
continue;
|
|
4041
|
+
}
|
|
4042
|
+
if (st.isDirectory()) {
|
|
4043
|
+
walk2(p);
|
|
4044
|
+
} else if (SCAN_EXTENSIONS.some((e) => entry.endsWith(e))) {
|
|
4045
|
+
if (scanned >= MAX_SCAN_FILES) return;
|
|
4046
|
+
scanned++;
|
|
4047
|
+
let body;
|
|
4048
|
+
try {
|
|
4049
|
+
body = readFileSync(p, "utf-8");
|
|
4050
|
+
} catch {
|
|
4051
|
+
continue;
|
|
4052
|
+
}
|
|
4053
|
+
for (const m of body.matchAll(hostRe)) {
|
|
4054
|
+
const host = m[1].toLowerCase();
|
|
4055
|
+
if (host === "localhost" || host.endsWith(".local")) continue;
|
|
4056
|
+
if (host.includes("dashwise")) continue;
|
|
4057
|
+
if (/^\d+\.\d+\.\d+\.\d+$/.test(host)) continue;
|
|
4058
|
+
hosts.add(host);
|
|
4059
|
+
}
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
};
|
|
4063
|
+
for (const d of SCAN_DIRS) {
|
|
4064
|
+
const abs = join(dir, d);
|
|
4065
|
+
if (existsSync(abs)) walk2(abs);
|
|
4066
|
+
}
|
|
4067
|
+
return Array.from(hosts).sort();
|
|
4068
|
+
}
|
|
4069
|
+
async function moduleConvertCommand(opts) {
|
|
4070
|
+
try {
|
|
4071
|
+
const result = convertModule({
|
|
4072
|
+
dir: opts.dir ?? process.cwd(),
|
|
4073
|
+
...opts.slug !== void 0 ? { slug: opts.slug } : {},
|
|
4074
|
+
...opts.name !== void 0 ? { name: opts.name } : {},
|
|
4075
|
+
...opts.git !== void 0 ? { repositoryUrl: opts.git } : {}
|
|
4076
|
+
});
|
|
4077
|
+
console.log(`Converted "${result.name}" (slug: ${result.slug}) to a DashWise module.
|
|
4078
|
+
`);
|
|
4079
|
+
if (result.created.length) {
|
|
4080
|
+
console.log(" Created:");
|
|
4081
|
+
for (const f of result.created) console.log(` + ${f}`);
|
|
4082
|
+
}
|
|
4083
|
+
if (result.skipped.length) {
|
|
4084
|
+
console.log(" Left as-is:");
|
|
4085
|
+
for (const f of result.skipped) console.log(` \xB7 ${f}`);
|
|
4086
|
+
}
|
|
4087
|
+
if (result.runtimeBlockAdded) {
|
|
4088
|
+
console.log(" + module.json: added runtime block (nextjs/machine)");
|
|
4089
|
+
}
|
|
4090
|
+
if (result.outboundApis.length) {
|
|
4091
|
+
console.log(
|
|
4092
|
+
` + module.json runtime.outbound_apis: ${result.outboundApis.join(", ")}`
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
if (result.warnings.length) {
|
|
4096
|
+
console.log("\n Manual steps needed:");
|
|
4097
|
+
for (const w of result.warnings) console.log(` ! ${w}`);
|
|
4098
|
+
}
|
|
4099
|
+
console.log(
|
|
4100
|
+
"\nNext: `npm install`, then `dashwise dev` to run it against DashWise, or `dashwise module publish` to deploy."
|
|
4101
|
+
);
|
|
4102
|
+
return 0;
|
|
4103
|
+
} catch (err) {
|
|
4104
|
+
console.error(
|
|
4105
|
+
`convert failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4106
|
+
);
|
|
4107
|
+
return 1;
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
|
|
3768
4111
|
// src/commands/module/generate.ts
|
|
3769
4112
|
init_api_client();
|
|
3770
4113
|
init_output();
|
|
@@ -5243,6 +5586,9 @@ function surfacePullFailure(err) {
|
|
|
5243
5586
|
return 1;
|
|
5244
5587
|
}
|
|
5245
5588
|
|
|
5589
|
+
// src/bin.ts
|
|
5590
|
+
init_scaffold();
|
|
5591
|
+
|
|
5246
5592
|
// src/commands/api-keys.ts
|
|
5247
5593
|
init_api_client();
|
|
5248
5594
|
init_config();
|
|
@@ -6004,7 +6350,7 @@ function formatTimestamp(iso) {
|
|
|
6004
6350
|
return iso.slice(0, 19).replace("T", " ");
|
|
6005
6351
|
}
|
|
6006
6352
|
function sleep2(ms) {
|
|
6007
|
-
return new Promise((
|
|
6353
|
+
return new Promise((resolve12) => setTimeout(resolve12, ms));
|
|
6008
6354
|
}
|
|
6009
6355
|
|
|
6010
6356
|
// src/commands/module/action-log.ts
|
|
@@ -6098,7 +6444,7 @@ async function runTail2(installationId, options) {
|
|
|
6098
6444
|
}
|
|
6099
6445
|
dim(`(tail: transient error \u2014 ${err.message})`);
|
|
6100
6446
|
}
|
|
6101
|
-
await new Promise((
|
|
6447
|
+
await new Promise((resolve12) => setTimeout(resolve12, POLL_INTERVAL_MS2));
|
|
6102
6448
|
}
|
|
6103
6449
|
}
|
|
6104
6450
|
function printTable(rows) {
|
|
@@ -6253,6 +6599,11 @@ var initOptions = (cmd) => cmd.option("--dir <path>", "Override the target direc
|
|
|
6253
6599
|
).option(
|
|
6254
6600
|
"--no-provision",
|
|
6255
6601
|
"Skip the `POST /api/installations/dev` step. Local scaffold is still written; you can provision later by re-running `dashwise init --force` (or, once it ships, `dashwise module provision-dev-install`)."
|
|
6602
|
+
).addOption(
|
|
6603
|
+
new Option(
|
|
6604
|
+
"--deploy-target <platform>",
|
|
6605
|
+
"Emit a platform deploy config file alongside the scaffold (railway.json | vercel.json | fly.toml). Default: none (clean repo). Pair with `dashwise env export --format <platform>` to push env vars after provisioning."
|
|
6606
|
+
).choices([...DEPLOY_TARGETS])
|
|
6256
6607
|
);
|
|
6257
6608
|
var initAction = async (slugArg, cmdOpts) => {
|
|
6258
6609
|
process.exitCode = await moduleInitCommand(slugArg, {
|
|
@@ -6264,7 +6615,8 @@ var initAction = async (slugArg, cmdOpts) => {
|
|
|
6264
6615
|
...cmdOpts.git !== void 0 ? { git: cmdOpts.git } : {},
|
|
6265
6616
|
...cmdOpts.description !== void 0 ? { description: cmdOpts.description } : {},
|
|
6266
6617
|
...cmdOpts.workspace !== void 0 ? { workspace: cmdOpts.workspace } : {},
|
|
6267
|
-
...cmdOpts.provision !== void 0 ? { provision: cmdOpts.provision } : {}
|
|
6618
|
+
...cmdOpts.provision !== void 0 ? { provision: cmdOpts.provision } : {},
|
|
6619
|
+
...cmdOpts.deployTarget !== void 0 ? { deployTarget: cmdOpts.deployTarget } : {}
|
|
6268
6620
|
});
|
|
6269
6621
|
};
|
|
6270
6622
|
initOptions(
|
|
@@ -6286,6 +6638,18 @@ moduleCmd.command("connect-git <repo-url>").description("Link the local module t
|
|
|
6286
6638
|
});
|
|
6287
6639
|
}
|
|
6288
6640
|
);
|
|
6641
|
+
moduleCmd.command("convert [dir]").description(
|
|
6642
|
+
"Convert an EXISTING Next.js app into a DashWise module \u2014 idempotently inject module.json, auth wiring, the SDK dep, and output:standalone. Creates missing files; warns (never overwrites) for an existing layout/next.config/module.json."
|
|
6643
|
+
).option("--slug <slug>", "Module slug (default: derived from package.json name / dir).").option("--name <name>", "Human-readable module name (default: package.json name).").option("--git <url>", "Git repository URL \u2014 written to module.json#repository.url.").action(
|
|
6644
|
+
async (dir, cmdOpts) => {
|
|
6645
|
+
process.exitCode = await moduleConvertCommand({
|
|
6646
|
+
...dir !== void 0 ? { dir } : {},
|
|
6647
|
+
...cmdOpts.slug !== void 0 ? { slug: cmdOpts.slug } : {},
|
|
6648
|
+
...cmdOpts.name !== void 0 ? { name: cmdOpts.name } : {},
|
|
6649
|
+
...cmdOpts.git !== void 0 ? { git: cmdOpts.git } : {}
|
|
6650
|
+
});
|
|
6651
|
+
}
|
|
6652
|
+
);
|
|
6289
6653
|
moduleCmd.command("generate").description("Regenerate typed wrappers from the local module.json into node_modules/@dashai/generated/.").option("--dir <path>", "Project root (default: current directory).").option("--dry-run", "Print summary without writing files.").action(async (cmdOpts) => {
|
|
6290
6654
|
process.exitCode = await moduleGenerateCommand({
|
|
6291
6655
|
...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
|
|
@@ -6651,7 +7015,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
|
|
|
6651
7015
|
}
|
|
6652
7016
|
})();
|
|
6653
7017
|
function getVersion() {
|
|
6654
|
-
return "0.
|
|
7018
|
+
return "0.6.0";
|
|
6655
7019
|
}
|
|
6656
7020
|
function parseIntOption(value) {
|
|
6657
7021
|
const n = Number.parseInt(value, 10);
|