@dashai/cli 0.5.1 → 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,7 +1,7 @@
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
7
  import { Command, Option } from 'commander';
@@ -3223,13 +3223,13 @@ async function resolveDevPort(requested) {
3223
3223
  }
3224
3224
  async function isPortFree(port) {
3225
3225
  const { createServer } = await import('net');
3226
- return new Promise((resolve11) => {
3226
+ return new Promise((resolve12) => {
3227
3227
  const server = createServer();
3228
3228
  server.once("error", () => {
3229
- resolve11(false);
3229
+ resolve12(false);
3230
3230
  });
3231
3231
  server.once("listening", () => {
3232
- server.close(() => resolve11(true));
3232
+ server.close(() => resolve12(true));
3233
3233
  });
3234
3234
  server.listen(port, "0.0.0.0");
3235
3235
  });
@@ -3426,7 +3426,7 @@ async function loginCommand(options) {
3426
3426
  return 0;
3427
3427
  }
3428
3428
  function sleep(ms) {
3429
- return new Promise((resolve11) => setTimeout(resolve11, ms));
3429
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
3430
3430
  }
3431
3431
  function formatSeconds(seconds) {
3432
3432
  if (seconds < 60) return `${seconds}s`;
@@ -3884,6 +3884,230 @@ function isPlausibleRepoUrl(url) {
3884
3884
  return /^git@[^:]+:[^/]+\/.+/.test(url);
3885
3885
  }
3886
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
+
3887
4111
  // src/commands/module/generate.ts
3888
4112
  init_api_client();
3889
4113
  init_output();
@@ -6126,7 +6350,7 @@ function formatTimestamp(iso) {
6126
6350
  return iso.slice(0, 19).replace("T", " ");
6127
6351
  }
6128
6352
  function sleep2(ms) {
6129
- return new Promise((resolve11) => setTimeout(resolve11, ms));
6353
+ return new Promise((resolve12) => setTimeout(resolve12, ms));
6130
6354
  }
6131
6355
 
6132
6356
  // src/commands/module/action-log.ts
@@ -6220,7 +6444,7 @@ async function runTail2(installationId, options) {
6220
6444
  }
6221
6445
  dim(`(tail: transient error \u2014 ${err.message})`);
6222
6446
  }
6223
- await new Promise((resolve11) => setTimeout(resolve11, POLL_INTERVAL_MS2));
6447
+ await new Promise((resolve12) => setTimeout(resolve12, POLL_INTERVAL_MS2));
6224
6448
  }
6225
6449
  }
6226
6450
  function printTable(rows) {
@@ -6414,6 +6638,18 @@ moduleCmd.command("connect-git <repo-url>").description("Link the local module t
6414
6638
  });
6415
6639
  }
6416
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
+ );
6417
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) => {
6418
6654
  process.exitCode = await moduleGenerateCommand({
6419
6655
  ...cmdOpts.dir !== void 0 ? { dir: cmdOpts.dir } : {},
@@ -6779,7 +7015,7 @@ fieldCmd.command("set-type <table-slug> <field-slug> <new-type>").description(
6779
7015
  }
6780
7016
  })();
6781
7017
  function getVersion() {
6782
- return "0.5.1";
7018
+ return "0.6.0";
6783
7019
  }
6784
7020
  function parseIntOption(value) {
6785
7021
  const n = Number.parseInt(value, 10);