@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 +244 -8
- package/dist/bin.js.map +1 -1
- package/package.json +2 -2
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((
|
|
3226
|
+
return new Promise((resolve12) => {
|
|
3227
3227
|
const server = createServer();
|
|
3228
3228
|
server.once("error", () => {
|
|
3229
|
-
|
|
3229
|
+
resolve12(false);
|
|
3230
3230
|
});
|
|
3231
3231
|
server.once("listening", () => {
|
|
3232
|
-
server.close(() =>
|
|
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((
|
|
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((
|
|
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((
|
|
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.
|
|
7018
|
+
return "0.6.0";
|
|
6783
7019
|
}
|
|
6784
7020
|
function parseIntOption(value) {
|
|
6785
7021
|
const n = Number.parseInt(value, 10);
|