@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/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
- const moduleOrigin = url.origin;
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', url.origin);
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', url.origin);
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', url.origin);
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, url.origin));
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
- return NextResponse.redirect(new URL('/', request.url), 303);
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
- var SCAFFOLD_DEFAULT_API_URL;
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((resolve11) => {
3226
+ return new Promise((resolve12) => {
3108
3227
  const server = createServer();
3109
3228
  server.once("error", () => {
3110
- resolve11(false);
3229
+ resolve12(false);
3111
3230
  });
3112
3231
  server.once("listening", () => {
3113
- server.close(() => resolve11(true));
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((resolve11) => setTimeout(resolve11, ms));
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((resolve11) => setTimeout(resolve11, ms));
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((resolve11) => setTimeout(resolve11, POLL_INTERVAL_MS2));
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.5.0";
7018
+ return "0.6.0";
6655
7019
  }
6656
7020
  function parseIntOption(value) {
6657
7021
  const n = Number.parseInt(value, 10);