@objectstack/cli 1.1.0 → 2.0.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/.turbo/turbo-build.log +6 -6
- package/CHANGELOG.md +14 -0
- package/dist/bin.js +54 -162
- package/dist/index.js +50 -158
- package/package.json +9 -8
- package/src/bin.ts +3 -1
- package/src/commands/compile.ts +2 -0
- package/src/commands/create.ts +2 -0
- package/src/commands/dev.ts +3 -1
- package/src/commands/doctor.ts +2 -0
- package/src/commands/generate.ts +2 -0
- package/src/commands/info.ts +2 -0
- package/src/commands/init.ts +2 -0
- package/src/commands/serve.ts +40 -37
- package/src/commands/studio.ts +5 -3
- package/src/commands/test.ts +2 -0
- package/src/commands/validate.ts +2 -0
- package/src/index.ts +2 -0
- package/src/utils/config.ts +2 -0
- package/src/utils/format.ts +3 -1
- package/src/utils/{console.ts → studio.ts} +43 -29
- package/tsup.config.ts +2 -0
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @objectstack/cli@
|
|
2
|
+
> @objectstack/cli@2.0.0 build /home/runner/work/spec/spec/packages/cli
|
|
3
3
|
> tsup
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/bin.ts
|
|
@@ -15,10 +15,10 @@
|
|
|
15
15
|
[34mESM[39m Build start
|
|
16
16
|
[34mCLI[39m Cleaning output folder
|
|
17
17
|
[34mESM[39m Build start
|
|
18
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
19
|
-
[32mESM[39m ⚡️ Build success in
|
|
20
|
-
[32mESM[39m [1mdist/bin.js [22m[
|
|
21
|
-
[32mESM[39m ⚡️ Build success in
|
|
18
|
+
[32mESM[39m [1mdist/index.js [22m[32m57.98 KB[39m
|
|
19
|
+
[32mESM[39m ⚡️ Build success in 106ms
|
|
20
|
+
[32mESM[39m [1mdist/bin.js [22m[32m60.60 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 119ms
|
|
22
22
|
[34mDTS[39m Build start
|
|
23
|
-
[32mDTS[39m ⚡️ Build success in
|
|
23
|
+
[32mDTS[39m ⚡️ Build success in 9596ms
|
|
24
24
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m2.93 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @objectstack/cli
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [38e5dd5]
|
|
8
|
+
- Updated dependencies [38e5dd5]
|
|
9
|
+
- @objectstack/spec@2.0.0
|
|
10
|
+
- @objectstack/core@2.0.0
|
|
11
|
+
- @objectstack/objectql@2.0.0
|
|
12
|
+
- @objectstack/driver-memory@2.0.0
|
|
13
|
+
- @objectstack/plugin-hono-server@2.0.0
|
|
14
|
+
- @objectstack/rest@2.0.0
|
|
15
|
+
- @objectstack/runtime@2.0.0
|
|
16
|
+
|
|
3
17
|
## 1.0.12
|
|
4
18
|
|
|
5
19
|
### Patch Changes
|
package/dist/bin.js
CHANGED
|
@@ -132,7 +132,7 @@ function printServerReady(opts) {
|
|
|
132
132
|
console.log("");
|
|
133
133
|
console.log(chalk.cyan(" \u279C") + chalk.bold(" API: ") + chalk.cyan(base + "/"));
|
|
134
134
|
if (opts.uiEnabled && opts.studioPath) {
|
|
135
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold("
|
|
135
|
+
console.log(chalk.cyan(" \u279C") + chalk.bold(" Studio: ") + chalk.cyan(base + opts.studioPath + "/"));
|
|
136
136
|
}
|
|
137
137
|
console.log("");
|
|
138
138
|
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
@@ -314,7 +314,7 @@ import chalk4 from "chalk";
|
|
|
314
314
|
import { execSync, spawn } from "child_process";
|
|
315
315
|
import fs3 from "fs";
|
|
316
316
|
import path3 from "path";
|
|
317
|
-
var devCommand = new Command2("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable
|
|
317
|
+
var devCommand = new Command2("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable Studio UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
|
|
318
318
|
printHeader("Development Mode");
|
|
319
319
|
const configPath = path3.resolve(process.cwd(), "objectstack.config.ts");
|
|
320
320
|
if (packageName === "all" && fs3.existsSync(configPath)) {
|
|
@@ -740,30 +740,27 @@ var createCommand = new Command4("create").description("Create a new package, pl
|
|
|
740
740
|
import { Command as Command5 } from "commander";
|
|
741
741
|
import path7 from "path";
|
|
742
742
|
import fs7 from "fs";
|
|
743
|
-
import
|
|
743
|
+
import net from "net";
|
|
744
744
|
import chalk7 from "chalk";
|
|
745
745
|
import { bundleRequire as bundleRequire2 } from "bundle-require";
|
|
746
746
|
|
|
747
|
-
// src/utils/
|
|
747
|
+
// src/utils/studio.ts
|
|
748
748
|
import path6 from "path";
|
|
749
749
|
import fs6 from "fs";
|
|
750
|
-
import net from "net";
|
|
751
|
-
import { spawn as spawn2 } from "child_process";
|
|
752
750
|
var STUDIO_PATH = "/_studio";
|
|
753
|
-
|
|
754
|
-
function resolveConsolePath() {
|
|
751
|
+
function resolveStudioPath() {
|
|
755
752
|
const cwd = process.cwd();
|
|
756
753
|
const candidates = [
|
|
757
|
-
path6.resolve(cwd, "apps/
|
|
758
|
-
path6.resolve(cwd, "../../apps/
|
|
759
|
-
path6.resolve(cwd, "../apps/
|
|
754
|
+
path6.resolve(cwd, "apps/studio"),
|
|
755
|
+
path6.resolve(cwd, "../../apps/studio"),
|
|
756
|
+
path6.resolve(cwd, "../apps/studio")
|
|
760
757
|
];
|
|
761
758
|
for (const candidate of candidates) {
|
|
762
759
|
const pkgPath = path6.join(candidate, "package.json");
|
|
763
760
|
if (fs6.existsSync(pkgPath)) {
|
|
764
761
|
try {
|
|
765
762
|
const pkg2 = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
|
|
766
|
-
if (pkg2.name === "@objectstack/
|
|
763
|
+
if (pkg2.name === "@objectstack/studio") return candidate;
|
|
767
764
|
} catch {
|
|
768
765
|
}
|
|
769
766
|
}
|
|
@@ -771,142 +768,41 @@ function resolveConsolePath() {
|
|
|
771
768
|
try {
|
|
772
769
|
const { createRequire: createRequire2 } = __require("module");
|
|
773
770
|
const req = createRequire2(import.meta.url);
|
|
774
|
-
const resolved = req.resolve("@objectstack/
|
|
771
|
+
const resolved = req.resolve("@objectstack/studio/package.json");
|
|
775
772
|
return path6.dirname(resolved);
|
|
776
773
|
} catch {
|
|
777
774
|
return null;
|
|
778
775
|
}
|
|
779
776
|
}
|
|
780
|
-
function
|
|
781
|
-
return fs6.existsSync(path6.join(
|
|
782
|
-
}
|
|
783
|
-
function findAvailablePort(start = VITE_PORT_START) {
|
|
784
|
-
return new Promise((resolve, reject) => {
|
|
785
|
-
const server = net.createServer();
|
|
786
|
-
server.once("error", () => {
|
|
787
|
-
findAvailablePort(start + 1).then(resolve, reject);
|
|
788
|
-
});
|
|
789
|
-
server.once("listening", () => {
|
|
790
|
-
server.close(() => resolve(start));
|
|
791
|
-
});
|
|
792
|
-
server.listen(start);
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
async function spawnViteDevServer(consolePath, options = {}) {
|
|
796
|
-
const vitePort = await findAvailablePort(VITE_PORT_START);
|
|
797
|
-
const viteBinCandidates = [
|
|
798
|
-
path6.join(consolePath, "node_modules", ".bin", "vite"),
|
|
799
|
-
path6.join(consolePath, "..", "..", "node_modules", ".bin", "vite")
|
|
800
|
-
];
|
|
801
|
-
let viteBin = null;
|
|
802
|
-
for (const candidate of viteBinCandidates) {
|
|
803
|
-
if (fs6.existsSync(candidate)) {
|
|
804
|
-
viteBin = candidate;
|
|
805
|
-
break;
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
const command = viteBin || "npx";
|
|
809
|
-
const args = viteBin ? ["--port", String(vitePort), "--strictPort"] : ["vite", "--port", String(vitePort), "--strictPort"];
|
|
810
|
-
const child = spawn2(command, args, {
|
|
811
|
-
cwd: consolePath,
|
|
812
|
-
env: {
|
|
813
|
-
...process.env,
|
|
814
|
-
VITE_BASE: `${STUDIO_PATH}/`,
|
|
815
|
-
VITE_PORT: String(vitePort),
|
|
816
|
-
VITE_HMR_PORT: String(vitePort),
|
|
817
|
-
VITE_RUNTIME_MODE: "server",
|
|
818
|
-
VITE_SERVER_URL: "",
|
|
819
|
-
// Same-origin API
|
|
820
|
-
NODE_ENV: "development"
|
|
821
|
-
},
|
|
822
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
823
|
-
});
|
|
824
|
-
let stderr = "";
|
|
825
|
-
child.stderr?.on("data", (data) => {
|
|
826
|
-
stderr += data.toString();
|
|
827
|
-
});
|
|
828
|
-
await new Promise((resolve, reject) => {
|
|
829
|
-
const timeout = setTimeout(() => {
|
|
830
|
-
child.kill();
|
|
831
|
-
reject(new Error(`Vite dev server timed out after 30 s.
|
|
832
|
-
${stderr}`));
|
|
833
|
-
}, 3e4);
|
|
834
|
-
child.stdout?.on("data", (data) => {
|
|
835
|
-
const output = data.toString();
|
|
836
|
-
if (output.includes("Local:") || output.includes("ready in")) {
|
|
837
|
-
clearTimeout(timeout);
|
|
838
|
-
resolve();
|
|
839
|
-
}
|
|
840
|
-
});
|
|
841
|
-
child.on("error", (err) => {
|
|
842
|
-
clearTimeout(timeout);
|
|
843
|
-
reject(err);
|
|
844
|
-
});
|
|
845
|
-
child.on("exit", (code) => {
|
|
846
|
-
if (code !== 0 && code !== null) {
|
|
847
|
-
clearTimeout(timeout);
|
|
848
|
-
reject(new Error(`Vite exited with code ${code}.
|
|
849
|
-
${stderr}`));
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
|
-
return { port: vitePort, process: child };
|
|
854
|
-
}
|
|
855
|
-
function createConsoleProxyPlugin(vitePort) {
|
|
856
|
-
return {
|
|
857
|
-
name: "com.objectstack.console-proxy",
|
|
858
|
-
init: async () => {
|
|
859
|
-
},
|
|
860
|
-
start: async (ctx) => {
|
|
861
|
-
const httpServer = ctx.getService?.("http.server");
|
|
862
|
-
if (!httpServer?.getRawApp) {
|
|
863
|
-
ctx.logger?.warn?.("Console proxy: http.server service not found \u2014 skipping");
|
|
864
|
-
return;
|
|
865
|
-
}
|
|
866
|
-
const app = httpServer.getRawApp();
|
|
867
|
-
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
868
|
-
app.all(`${STUDIO_PATH}/*`, async (c) => {
|
|
869
|
-
const targetUrl = `http://localhost:${vitePort}${c.req.path}`;
|
|
870
|
-
try {
|
|
871
|
-
const headers = new Headers(c.req.raw.headers);
|
|
872
|
-
headers.delete("host");
|
|
873
|
-
const isBodyAllowed = !["GET", "HEAD"].includes(c.req.method);
|
|
874
|
-
const resp = await fetch(targetUrl, {
|
|
875
|
-
method: c.req.method,
|
|
876
|
-
headers,
|
|
877
|
-
body: isBodyAllowed ? c.req.raw.body : void 0,
|
|
878
|
-
// @ts-expect-error — duplex required for streaming request body
|
|
879
|
-
duplex: isBodyAllowed ? "half" : void 0
|
|
880
|
-
});
|
|
881
|
-
return new Response(resp.body, {
|
|
882
|
-
status: resp.status,
|
|
883
|
-
headers: resp.headers
|
|
884
|
-
});
|
|
885
|
-
} catch {
|
|
886
|
-
return c.text("Console dev server is starting\u2026", 502);
|
|
887
|
-
}
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
};
|
|
777
|
+
function hasStudioDist(studioPath) {
|
|
778
|
+
return fs6.existsSync(path6.join(studioPath, "dist", "index.html"));
|
|
891
779
|
}
|
|
892
|
-
function
|
|
780
|
+
function createStudioStaticPlugin(distPath, options) {
|
|
893
781
|
return {
|
|
894
|
-
name: "com.objectstack.
|
|
782
|
+
name: "com.objectstack.studio-static",
|
|
895
783
|
init: async () => {
|
|
896
784
|
},
|
|
897
785
|
start: async (ctx) => {
|
|
898
786
|
const httpServer = ctx.getService?.("http.server");
|
|
899
787
|
if (!httpServer?.getRawApp) {
|
|
900
|
-
ctx.logger?.warn?.("
|
|
788
|
+
ctx.logger?.warn?.("Studio static: http.server service not found \u2014 skipping");
|
|
901
789
|
return;
|
|
902
790
|
}
|
|
903
791
|
const app = httpServer.getRawApp();
|
|
904
792
|
const absoluteDist = path6.resolve(distPath);
|
|
905
793
|
const indexPath = path6.join(absoluteDist, "index.html");
|
|
906
794
|
if (!fs6.existsSync(indexPath)) {
|
|
907
|
-
ctx.logger?.warn?.(`
|
|
795
|
+
ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
|
|
908
796
|
return;
|
|
909
797
|
}
|
|
798
|
+
const rawHtml = fs6.readFileSync(indexPath, "utf-8");
|
|
799
|
+
const rewrittenHtml = rawHtml.replace(
|
|
800
|
+
/(\s(?:href|src))="\/(?!\/)/g,
|
|
801
|
+
`$1="${STUDIO_PATH}/`
|
|
802
|
+
);
|
|
803
|
+
if (options?.isDev) {
|
|
804
|
+
app.get("/", (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
805
|
+
}
|
|
910
806
|
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
911
807
|
app.get(`${STUDIO_PATH}/*`, async (c) => {
|
|
912
808
|
const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
|
|
@@ -920,8 +816,7 @@ function createConsoleStaticPlugin(distPath) {
|
|
|
920
816
|
headers: { "content-type": mimeType(filePath) }
|
|
921
817
|
});
|
|
922
818
|
}
|
|
923
|
-
|
|
924
|
-
return new Response(html, {
|
|
819
|
+
return new Response(rewrittenHtml, {
|
|
925
820
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
926
821
|
});
|
|
927
822
|
});
|
|
@@ -954,7 +849,7 @@ function mimeType(filePath) {
|
|
|
954
849
|
var getAvailablePort = async (startPort) => {
|
|
955
850
|
const isPortAvailable = (port2) => {
|
|
956
851
|
return new Promise((resolve) => {
|
|
957
|
-
const server =
|
|
852
|
+
const server = net.createServer();
|
|
958
853
|
server.once("error", (err) => {
|
|
959
854
|
resolve(false);
|
|
960
855
|
});
|
|
@@ -973,7 +868,7 @@ var getAvailablePort = async (startPort) => {
|
|
|
973
868
|
}
|
|
974
869
|
return port;
|
|
975
870
|
};
|
|
976
|
-
var serveCommand = new Command5("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable
|
|
871
|
+
var serveCommand = new Command5("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable Studio UI at /_studio/ (default: true in dev mode)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
|
|
977
872
|
let port = parseInt(options.port);
|
|
978
873
|
try {
|
|
979
874
|
const availablePort = await getAvailablePort(port);
|
|
@@ -1101,29 +996,30 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1101
996
|
} catch (e) {
|
|
1102
997
|
console.warn(chalk7.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1103
998
|
}
|
|
999
|
+
try {
|
|
1000
|
+
const { createRestApiPlugin } = await import("@objectstack/rest");
|
|
1001
|
+
await kernel.use(createRestApiPlugin());
|
|
1002
|
+
trackPlugin("RestAPI");
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
}
|
|
1005
|
+
try {
|
|
1006
|
+
const { createDispatcherPlugin } = await import("@objectstack/runtime");
|
|
1007
|
+
await kernel.use(createDispatcherPlugin());
|
|
1008
|
+
trackPlugin("Dispatcher");
|
|
1009
|
+
} catch (e) {
|
|
1010
|
+
}
|
|
1104
1011
|
}
|
|
1105
|
-
|
|
1106
|
-
if (
|
|
1107
|
-
const
|
|
1108
|
-
if (!
|
|
1109
|
-
console.warn(chalk7.yellow(` \u26A0 @objectstack/
|
|
1110
|
-
} else if (
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
await kernel.use(createConsoleProxyPlugin(result.port));
|
|
1115
|
-
trackPlugin("ConsoleUI");
|
|
1116
|
-
} catch (e) {
|
|
1117
|
-
console.warn(chalk7.yellow(` \u26A0 Console UI failed to start: ${e.message}`));
|
|
1118
|
-
}
|
|
1012
|
+
const enableUI = options.ui ?? isDev;
|
|
1013
|
+
if (enableUI) {
|
|
1014
|
+
const studioPath = resolveStudioPath();
|
|
1015
|
+
if (!studioPath) {
|
|
1016
|
+
console.warn(chalk7.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1017
|
+
} else if (hasStudioDist(studioPath)) {
|
|
1018
|
+
const distPath = path7.join(studioPath, "dist");
|
|
1019
|
+
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
1020
|
+
trackPlugin("StudioUI");
|
|
1119
1021
|
} else {
|
|
1120
|
-
|
|
1121
|
-
if (hasConsoleDist(consolePath)) {
|
|
1122
|
-
await kernel.use(createConsoleStaticPlugin(distPath));
|
|
1123
|
-
trackPlugin("ConsoleUI");
|
|
1124
|
-
} else {
|
|
1125
|
-
console.warn(chalk7.yellow(` \u26A0 Console dist not found \u2014 run "pnpm --filter @objectstack/console build" first`));
|
|
1126
|
-
}
|
|
1022
|
+
console.warn(chalk7.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
|
|
1127
1023
|
}
|
|
1128
1024
|
}
|
|
1129
1025
|
await runtime.start();
|
|
@@ -1135,17 +1031,13 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1135
1031
|
isDev,
|
|
1136
1032
|
pluginCount: loadedPlugins.length,
|
|
1137
1033
|
pluginNames: loadedPlugins,
|
|
1138
|
-
uiEnabled:
|
|
1034
|
+
uiEnabled: enableUI,
|
|
1139
1035
|
studioPath: STUDIO_PATH
|
|
1140
1036
|
});
|
|
1141
1037
|
process.on("SIGINT", async () => {
|
|
1142
1038
|
console.warn(chalk7.yellow(`
|
|
1143
1039
|
|
|
1144
1040
|
\u23F9 Stopping server...`));
|
|
1145
|
-
if (viteProcess) {
|
|
1146
|
-
viteProcess.kill();
|
|
1147
|
-
viteProcess = null;
|
|
1148
|
-
}
|
|
1149
1041
|
await runtime.getKernel().shutdown();
|
|
1150
1042
|
console.log(chalk7.green(`\u2705 Server stopped`));
|
|
1151
1043
|
process.exit(0);
|
|
@@ -1161,8 +1053,8 @@ var serveCommand = new Command5("serve").description("Start ObjectStack server w
|
|
|
1161
1053
|
|
|
1162
1054
|
// src/commands/studio.ts
|
|
1163
1055
|
import { Command as Command6 } from "commander";
|
|
1164
|
-
import { spawn as
|
|
1165
|
-
var studioCommand = new Command6("studio").description("Launch
|
|
1056
|
+
import { spawn as spawn2 } from "child_process";
|
|
1057
|
+
var studioCommand = new Command6("studio").description("Launch Studio UI with development server").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").action(async (configPath, options) => {
|
|
1166
1058
|
printHeader("Studio");
|
|
1167
1059
|
printKV("Mode", "dev + ui", "\u{1F3A8}");
|
|
1168
1060
|
printStep("Delegating to serve --dev --ui \u2026");
|
|
@@ -1177,7 +1069,7 @@ var studioCommand = new Command6("studio").description("Launch Console UI with d
|
|
|
1177
1069
|
"--port",
|
|
1178
1070
|
options.port
|
|
1179
1071
|
];
|
|
1180
|
-
const child =
|
|
1072
|
+
const child = spawn2(process.execPath, args, {
|
|
1181
1073
|
stdio: "inherit",
|
|
1182
1074
|
env: { ...process.env, NODE_ENV: "development" }
|
|
1183
1075
|
});
|
|
@@ -1979,7 +1871,7 @@ ${chalk13.bold("Workflow:")}
|
|
|
1979
1871
|
${chalk13.dim("$")} os generate object task ${chalk13.dim("# Add metadata")}
|
|
1980
1872
|
${chalk13.dim("$")} os validate ${chalk13.dim("# Check configuration")}
|
|
1981
1873
|
${chalk13.dim("$")} os dev ${chalk13.dim("# Start dev server")}
|
|
1982
|
-
${chalk13.dim("$")} os studio ${chalk13.dim("# Dev server +
|
|
1874
|
+
${chalk13.dim("$")} os studio ${chalk13.dim("# Dev server + Studio UI")}
|
|
1983
1875
|
${chalk13.dim("$")} os compile ${chalk13.dim("# Build for production")}
|
|
1984
1876
|
|
|
1985
1877
|
${chalk13.dim("Aliases: objectstack | os")}
|
package/dist/index.js
CHANGED
|
@@ -126,7 +126,7 @@ function printServerReady(opts) {
|
|
|
126
126
|
console.log("");
|
|
127
127
|
console.log(chalk.cyan(" \u279C") + chalk.bold(" API: ") + chalk.cyan(base + "/"));
|
|
128
128
|
if (opts.uiEnabled && opts.studioPath) {
|
|
129
|
-
console.log(chalk.cyan(" \u279C") + chalk.bold("
|
|
129
|
+
console.log(chalk.cyan(" \u279C") + chalk.bold(" Studio: ") + chalk.cyan(base + opts.studioPath + "/"));
|
|
130
130
|
}
|
|
131
131
|
console.log("");
|
|
132
132
|
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
@@ -1251,7 +1251,7 @@ import chalk9 from "chalk";
|
|
|
1251
1251
|
import { execSync, spawn } from "child_process";
|
|
1252
1252
|
import fs6 from "fs";
|
|
1253
1253
|
import path6 from "path";
|
|
1254
|
-
var devCommand = new Command7("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable
|
|
1254
|
+
var devCommand = new Command7("dev").description("Start development mode with hot-reload").argument("[package]", "Package name or filter pattern", "all").option("-w, --watch", "Enable watch mode (default)", true).option("--ui", "Enable Studio UI at /_studio/").option("-v, --verbose", "Verbose output").action(async (packageName, options) => {
|
|
1255
1255
|
printHeader("Development Mode");
|
|
1256
1256
|
const configPath = path6.resolve(process.cwd(), "objectstack.config.ts");
|
|
1257
1257
|
if (packageName === "all" && fs6.existsSync(configPath)) {
|
|
@@ -1293,30 +1293,27 @@ var devCommand = new Command7("dev").description("Start development mode with ho
|
|
|
1293
1293
|
import { Command as Command8 } from "commander";
|
|
1294
1294
|
import path8 from "path";
|
|
1295
1295
|
import fs8 from "fs";
|
|
1296
|
-
import
|
|
1296
|
+
import net from "net";
|
|
1297
1297
|
import chalk10 from "chalk";
|
|
1298
1298
|
import { bundleRequire as bundleRequire2 } from "bundle-require";
|
|
1299
1299
|
|
|
1300
|
-
// src/utils/
|
|
1300
|
+
// src/utils/studio.ts
|
|
1301
1301
|
import path7 from "path";
|
|
1302
1302
|
import fs7 from "fs";
|
|
1303
|
-
import net from "net";
|
|
1304
|
-
import { spawn as spawn2 } from "child_process";
|
|
1305
1303
|
var STUDIO_PATH = "/_studio";
|
|
1306
|
-
|
|
1307
|
-
function resolveConsolePath() {
|
|
1304
|
+
function resolveStudioPath() {
|
|
1308
1305
|
const cwd = process.cwd();
|
|
1309
1306
|
const candidates = [
|
|
1310
|
-
path7.resolve(cwd, "apps/
|
|
1311
|
-
path7.resolve(cwd, "../../apps/
|
|
1312
|
-
path7.resolve(cwd, "../apps/
|
|
1307
|
+
path7.resolve(cwd, "apps/studio"),
|
|
1308
|
+
path7.resolve(cwd, "../../apps/studio"),
|
|
1309
|
+
path7.resolve(cwd, "../apps/studio")
|
|
1313
1310
|
];
|
|
1314
1311
|
for (const candidate of candidates) {
|
|
1315
1312
|
const pkgPath = path7.join(candidate, "package.json");
|
|
1316
1313
|
if (fs7.existsSync(pkgPath)) {
|
|
1317
1314
|
try {
|
|
1318
1315
|
const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
|
|
1319
|
-
if (pkg.name === "@objectstack/
|
|
1316
|
+
if (pkg.name === "@objectstack/studio") return candidate;
|
|
1320
1317
|
} catch {
|
|
1321
1318
|
}
|
|
1322
1319
|
}
|
|
@@ -1324,142 +1321,41 @@ function resolveConsolePath() {
|
|
|
1324
1321
|
try {
|
|
1325
1322
|
const { createRequire } = __require("module");
|
|
1326
1323
|
const req = createRequire(import.meta.url);
|
|
1327
|
-
const resolved = req.resolve("@objectstack/
|
|
1324
|
+
const resolved = req.resolve("@objectstack/studio/package.json");
|
|
1328
1325
|
return path7.dirname(resolved);
|
|
1329
1326
|
} catch {
|
|
1330
1327
|
return null;
|
|
1331
1328
|
}
|
|
1332
1329
|
}
|
|
1333
|
-
function
|
|
1334
|
-
return fs7.existsSync(path7.join(
|
|
1335
|
-
}
|
|
1336
|
-
function findAvailablePort(start = VITE_PORT_START) {
|
|
1337
|
-
return new Promise((resolve, reject) => {
|
|
1338
|
-
const server = net.createServer();
|
|
1339
|
-
server.once("error", () => {
|
|
1340
|
-
findAvailablePort(start + 1).then(resolve, reject);
|
|
1341
|
-
});
|
|
1342
|
-
server.once("listening", () => {
|
|
1343
|
-
server.close(() => resolve(start));
|
|
1344
|
-
});
|
|
1345
|
-
server.listen(start);
|
|
1346
|
-
});
|
|
1347
|
-
}
|
|
1348
|
-
async function spawnViteDevServer(consolePath, options = {}) {
|
|
1349
|
-
const vitePort = await findAvailablePort(VITE_PORT_START);
|
|
1350
|
-
const viteBinCandidates = [
|
|
1351
|
-
path7.join(consolePath, "node_modules", ".bin", "vite"),
|
|
1352
|
-
path7.join(consolePath, "..", "..", "node_modules", ".bin", "vite")
|
|
1353
|
-
];
|
|
1354
|
-
let viteBin = null;
|
|
1355
|
-
for (const candidate of viteBinCandidates) {
|
|
1356
|
-
if (fs7.existsSync(candidate)) {
|
|
1357
|
-
viteBin = candidate;
|
|
1358
|
-
break;
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
const command = viteBin || "npx";
|
|
1362
|
-
const args = viteBin ? ["--port", String(vitePort), "--strictPort"] : ["vite", "--port", String(vitePort), "--strictPort"];
|
|
1363
|
-
const child = spawn2(command, args, {
|
|
1364
|
-
cwd: consolePath,
|
|
1365
|
-
env: {
|
|
1366
|
-
...process.env,
|
|
1367
|
-
VITE_BASE: `${STUDIO_PATH}/`,
|
|
1368
|
-
VITE_PORT: String(vitePort),
|
|
1369
|
-
VITE_HMR_PORT: String(vitePort),
|
|
1370
|
-
VITE_RUNTIME_MODE: "server",
|
|
1371
|
-
VITE_SERVER_URL: "",
|
|
1372
|
-
// Same-origin API
|
|
1373
|
-
NODE_ENV: "development"
|
|
1374
|
-
},
|
|
1375
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
1376
|
-
});
|
|
1377
|
-
let stderr = "";
|
|
1378
|
-
child.stderr?.on("data", (data) => {
|
|
1379
|
-
stderr += data.toString();
|
|
1380
|
-
});
|
|
1381
|
-
await new Promise((resolve, reject) => {
|
|
1382
|
-
const timeout = setTimeout(() => {
|
|
1383
|
-
child.kill();
|
|
1384
|
-
reject(new Error(`Vite dev server timed out after 30 s.
|
|
1385
|
-
${stderr}`));
|
|
1386
|
-
}, 3e4);
|
|
1387
|
-
child.stdout?.on("data", (data) => {
|
|
1388
|
-
const output = data.toString();
|
|
1389
|
-
if (output.includes("Local:") || output.includes("ready in")) {
|
|
1390
|
-
clearTimeout(timeout);
|
|
1391
|
-
resolve();
|
|
1392
|
-
}
|
|
1393
|
-
});
|
|
1394
|
-
child.on("error", (err) => {
|
|
1395
|
-
clearTimeout(timeout);
|
|
1396
|
-
reject(err);
|
|
1397
|
-
});
|
|
1398
|
-
child.on("exit", (code) => {
|
|
1399
|
-
if (code !== 0 && code !== null) {
|
|
1400
|
-
clearTimeout(timeout);
|
|
1401
|
-
reject(new Error(`Vite exited with code ${code}.
|
|
1402
|
-
${stderr}`));
|
|
1403
|
-
}
|
|
1404
|
-
});
|
|
1405
|
-
});
|
|
1406
|
-
return { port: vitePort, process: child };
|
|
1407
|
-
}
|
|
1408
|
-
function createConsoleProxyPlugin(vitePort) {
|
|
1409
|
-
return {
|
|
1410
|
-
name: "com.objectstack.console-proxy",
|
|
1411
|
-
init: async () => {
|
|
1412
|
-
},
|
|
1413
|
-
start: async (ctx) => {
|
|
1414
|
-
const httpServer = ctx.getService?.("http.server");
|
|
1415
|
-
if (!httpServer?.getRawApp) {
|
|
1416
|
-
ctx.logger?.warn?.("Console proxy: http.server service not found \u2014 skipping");
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
const app = httpServer.getRawApp();
|
|
1420
|
-
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
1421
|
-
app.all(`${STUDIO_PATH}/*`, async (c) => {
|
|
1422
|
-
const targetUrl = `http://localhost:${vitePort}${c.req.path}`;
|
|
1423
|
-
try {
|
|
1424
|
-
const headers = new Headers(c.req.raw.headers);
|
|
1425
|
-
headers.delete("host");
|
|
1426
|
-
const isBodyAllowed = !["GET", "HEAD"].includes(c.req.method);
|
|
1427
|
-
const resp = await fetch(targetUrl, {
|
|
1428
|
-
method: c.req.method,
|
|
1429
|
-
headers,
|
|
1430
|
-
body: isBodyAllowed ? c.req.raw.body : void 0,
|
|
1431
|
-
// @ts-expect-error — duplex required for streaming request body
|
|
1432
|
-
duplex: isBodyAllowed ? "half" : void 0
|
|
1433
|
-
});
|
|
1434
|
-
return new Response(resp.body, {
|
|
1435
|
-
status: resp.status,
|
|
1436
|
-
headers: resp.headers
|
|
1437
|
-
});
|
|
1438
|
-
} catch {
|
|
1439
|
-
return c.text("Console dev server is starting\u2026", 502);
|
|
1440
|
-
}
|
|
1441
|
-
});
|
|
1442
|
-
}
|
|
1443
|
-
};
|
|
1330
|
+
function hasStudioDist(studioPath) {
|
|
1331
|
+
return fs7.existsSync(path7.join(studioPath, "dist", "index.html"));
|
|
1444
1332
|
}
|
|
1445
|
-
function
|
|
1333
|
+
function createStudioStaticPlugin(distPath, options) {
|
|
1446
1334
|
return {
|
|
1447
|
-
name: "com.objectstack.
|
|
1335
|
+
name: "com.objectstack.studio-static",
|
|
1448
1336
|
init: async () => {
|
|
1449
1337
|
},
|
|
1450
1338
|
start: async (ctx) => {
|
|
1451
1339
|
const httpServer = ctx.getService?.("http.server");
|
|
1452
1340
|
if (!httpServer?.getRawApp) {
|
|
1453
|
-
ctx.logger?.warn?.("
|
|
1341
|
+
ctx.logger?.warn?.("Studio static: http.server service not found \u2014 skipping");
|
|
1454
1342
|
return;
|
|
1455
1343
|
}
|
|
1456
1344
|
const app = httpServer.getRawApp();
|
|
1457
1345
|
const absoluteDist = path7.resolve(distPath);
|
|
1458
1346
|
const indexPath = path7.join(absoluteDist, "index.html");
|
|
1459
1347
|
if (!fs7.existsSync(indexPath)) {
|
|
1460
|
-
ctx.logger?.warn?.(`
|
|
1348
|
+
ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
|
|
1461
1349
|
return;
|
|
1462
1350
|
}
|
|
1351
|
+
const rawHtml = fs7.readFileSync(indexPath, "utf-8");
|
|
1352
|
+
const rewrittenHtml = rawHtml.replace(
|
|
1353
|
+
/(\s(?:href|src))="\/(?!\/)/g,
|
|
1354
|
+
`$1="${STUDIO_PATH}/`
|
|
1355
|
+
);
|
|
1356
|
+
if (options?.isDev) {
|
|
1357
|
+
app.get("/", (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
1358
|
+
}
|
|
1463
1359
|
app.get(STUDIO_PATH, (c) => c.redirect(`${STUDIO_PATH}/`));
|
|
1464
1360
|
app.get(`${STUDIO_PATH}/*`, async (c) => {
|
|
1465
1361
|
const reqPath = c.req.path.substring(STUDIO_PATH.length) || "/";
|
|
@@ -1473,8 +1369,7 @@ function createConsoleStaticPlugin(distPath) {
|
|
|
1473
1369
|
headers: { "content-type": mimeType(filePath) }
|
|
1474
1370
|
});
|
|
1475
1371
|
}
|
|
1476
|
-
|
|
1477
|
-
return new Response(html, {
|
|
1372
|
+
return new Response(rewrittenHtml, {
|
|
1478
1373
|
headers: { "content-type": "text/html; charset=utf-8" }
|
|
1479
1374
|
});
|
|
1480
1375
|
});
|
|
@@ -1507,7 +1402,7 @@ function mimeType(filePath) {
|
|
|
1507
1402
|
var getAvailablePort = async (startPort) => {
|
|
1508
1403
|
const isPortAvailable = (port2) => {
|
|
1509
1404
|
return new Promise((resolve) => {
|
|
1510
|
-
const server =
|
|
1405
|
+
const server = net.createServer();
|
|
1511
1406
|
server.once("error", (err) => {
|
|
1512
1407
|
resolve(false);
|
|
1513
1408
|
});
|
|
@@ -1526,7 +1421,7 @@ var getAvailablePort = async (startPort) => {
|
|
|
1526
1421
|
}
|
|
1527
1422
|
return port;
|
|
1528
1423
|
};
|
|
1529
|
-
var serveCommand = new Command8("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable
|
|
1424
|
+
var serveCommand = new Command8("serve").description("Start ObjectStack server with plugins from configuration").argument("[config]", "Configuration file path", "objectstack.config.ts").option("-p, --port <port>", "Server port", "3000").option("--dev", "Run in development mode (load devPlugins)").option("--ui", "Enable Studio UI at /_studio/ (default: true in dev mode)").option("--no-server", "Skip starting HTTP server plugin").action(async (configPath, options) => {
|
|
1530
1425
|
let port = parseInt(options.port);
|
|
1531
1426
|
try {
|
|
1532
1427
|
const availablePort = await getAvailablePort(port);
|
|
@@ -1654,29 +1549,30 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1654
1549
|
} catch (e) {
|
|
1655
1550
|
console.warn(chalk10.yellow(` \u26A0 HTTP server plugin not available: ${e.message}`));
|
|
1656
1551
|
}
|
|
1552
|
+
try {
|
|
1553
|
+
const { createRestApiPlugin } = await import("@objectstack/rest");
|
|
1554
|
+
await kernel.use(createRestApiPlugin());
|
|
1555
|
+
trackPlugin("RestAPI");
|
|
1556
|
+
} catch (e) {
|
|
1557
|
+
}
|
|
1558
|
+
try {
|
|
1559
|
+
const { createDispatcherPlugin } = await import("@objectstack/runtime");
|
|
1560
|
+
await kernel.use(createDispatcherPlugin());
|
|
1561
|
+
trackPlugin("Dispatcher");
|
|
1562
|
+
} catch (e) {
|
|
1563
|
+
}
|
|
1657
1564
|
}
|
|
1658
|
-
|
|
1659
|
-
if (
|
|
1660
|
-
const
|
|
1661
|
-
if (!
|
|
1662
|
-
console.warn(chalk10.yellow(` \u26A0 @objectstack/
|
|
1663
|
-
} else if (
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
await kernel.use(createConsoleProxyPlugin(result.port));
|
|
1668
|
-
trackPlugin("ConsoleUI");
|
|
1669
|
-
} catch (e) {
|
|
1670
|
-
console.warn(chalk10.yellow(` \u26A0 Console UI failed to start: ${e.message}`));
|
|
1671
|
-
}
|
|
1565
|
+
const enableUI = options.ui ?? isDev;
|
|
1566
|
+
if (enableUI) {
|
|
1567
|
+
const studioPath = resolveStudioPath();
|
|
1568
|
+
if (!studioPath) {
|
|
1569
|
+
console.warn(chalk10.yellow(` \u26A0 @objectstack/studio not found \u2014 skipping UI`));
|
|
1570
|
+
} else if (hasStudioDist(studioPath)) {
|
|
1571
|
+
const distPath = path8.join(studioPath, "dist");
|
|
1572
|
+
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
1573
|
+
trackPlugin("StudioUI");
|
|
1672
1574
|
} else {
|
|
1673
|
-
|
|
1674
|
-
if (hasConsoleDist(consolePath)) {
|
|
1675
|
-
await kernel.use(createConsoleStaticPlugin(distPath));
|
|
1676
|
-
trackPlugin("ConsoleUI");
|
|
1677
|
-
} else {
|
|
1678
|
-
console.warn(chalk10.yellow(` \u26A0 Console dist not found \u2014 run "pnpm --filter @objectstack/console build" first`));
|
|
1679
|
-
}
|
|
1575
|
+
console.warn(chalk10.yellow(` \u26A0 Studio dist not found \u2014 run "pnpm --filter @objectstack/studio build" first`));
|
|
1680
1576
|
}
|
|
1681
1577
|
}
|
|
1682
1578
|
await runtime.start();
|
|
@@ -1688,17 +1584,13 @@ var serveCommand = new Command8("serve").description("Start ObjectStack server w
|
|
|
1688
1584
|
isDev,
|
|
1689
1585
|
pluginCount: loadedPlugins.length,
|
|
1690
1586
|
pluginNames: loadedPlugins,
|
|
1691
|
-
uiEnabled:
|
|
1587
|
+
uiEnabled: enableUI,
|
|
1692
1588
|
studioPath: STUDIO_PATH
|
|
1693
1589
|
});
|
|
1694
1590
|
process.on("SIGINT", async () => {
|
|
1695
1591
|
console.warn(chalk10.yellow(`
|
|
1696
1592
|
|
|
1697
1593
|
\u23F9 Stopping server...`));
|
|
1698
|
-
if (viteProcess) {
|
|
1699
|
-
viteProcess.kill();
|
|
1700
|
-
viteProcess = null;
|
|
1701
|
-
}
|
|
1702
1594
|
await runtime.getKernel().shutdown();
|
|
1703
1595
|
console.log(chalk10.green(`\u2705 Server stopped`));
|
|
1704
1596
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Command Line Interface for ObjectStack Protocol",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -22,15 +22,16 @@
|
|
|
22
22
|
"commander": "^14.0.3",
|
|
23
23
|
"tsx": "^4.7.1",
|
|
24
24
|
"zod": "^3.24.1",
|
|
25
|
-
"@objectstack/core": "
|
|
26
|
-
"@objectstack/driver-memory": "^
|
|
27
|
-
"@objectstack/objectql": "^
|
|
28
|
-
"@objectstack/plugin-hono-server": "
|
|
29
|
-
"@objectstack/
|
|
30
|
-
"@objectstack/
|
|
25
|
+
"@objectstack/core": "2.0.0",
|
|
26
|
+
"@objectstack/driver-memory": "^2.0.0",
|
|
27
|
+
"@objectstack/objectql": "^2.0.0",
|
|
28
|
+
"@objectstack/plugin-hono-server": "2.0.0",
|
|
29
|
+
"@objectstack/rest": "2.0.0",
|
|
30
|
+
"@objectstack/runtime": "^2.0.0",
|
|
31
|
+
"@objectstack/spec": "2.0.0"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
|
-
"@objectstack/core": "
|
|
34
|
+
"@objectstack/core": "2.0.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/node": "^25.1.0",
|
package/src/bin.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
import { createRequire } from 'module';
|
|
2
4
|
import { Command } from 'commander';
|
|
3
5
|
import chalk from 'chalk';
|
|
@@ -46,7 +48,7 @@ ${chalk.bold('Workflow:')}
|
|
|
46
48
|
${chalk.dim('$')} os generate object task ${chalk.dim('# Add metadata')}
|
|
47
49
|
${chalk.dim('$')} os validate ${chalk.dim('# Check configuration')}
|
|
48
50
|
${chalk.dim('$')} os dev ${chalk.dim('# Start dev server')}
|
|
49
|
-
${chalk.dim('$')} os studio ${chalk.dim('# Dev server +
|
|
51
|
+
${chalk.dim('$')} os studio ${chalk.dim('# Dev server + Studio UI')}
|
|
50
52
|
${chalk.dim('$')} os compile ${chalk.dim('# Build for production')}
|
|
51
53
|
|
|
52
54
|
${chalk.dim('Aliases: objectstack | os')}
|
package/src/commands/compile.ts
CHANGED
package/src/commands/create.ts
CHANGED
package/src/commands/dev.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
import { Command } from 'commander';
|
|
2
4
|
import chalk from 'chalk';
|
|
3
5
|
import { execSync, spawn } from 'child_process';
|
|
@@ -9,7 +11,7 @@ export const devCommand = new Command('dev')
|
|
|
9
11
|
.description('Start development mode with hot-reload')
|
|
10
12
|
.argument('[package]', 'Package name or filter pattern', 'all')
|
|
11
13
|
.option('-w, --watch', 'Enable watch mode (default)', true)
|
|
12
|
-
.option('--ui', 'Enable
|
|
14
|
+
.option('--ui', 'Enable Studio UI at /_studio/')
|
|
13
15
|
.option('-v, --verbose', 'Verbose output')
|
|
14
16
|
.action(async (packageName, options) => {
|
|
15
17
|
printHeader('Development Mode');
|
package/src/commands/doctor.ts
CHANGED
package/src/commands/generate.ts
CHANGED
package/src/commands/info.ts
CHANGED
package/src/commands/init.ts
CHANGED
package/src/commands/serve.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
import { Command } from 'commander';
|
|
2
4
|
import path from 'path';
|
|
3
5
|
import fs from 'fs';
|
|
@@ -16,12 +18,10 @@ import {
|
|
|
16
18
|
} from '../utils/format.js';
|
|
17
19
|
import {
|
|
18
20
|
STUDIO_PATH,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
createConsoleStaticPlugin,
|
|
24
|
-
} from '../utils/console.js';
|
|
21
|
+
resolveStudioPath,
|
|
22
|
+
hasStudioDist,
|
|
23
|
+
createStudioStaticPlugin,
|
|
24
|
+
} from '../utils/studio.js';
|
|
25
25
|
|
|
26
26
|
// Helper to find available port
|
|
27
27
|
const getAvailablePort = async (startPort: number): Promise<number> => {
|
|
@@ -53,7 +53,7 @@ export const serveCommand = new Command('serve')
|
|
|
53
53
|
.argument('[config]', 'Configuration file path', 'objectstack.config.ts')
|
|
54
54
|
.option('-p, --port <port>', 'Server port', '3000')
|
|
55
55
|
.option('--dev', 'Run in development mode (load devPlugins)')
|
|
56
|
-
.option('--ui', 'Enable
|
|
56
|
+
.option('--ui', 'Enable Studio UI at /_studio/ (default: true in dev mode)')
|
|
57
57
|
.option('--no-server', 'Skip starting HTTP server plugin')
|
|
58
58
|
.action(async (configPath, options) => {
|
|
59
59
|
let port = parseInt(options.port);
|
|
@@ -226,34 +226,41 @@ export const serveCommand = new Command('serve')
|
|
|
226
226
|
} catch (e: any) {
|
|
227
227
|
console.warn(chalk.yellow(` ⚠ HTTP server plugin not available: ${e.message}`));
|
|
228
228
|
}
|
|
229
|
-
}
|
|
230
229
|
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
// Register REST API plugin (consumes http.server + protocol services)
|
|
231
|
+
try {
|
|
232
|
+
const { createRestApiPlugin } = await import('@objectstack/rest');
|
|
233
|
+
await kernel.use(createRestApiPlugin());
|
|
234
|
+
trackPlugin('RestAPI');
|
|
235
|
+
} catch (e: any) {
|
|
236
|
+
// @objectstack/rest is optional
|
|
237
|
+
}
|
|
233
238
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
239
|
+
// Register Dispatcher plugin (auth, graphql, analytics, packages, hub, storage, automation)
|
|
240
|
+
try {
|
|
241
|
+
const { createDispatcherPlugin } = await import('@objectstack/runtime');
|
|
242
|
+
await kernel.use(createDispatcherPlugin());
|
|
243
|
+
trackPlugin('Dispatcher');
|
|
244
|
+
} catch (e: any) {
|
|
245
|
+
// optional
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// ── Studio UI ─────────────────────────────────────────────────
|
|
250
|
+
// In dev mode, Studio UI is enabled by default (use --no-ui to disable).
|
|
251
|
+
// Always serves the pre-built dist/ — no Vite dev server, no extra port.
|
|
252
|
+
const enableUI = options.ui ?? isDev;
|
|
253
|
+
|
|
254
|
+
if (enableUI) {
|
|
255
|
+
const studioPath = resolveStudioPath();
|
|
256
|
+
if (!studioPath) {
|
|
257
|
+
console.warn(chalk.yellow(` ⚠ @objectstack/studio not found — skipping UI`));
|
|
258
|
+
} else if (hasStudioDist(studioPath)) {
|
|
259
|
+
const distPath = path.join(studioPath, 'dist');
|
|
260
|
+
await kernel.use(createStudioStaticPlugin(distPath, { isDev }));
|
|
261
|
+
trackPlugin('StudioUI');
|
|
248
262
|
} else {
|
|
249
|
-
|
|
250
|
-
const distPath = path.join(consolePath, 'dist');
|
|
251
|
-
if (hasConsoleDist(consolePath)) {
|
|
252
|
-
await kernel.use(createConsoleStaticPlugin(distPath));
|
|
253
|
-
trackPlugin('ConsoleUI');
|
|
254
|
-
} else {
|
|
255
|
-
console.warn(chalk.yellow(` ⚠ Console dist not found — run "pnpm --filter @objectstack/console build" first`));
|
|
256
|
-
}
|
|
263
|
+
console.warn(chalk.yellow(` ⚠ Studio dist not found — run "pnpm --filter @objectstack/studio build" first`));
|
|
257
264
|
}
|
|
258
265
|
}
|
|
259
266
|
|
|
@@ -271,17 +278,13 @@ export const serveCommand = new Command('serve')
|
|
|
271
278
|
isDev,
|
|
272
279
|
pluginCount: loadedPlugins.length,
|
|
273
280
|
pluginNames: loadedPlugins,
|
|
274
|
-
uiEnabled:
|
|
281
|
+
uiEnabled: enableUI,
|
|
275
282
|
studioPath: STUDIO_PATH,
|
|
276
283
|
});
|
|
277
284
|
|
|
278
285
|
// Keep process alive
|
|
279
286
|
process.on('SIGINT', async () => {
|
|
280
287
|
console.warn(chalk.yellow(`\n\n⏹ Stopping server...`));
|
|
281
|
-
if (viteProcess) {
|
|
282
|
-
viteProcess.kill();
|
|
283
|
-
viteProcess = null;
|
|
284
|
-
}
|
|
285
288
|
await runtime.getKernel().shutdown();
|
|
286
289
|
console.log(chalk.green(`✅ Server stopped`));
|
|
287
290
|
process.exit(0);
|
package/src/commands/studio.ts
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
import { Command } from 'commander';
|
|
2
4
|
import chalk from 'chalk';
|
|
3
5
|
import { spawn } from 'child_process';
|
|
4
6
|
import { printHeader, printKV, printStep } from '../utils/format.js';
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
|
-
* `objectstack studio` — Launch the ObjectStack
|
|
9
|
+
* `objectstack studio` — Launch the ObjectStack Studio UI.
|
|
8
10
|
*
|
|
9
11
|
* Alias for `objectstack serve --dev --ui`.
|
|
10
|
-
* Starts the ObjectStack server in development mode with the
|
|
12
|
+
* Starts the ObjectStack server in development mode with the Studio
|
|
11
13
|
* UI available at http://localhost:<port>/_studio/
|
|
12
14
|
*/
|
|
13
15
|
export const studioCommand = new Command('studio')
|
|
14
|
-
.description('Launch
|
|
16
|
+
.description('Launch Studio UI with development server')
|
|
15
17
|
.argument('[config]', 'Configuration file path', 'objectstack.config.ts')
|
|
16
18
|
.option('-p, --port <port>', 'Server port', '3000')
|
|
17
19
|
.action(async (configPath, options) => {
|
package/src/commands/test.ts
CHANGED
package/src/commands/validate.ts
CHANGED
package/src/index.ts
CHANGED
package/src/utils/config.ts
CHANGED
package/src/utils/format.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
import chalk from 'chalk';
|
|
2
4
|
import type { ZodError } from 'zod';
|
|
3
5
|
|
|
@@ -188,7 +190,7 @@ export function printServerReady(opts: ServerReadyOptions) {
|
|
|
188
190
|
console.log('');
|
|
189
191
|
console.log(chalk.cyan(' ➜') + chalk.bold(' API: ') + chalk.cyan(base + '/'));
|
|
190
192
|
if (opts.uiEnabled && opts.studioPath) {
|
|
191
|
-
console.log(chalk.cyan(' ➜') + chalk.bold('
|
|
193
|
+
console.log(chalk.cyan(' ➜') + chalk.bold(' Studio: ') + chalk.cyan(base + opts.studioPath + '/'));
|
|
192
194
|
}
|
|
193
195
|
console.log('');
|
|
194
196
|
console.log(chalk.dim(` Config: ${opts.configFile}`));
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
4
|
+
* Studio UI Integration Utilities
|
|
3
5
|
*
|
|
4
|
-
* Handles resolving, spawning, and proxying the @objectstack/
|
|
6
|
+
* Handles resolving, spawning, and proxying the @objectstack/studio
|
|
5
7
|
* frontend when the CLI is started with --ui or via the `studio` command.
|
|
6
8
|
*/
|
|
7
9
|
import path from 'path';
|
|
@@ -21,17 +23,17 @@ const VITE_PORT_START = 24678;
|
|
|
21
23
|
// ─── Path Resolution ────────────────────────────────────────────────
|
|
22
24
|
|
|
23
25
|
/**
|
|
24
|
-
* Resolve the filesystem path to the @objectstack/
|
|
26
|
+
* Resolve the filesystem path to the @objectstack/studio package.
|
|
25
27
|
* Searches workspace locations first, then falls back to node_modules.
|
|
26
28
|
*/
|
|
27
|
-
export function
|
|
29
|
+
export function resolveStudioPath(): string | null {
|
|
28
30
|
const cwd = process.cwd();
|
|
29
31
|
|
|
30
32
|
// Workspace candidates (monorepo layouts)
|
|
31
33
|
const candidates = [
|
|
32
|
-
path.resolve(cwd, 'apps/
|
|
33
|
-
path.resolve(cwd, '../../apps/
|
|
34
|
-
path.resolve(cwd, '../apps/
|
|
34
|
+
path.resolve(cwd, 'apps/studio'),
|
|
35
|
+
path.resolve(cwd, '../../apps/studio'),
|
|
36
|
+
path.resolve(cwd, '../apps/studio'),
|
|
35
37
|
];
|
|
36
38
|
|
|
37
39
|
for (const candidate of candidates) {
|
|
@@ -39,7 +41,7 @@ export function resolveConsolePath(): string | null {
|
|
|
39
41
|
if (fs.existsSync(pkgPath)) {
|
|
40
42
|
try {
|
|
41
43
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
42
|
-
if (pkg.name === '@objectstack/
|
|
44
|
+
if (pkg.name === '@objectstack/studio') return candidate;
|
|
43
45
|
} catch {
|
|
44
46
|
// Skip invalid package.json
|
|
45
47
|
}
|
|
@@ -50,7 +52,7 @@ export function resolveConsolePath(): string | null {
|
|
|
50
52
|
try {
|
|
51
53
|
const { createRequire } = require('module');
|
|
52
54
|
const req = createRequire(import.meta.url);
|
|
53
|
-
const resolved = req.resolve('@objectstack/
|
|
55
|
+
const resolved = req.resolve('@objectstack/studio/package.json');
|
|
54
56
|
return path.dirname(resolved);
|
|
55
57
|
} catch {
|
|
56
58
|
return null;
|
|
@@ -58,10 +60,10 @@ export function resolveConsolePath(): string | null {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
/**
|
|
61
|
-
* Check whether the
|
|
63
|
+
* Check whether the Studio has a pre-built `dist/` directory.
|
|
62
64
|
*/
|
|
63
|
-
export function
|
|
64
|
-
return fs.existsSync(path.join(
|
|
65
|
+
export function hasStudioDist(studioPath: string): boolean {
|
|
66
|
+
return fs.existsSync(path.join(studioPath, 'dist', 'index.html'));
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
// ─── Port Utilities ─────────────────────────────────────────────────
|
|
@@ -98,19 +100,19 @@ export interface ViteDevResult {
|
|
|
98
100
|
* Sets environment variables so the Console runs in server mode and
|
|
99
101
|
* connects to the ObjectStack API on the same origin.
|
|
100
102
|
*
|
|
101
|
-
* @param
|
|
103
|
+
* @param studioPath - Absolute path to the @objectstack/studio package
|
|
102
104
|
* @param options.serverPort - The main ObjectStack server port (for display only)
|
|
103
105
|
*/
|
|
104
106
|
export async function spawnViteDevServer(
|
|
105
|
-
|
|
107
|
+
studioPath: string,
|
|
106
108
|
options: { serverPort?: number } = {},
|
|
107
109
|
): Promise<ViteDevResult> {
|
|
108
110
|
const vitePort = await findAvailablePort(VITE_PORT_START);
|
|
109
111
|
|
|
110
|
-
// Resolve the Vite binary from the
|
|
112
|
+
// Resolve the Vite binary from the Studio's own dependencies
|
|
111
113
|
const viteBinCandidates = [
|
|
112
|
-
path.join(
|
|
113
|
-
path.join(
|
|
114
|
+
path.join(studioPath, 'node_modules', '.bin', 'vite'),
|
|
115
|
+
path.join(studioPath, '..', '..', 'node_modules', '.bin', 'vite'),
|
|
114
116
|
];
|
|
115
117
|
|
|
116
118
|
let viteBin: string | null = null;
|
|
@@ -127,7 +129,7 @@ export async function spawnViteDevServer(
|
|
|
127
129
|
: ['vite', '--port', String(vitePort), '--strictPort'];
|
|
128
130
|
|
|
129
131
|
const child = spawn(command, args, {
|
|
130
|
-
cwd:
|
|
132
|
+
cwd: studioPath,
|
|
131
133
|
env: {
|
|
132
134
|
...process.env,
|
|
133
135
|
VITE_BASE: `${STUDIO_PATH}/`,
|
|
@@ -184,16 +186,16 @@ export async function spawnViteDevServer(
|
|
|
184
186
|
* Create a lightweight kernel plugin that proxies `/_studio/*` requests
|
|
185
187
|
* to the Vite dev server. Used in development mode.
|
|
186
188
|
*/
|
|
187
|
-
export function
|
|
189
|
+
export function createStudioProxyPlugin(vitePort: number) {
|
|
188
190
|
return {
|
|
189
|
-
name: 'com.objectstack.
|
|
191
|
+
name: 'com.objectstack.studio-proxy',
|
|
190
192
|
|
|
191
193
|
init: async () => {},
|
|
192
194
|
|
|
193
195
|
start: async (ctx: any) => {
|
|
194
196
|
const httpServer = ctx.getService?.('http.server');
|
|
195
197
|
if (!httpServer?.getRawApp) {
|
|
196
|
-
ctx.logger?.warn?.('
|
|
198
|
+
ctx.logger?.warn?.('Studio proxy: http.server service not found — skipping');
|
|
197
199
|
return;
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -234,22 +236,22 @@ export function createConsoleProxyPlugin(vitePort: number) {
|
|
|
234
236
|
}
|
|
235
237
|
|
|
236
238
|
/**
|
|
237
|
-
* Create a lightweight kernel plugin that serves the pre-built
|
|
239
|
+
* Create a lightweight kernel plugin that serves the pre-built Studio
|
|
238
240
|
* static files at `/_studio/*`. Used in production mode.
|
|
239
241
|
*
|
|
240
242
|
* Uses Node.js built-in fs for static file serving to avoid external
|
|
241
243
|
* bundling dependencies.
|
|
242
244
|
*/
|
|
243
|
-
export function
|
|
245
|
+
export function createStudioStaticPlugin(distPath: string, options?: { isDev?: boolean }) {
|
|
244
246
|
return {
|
|
245
|
-
name: 'com.objectstack.
|
|
247
|
+
name: 'com.objectstack.studio-static',
|
|
246
248
|
|
|
247
249
|
init: async () => {},
|
|
248
250
|
|
|
249
251
|
start: async (ctx: any) => {
|
|
250
252
|
const httpServer = ctx.getService?.('http.server');
|
|
251
253
|
if (!httpServer?.getRawApp) {
|
|
252
|
-
ctx.logger?.warn?.('
|
|
254
|
+
ctx.logger?.warn?.('Studio static: http.server service not found — skipping');
|
|
253
255
|
return;
|
|
254
256
|
}
|
|
255
257
|
|
|
@@ -258,10 +260,23 @@ export function createConsoleStaticPlugin(distPath: string) {
|
|
|
258
260
|
|
|
259
261
|
const indexPath = path.join(absoluteDist, 'index.html');
|
|
260
262
|
if (!fs.existsSync(indexPath)) {
|
|
261
|
-
ctx.logger?.warn?.(`
|
|
263
|
+
ctx.logger?.warn?.(`Studio static: dist not found at ${absoluteDist}`);
|
|
262
264
|
return;
|
|
263
265
|
}
|
|
264
266
|
|
|
267
|
+
// Read and rewrite index.html so asset paths respect the mount path.
|
|
268
|
+
// The dist may have been built with base '/' (absolute paths like
|
|
269
|
+
// /assets/...) which won't resolve when mounted under /_studio/.
|
|
270
|
+
const rawHtml = fs.readFileSync(indexPath, 'utf-8');
|
|
271
|
+
const rewrittenHtml = rawHtml.replace(
|
|
272
|
+
/(\s(?:href|src))="\/(?!\/)/g,
|
|
273
|
+
`$1="${STUDIO_PATH}/`,
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// In dev mode, redirect root to Studio for convenience
|
|
277
|
+
if (options?.isDev) {
|
|
278
|
+
app.get('/', (c: any) => c.redirect(`${STUDIO_PATH}/`));
|
|
279
|
+
}
|
|
265
280
|
// Redirect bare path
|
|
266
281
|
app.get(STUDIO_PATH, (c: any) => c.redirect(`${STUDIO_PATH}/`));
|
|
267
282
|
|
|
@@ -283,9 +298,8 @@ export function createConsoleStaticPlugin(distPath: string) {
|
|
|
283
298
|
});
|
|
284
299
|
}
|
|
285
300
|
|
|
286
|
-
// SPA fallback: serve index.html for non-file routes
|
|
287
|
-
|
|
288
|
-
return new Response(html, {
|
|
301
|
+
// SPA fallback: serve rewritten index.html for non-file routes
|
|
302
|
+
return new Response(rewrittenHtml, {
|
|
289
303
|
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
290
304
|
});
|
|
291
305
|
});
|