Skip to content

Commit bfe2b68

Browse files
authored
feat: Add incloud cli (#25)
1 parent eb61384 commit bfe2b68

File tree

21 files changed

+428
-143
lines changed

21 files changed

+428
-143
lines changed

README.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ _Reality is your canvas_
4040

4141
</div>
4242

43-
##
43+
##
4444

4545
<div align="center">
4646

@@ -126,28 +126,24 @@ InSpatial Cloud is released under the Apache 2.0 License. See the
126126

127127
### Installation
128128

129-
Add the InSpatial Cloud module to your Deno project using the following command:
129+
Install the InSpatial Cloud CLI:
130130

131131
```shell
132-
deno add jsr:@inspatial/cloud
132+
deno install -g jsr:@inspatial/cloud/incloud
133133
```
134134

135-
## Usage
135+
initialize a new project
136136

137-
### Basic Usage
138-
139-
```ts
140-
import { createInCloud } from "@inspatial/cloud";
141-
142-
createInCloud();
137+
```shell
138+
incloud init myProject
143139
```
140+
This will create a folder in the current directory with a started project.
144141

145-
That's it! Now you can run the app:
142+
Now you can run your InSpatial Cloud project:
146143

147144
```shell
148-
deno run -A main.ts
145+
cd my-project
146+
incloud run main.ts
149147
```
150148

151-
You can verify the app is running by pinging the API endpoint in you browser:
152-
153-
[http://localhost:8000/api?group=api&action=ping](http://localhost:8000/api?group=api&action=ping)
149+
You can verify the app is running by navigating to [http://localhost:8000](http://localhost:8000) in your browser

cli/incloud.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { InLog } from "~/in-log/in-log.ts";
2+
import { RunManager } from "./src/run-manager.ts";
3+
import convertString from "~/utils/convert-string.ts";
4+
import { center } from "~/terminal/format-utils.ts";
5+
6+
const inLog = new InLog({
7+
consoleDefaultStyle: "compact",
8+
name: "InSpatial CLI",
9+
traceOffset: 1,
10+
});
11+
function parseArgs() {
12+
let command: string | undefined;
13+
let lastArg: string | undefined;
14+
const args: string[] = [];
15+
Deno.args.forEach((arg, index) => {
16+
if (index === 0) {
17+
command = arg;
18+
return; // Skip the first argument which is the command
19+
}
20+
if (index === Deno.args.length - 1) {
21+
lastArg = arg; // Last argument is the file
22+
return;
23+
}
24+
args.push(arg);
25+
});
26+
27+
const file = lastArg;
28+
return {
29+
command,
30+
file,
31+
args,
32+
};
33+
}
34+
35+
function makeMainFile(projectName: string) {
36+
const mainfile = `import { createInCloud } from "@inspatial/cloud";
37+
38+
createInCloud({
39+
name: "${projectName}",
40+
description: "My InCloud Project",
41+
entryTypes: [], // Define your entry types here
42+
settingsTypes: [], // Define your settings types here
43+
actionGroups: [], // Define your API action groups here
44+
});
45+
46+
`;
47+
return mainfile;
48+
}
49+
50+
function doRun(rootPath: string, file?: string) {
51+
inLog.info("Running InCloud project...");
52+
const runner = new RunManager(rootPath, file);
53+
runner.init();
54+
}
55+
56+
function doInit(_rootPath: string, projectName?: string) {
57+
if (!projectName) {
58+
inLog.warn("Project name is required for initialization.");
59+
Deno.exit(1);
60+
}
61+
const folderName = convertString(projectName, "kebab", true);
62+
const name = convertString(folderName, "title", true);
63+
inLog.warn(`Initializing project: ${name}`);
64+
65+
// Create the project directory
66+
try {
67+
Deno.mkdirSync(folderName);
68+
} catch (e) {
69+
if (e instanceof Deno.errors.AlreadyExists) {
70+
inLog.error(`Project directory ${folderName} already exists.`);
71+
Deno.exit(1);
72+
}
73+
}
74+
Deno.chdir(folderName);
75+
Deno.writeTextFileSync(
76+
"deno.json",
77+
JSON.stringify(
78+
{},
79+
null,
80+
2,
81+
),
82+
);
83+
const cmd = new Deno.Command(Deno.execPath(), {
84+
args: ["add", "jsr:@inspatial/cloud"],
85+
stdout: "piped",
86+
stderr: "piped",
87+
});
88+
89+
const proc = cmd.spawn();
90+
91+
proc.stdout.pipeTo(
92+
new WritableStream({
93+
write(chunk) {
94+
console.log(center(new TextDecoder().decode(chunk)));
95+
},
96+
}),
97+
);
98+
proc.stderr.pipeTo(
99+
new WritableStream({
100+
write(chunk) {
101+
console.log(center(new TextDecoder().decode(chunk)));
102+
},
103+
}),
104+
);
105+
proc.status.then((status) => {
106+
Deno.writeTextFileSync("main.ts", makeMainFile(projectName));
107+
if (status.success) {
108+
inLog.info([
109+
`Project ${name} initialized successfully!`,
110+
"",
111+
"You can now run your project:",
112+
`cd ${folderName}`,
113+
`incloud run main.ts`,
114+
]);
115+
} else {
116+
inLog.error(`Failed to initialize project ${name}.`);
117+
Deno.exit(1);
118+
}
119+
}).catch((err) => {
120+
inLog.error(`Error initializing project: ${err.message}`);
121+
Deno.exit(1);
122+
});
123+
}
124+
125+
function init() {
126+
inLog.setConfig({
127+
logTrace: false,
128+
});
129+
const rootPath = Deno.cwd();
130+
const { command, file } = parseArgs();
131+
switch (command) {
132+
case "run":
133+
doRun(rootPath, file);
134+
break;
135+
case "init":
136+
doInit(rootPath, file);
137+
break;
138+
139+
default:
140+
console.error(`Unknown command: ${command}`);
141+
Deno.exit(1);
142+
}
143+
}
144+
145+
init();

cli/src/cloud-config.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { joinPath } from "~/utils/path-utils.ts";
2+
import convertString from "~/utils/convert-string.ts";
3+
import type { BuiltInConfig } from "./config-types.ts";
4+
5+
/**
6+
* Checks for a cloud-config.json file in the current working directory and loads it to the environment variables.
7+
*/
8+
export function loadCloudConfigFile(
9+
cloudRoot: string,
10+
): { config: BuiltInConfig; env: Record<string, any> } | false {
11+
const builtInConfig: Record<string, Record<string, any>> = {};
12+
const env: Record<string, any> = {};
13+
try {
14+
const filePath = joinPath(cloudRoot, "cloud-config.json");
15+
const file = Deno.readTextFileSync(filePath);
16+
const config = JSON.parse(file);
17+
for (const key in config) {
18+
if (key.startsWith("$")) {
19+
continue;
20+
}
21+
const extensionConfig = config[key];
22+
for (const subKey in extensionConfig) {
23+
if (!Object.keys(builtInConfig).includes(key)) {
24+
builtInConfig[key] = {};
25+
}
26+
builtInConfig[key][convertString(subKey, "camel")] =
27+
extensionConfig[subKey];
28+
env[subKey] = extensionConfig[subKey].toString();
29+
}
30+
}
31+
// TODO: Validate the config against the schema
32+
return { config: builtInConfig as unknown as BuiltInConfig, env };
33+
} catch (e) {
34+
if (e instanceof Deno.errors.NotFound) {
35+
return false;
36+
}
37+
throw e;
38+
}
39+
}

cli/src/config-types.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
export type ORMConfig = {
2+
autoTypes: boolean;
3+
autoMigrate: boolean;
4+
embeddedDb: boolean;
5+
embeddedDbPort: number;
6+
ormDebugMode: boolean;
7+
dbConnectionType: "tcp" | "socket";
8+
dbSocketPath: string;
9+
dbName: string;
10+
dbHost: string;
11+
dbPort: number;
12+
dbUser: string;
13+
dbPassword: string;
14+
dbSchema: string;
15+
dbAppName: string;
16+
dbClientMode: "pool" | "single";
17+
dbPoolSize: number;
18+
dbMaxPoolSize: number;
19+
dbIdleTimeout: number;
20+
};
21+
export type AuthConfig = {
22+
allowAll: boolean;
23+
};
24+
25+
export type CloudConfig = {
26+
name: string;
27+
cloudMode: "production" | "development";
28+
logLevel: "info" | "debug" | "error" | "warn";
29+
logTrace: boolean;
30+
brokerPort: number;
31+
queuePort: number;
32+
hostName: string;
33+
port: number;
34+
autoConfig: boolean;
35+
allowedOrigins: Set<string>;
36+
publicRoot: string;
37+
singlePageApp: boolean;
38+
cacheStatic: boolean;
39+
};
40+
41+
export interface BuiltInConfig {
42+
cloud: CloudConfig;
43+
orm: ORMConfig;
44+
auth: AuthConfig;
45+
}
File renamed without changes.

0 commit comments

Comments
 (0)