Skip to content

Commit fb9ca8d

Browse files
committed
wip: setup and wql scripts
ref DRE-1554
1 parent 97d85bf commit fb9ca8d

6 files changed

Lines changed: 379 additions & 81 deletions

File tree

scripts/_utils/generate-project.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// deno-lint-ignore-file no-import-prefix
2+
import { Entity } from "@dreamlab/engine";
3+
import * as path from "jsr:@std/path@^1";
4+
5+
export const projectTemplate = () => ({
6+
meta: {
7+
schema_version: 1,
8+
engine_revision: "2024-08.001",
9+
},
10+
scenes: {
11+
main: {
12+
registration: [],
13+
world: [],
14+
local: [
15+
{
16+
ref: Entity.createRef(),
17+
type: "@core/Camera",
18+
name: "Camera",
19+
values: { active: true },
20+
},
21+
],
22+
server: [],
23+
prefabs: [],
24+
},
25+
},
26+
});
27+
28+
export const denoJson = (root: string | URL) => ({
29+
imports: {
30+
"@dreamlab/engine": path.join(root, "engine/mod.ts"),
31+
"@dreamlab/engine/internal": path.join(root, "engine/internal.ts"),
32+
"@dreamlab/vendor/": path.join(root, "engine/_deps/"),
33+
"@dreamlab/ui": path.join(root, "ui/mod.ts"),
34+
"@dreamlab/ui/jsx-runtime": path.join(root, "ui/jsx.ts"),
35+
"@dreamlab/util/": path.join(root, "util/"),
36+
},
37+
compilerOptions: {
38+
lib: ["deno.window", "dom"],
39+
noImplicitOverride: false,
40+
jsxImportSource: "@dreamlab/ui",
41+
},
42+
});
43+
44+
export const helloWorldScript =
45+
`
46+
import { Behavior } from "@dreamlab/engine";
47+
48+
export default class HelloWorld extends Behavior {
49+
onInitialize() {
50+
console.log("hello world!");
51+
}
52+
}
53+
`.trim() + "\n";

scripts/_utils/init-env.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// deno-lint-ignore-file no-import-prefix
2+
import * as fs from "jsr:@std/fs@^1";
3+
import * as path from "jsr:@std/path@^1";
4+
5+
export const initServerEnv = async (root: string | URL, token = "token"): Promise<boolean> => {
6+
const serverEnvLocal = path.join(root, "multiplayer", ".env.local");
7+
if (await fs.exists(serverEnvLocal)) return false;
8+
9+
const env =
10+
`
11+
DREAMLAB_MULTIPLAYER_AUTH_TOKEN="${token}"
12+
DREAMLAB_NEXT_GAME_JWT_SECRET="${token}"
13+
`.trim() + "\n";
14+
15+
await Deno.writeTextFile(serverEnvLocal, env);
16+
return true;
17+
};
18+
19+
export const initEditorEnv = async (root: string | URL): Promise<boolean> => {
20+
const editorEnvLocal = path.join(root, "editor", ".env.local");
21+
if (await fs.exists(editorEnvLocal)) return false;
22+
23+
const env =
24+
`
25+
IS_DEV="true"
26+
DREAMLAB_MULTIPLAYER_PUBLIC_URL="http://localhost:8001"
27+
`.trim() + "\n";
28+
29+
await Deno.writeTextFile(editorEnvLocal, env);
30+
return true;
31+
};

scripts/init-local-env.ts

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,11 @@
1-
import * as fs from "jsr:@std/fs";
2-
import * as path from "jsr:@std/path";
1+
import { initEditorEnv, initServerEnv } from "./_utils/init-env.ts";
32

43
if (import.meta.main) {
5-
const serverEnvLocal = path.join(Deno.cwd(), "multiplayer", ".env.local");
6-
if (await fs.exists(serverEnvLocal)) {
7-
console.log("warning: skipping server .env.local as it already exists");
8-
} else {
9-
const env =
10-
`
11-
DREAMLAB_MULTIPLAYER_AUTH_TOKEN="token"
12-
DREAMLAB_NEXT_GAME_JWT_SECRET="token"
13-
`.trim() + "\n";
4+
const root = Deno.cwd();
145

15-
await Deno.writeTextFile(serverEnvLocal, env);
16-
}
6+
const serverEnv = await initServerEnv(root);
7+
if (!serverEnv) console.log("warning: skipping server .env.local as it already exists");
178

18-
const editorEnvLocal = path.join(Deno.cwd(), "editor", ".env.local");
19-
if (await fs.exists(editorEnvLocal)) {
20-
console.log("warning: skipping editor .env.local as it already exists");
21-
} else {
22-
const env =
23-
`
24-
IS_DEV="true"
25-
DREAMLAB_MULTIPLAYER_PUBLIC_URL="http://localhost:8001"
26-
`.trim() + "\n";
27-
28-
await Deno.writeTextFile(editorEnvLocal, env);
29-
}
9+
const editorEnv = await initEditorEnv(root);
10+
if (!editorEnv) console.log("warning: skipping editor .env.local as it already exists");
3011
}

scripts/init-project.ts

Lines changed: 7 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,8 @@
1-
import { Entity } from "@dreamlab/engine";
2-
import * as cli from "jsr:@std/cli";
3-
import * as fs from "jsr:@std/fs";
4-
import * as path from "jsr:@std/path";
5-
6-
const projectTemplate = {
7-
meta: {
8-
schema_version: 1,
9-
engine_revision: "2024-08.001",
10-
},
11-
scenes: {
12-
main: {
13-
registration: [],
14-
world: [],
15-
local: [
16-
{
17-
ref: Entity.createRef(),
18-
type: "@core/Camera",
19-
name: "Camera",
20-
values: { active: true },
21-
},
22-
],
23-
server: [],
24-
prefabs: [],
25-
},
26-
},
27-
};
28-
29-
const denoJson = (root: string | URL) => ({
30-
imports: {
31-
"@dreamlab/engine": path.join(root, "engine/mod.ts"),
32-
"@dreamlab/engine/internal": path.join(root, "engine/internal.ts"),
33-
"@dreamlab/vendor/": path.join(root, "engine/_deps/"),
34-
"@dreamlab/ui": path.join(root, "ui/mod.ts"),
35-
"@dreamlab/ui/jsx-runtime": path.join(root, "ui/jsx.ts"),
36-
"@dreamlab/util/": path.join(root, "util/"),
37-
},
38-
compilerOptions: {
39-
lib: ["deno.window", "dom"],
40-
noImplicitOverride: false,
41-
jsxImportSource: "@dreamlab/ui",
42-
},
43-
});
44-
45-
const helloWorldScript =
46-
`
47-
import { Behavior } from "@dreamlab/engine";
48-
49-
export default class HelloWorld extends Behavior {
50-
onInitialize() {
51-
console.log("hello world!");
52-
}
53-
}
54-
`.trim() + "\n";
1+
// deno-lint-ignore-file no-import-prefix
2+
import * as cli from "jsr:@std/cli@^1";
3+
import * as fs from "jsr:@std/fs@^1";
4+
import * as path from "jsr:@std/path@^1";
5+
import { denoJson, helloWorldScript, projectTemplate } from "./_utils/generate-project.ts";
556

567
if (import.meta.main) {
578
const args = cli.parseArgs(Deno.args);
@@ -69,11 +20,11 @@ if (import.meta.main) {
6920
await fs.ensureDir(dir);
7021
await Deno.writeTextFile(
7122
path.join(dir, "project.json"),
72-
JSON.stringify(projectTemplate, null, 2),
23+
JSON.stringify(projectTemplate(), null, 2) + "\n",
7324
);
7425
await Deno.writeTextFile(
7526
path.join(dir, "deno.json"),
76-
JSON.stringify(denoJson(Deno.cwd()), null, 2),
27+
JSON.stringify(denoJson(Deno.cwd()), null, 2) + "\n",
7728
);
7829

7930
await fs.ensureDir(path.join(dir, "src"));

scripts/setup.ts

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env -S deno run --ext=ts -A
2+
// deno-lint-ignore-file no-import-prefix
3+
import * as fs from "jsr:@std/fs@^1";
4+
import * as path from "jsr:@std/path@^1";
5+
import { homedir } from "node:os";
6+
import {
7+
intro,
8+
isCancel,
9+
log,
10+
note,
11+
outro,
12+
select,
13+
tasks,
14+
text,
15+
type Task,
16+
} from "npm:@clack/prompts@^0.11.0";
17+
import { detectDefaultShell } from "npm:default-shell@^2.2.0";
18+
import color from "npm:picocolors@^1.1.1";
19+
import { denoJson, helloWorldScript, projectTemplate } from "./_utils/generate-project.ts";
20+
import { initEditorEnv, initServerEnv } from "./_utils/init-env.ts";
21+
22+
const DREAMLAB_ROOT = path.join(path.fromFileUrl(import.meta.url), "../..");
23+
24+
type Template = {
25+
readonly label: string;
26+
readonly directory: string;
27+
readonly repo: string | undefined;
28+
readonly hint?: string;
29+
};
30+
31+
const TEMPLATES: [Template, ...(readonly Template[])] = [
32+
{
33+
label: "OpenMonsters",
34+
directory: "openmonsters",
35+
repo: "https://github.com/WorldQL/openmonsters.git",
36+
hint: "recommended",
37+
},
38+
{
39+
label: "Blank",
40+
directory: "dreamlab-project",
41+
repo: undefined,
42+
},
43+
];
44+
45+
async function task(title: string, task: Task["task"]) {
46+
await tasks([{ title, task }]);
47+
}
48+
49+
if (import.meta.main) {
50+
let updatedShell: "bash" | "zsh" | undefined;
51+
// detect shell type and inject `wql` alias
52+
try {
53+
const shell = detectDefaultShell();
54+
55+
// TODO: other shell types?
56+
if (shell === "/bin/bash" || shell === "/bin/zsh") {
57+
const rcFile = (() => {
58+
const zDotDir = Deno.env.get("ZDOTDIR");
59+
if (shell === "/bin/zsh" && zDotDir) {
60+
return path.join(zDotDir, ".zshrc");
61+
}
62+
63+
const rc = shell === "/bin/bash" ? ".bashrc" : ".zshrc";
64+
return path.join(homedir(), rc);
65+
})();
66+
67+
const content = await Deno.readTextFile(rcFile);
68+
using rc = await Deno.open(rcFile, { read: true, append: true });
69+
70+
if (!content.includes(`alias wql='`)) {
71+
const writer = rc.writable.getWriter();
72+
await writer.ready;
73+
74+
const encoder = new TextEncoder();
75+
if (!content.endsWith("\n")) await writer.write(encoder.encode("\n"));
76+
await writer.write(encoder.encode(`export DREAMLAB_DIR="${DREAMLAB_ROOT}"\n`));
77+
await writer.write(
78+
encoder.encode(`alias wql='deno run -A "$DREAMLAB_DIR/scripts/wql.ts"'\n`),
79+
);
80+
81+
await writer.ready;
82+
await writer.close();
83+
84+
updatedShell = shell === "/bin/bash" ? "bash" : "zsh";
85+
}
86+
}
87+
} catch (_error) {
88+
// uncomment when debugging
89+
// console.error(_error);
90+
}
91+
92+
intro(color.bgCyan(" WorldQL Setup "));
93+
94+
await task("Initializing Dreamlab environment", async () => {
95+
await Promise.all([initEditorEnv(DREAMLAB_ROOT), initServerEnv(DREAMLAB_ROOT)]);
96+
return "Initialized Dreamlab environment";
97+
});
98+
99+
const template = await select({
100+
message: "Pick a project template:",
101+
options: TEMPLATES.map(
102+
template => ({ label: template.label, value: template, hint: template.hint }) as const,
103+
),
104+
});
105+
if (isCancel(template)) Deno.exit(1);
106+
107+
let directory: string | symbol | undefined = await text({
108+
message: "Project directory name:",
109+
placeholder: template.directory,
110+
validate: value => {
111+
const dir = path.join(Deno.cwd(), value === "" ? template.directory : value);
112+
const exists = fs.existsSync(dir);
113+
if (exists) return "Already exists";
114+
115+
return undefined;
116+
},
117+
});
118+
if (isCancel(directory)) Deno.exit(1);
119+
120+
directory ??= template.directory;
121+
const fullPath = path.join(Deno.cwd(), directory);
122+
123+
// TODO: show a confirmation step?
124+
// const shoudClone = await confirm({
125+
// message: "",
126+
// });
127+
// if (isCancel(shoudClone)) Deno.exit(1);
128+
// if (!shouldClone) {
129+
// cancel('Operation cancelled')
130+
// Deno.exit(0)
131+
// }
132+
133+
if (template.repo === undefined) {
134+
await task("Creating blank project", async () => {
135+
await fs.emptyDir(fullPath);
136+
await Deno.writeTextFile(
137+
path.join(fullPath, "project.json"),
138+
JSON.stringify(projectTemplate(), null, 2) + "\n",
139+
);
140+
await Deno.writeTextFile(
141+
path.join(fullPath, "deno.json"),
142+
JSON.stringify(denoJson(DREAMLAB_ROOT), null, 2) + "\n",
143+
);
144+
145+
await fs.ensureDir(path.join(fullPath, "src"));
146+
await Deno.writeTextFile(path.join(fullPath, "src", "hello-world.ts"), helloWorldScript);
147+
148+
return "Created blank project";
149+
});
150+
} else {
151+
const repo = template.repo;
152+
await task(`Cloning Template: "${template.label}"`, async () => {
153+
const cmd = new Deno.Command("git", {
154+
args: ["clone", "--depth=1", repo, fullPath],
155+
});
156+
157+
const result = await cmd.output();
158+
if (!result.success) {
159+
log.error("Failed to clone template!");
160+
Deno.exit(1);
161+
}
162+
163+
// clear git history
164+
await Deno.remove(path.join(fullPath, ".git"), { recursive: true });
165+
166+
// write correct deno.json
167+
await Deno.writeTextFile(
168+
path.join(fullPath, "deno.json"),
169+
JSON.stringify(denoJson(DREAMLAB_ROOT), null, 2) + "\n",
170+
);
171+
172+
return `Cloned Template: "${template.label}"`;
173+
});
174+
175+
// TODO: prompt to initialize a fresh git repo?
176+
}
177+
178+
const notes: (string | false)[] = [
179+
updatedShell === "bash" && "$ source ~/.bashrc",
180+
updatedShell === "zsh" && "", // TODO: shell update for zsh
181+
182+
`$ cd ${directory} && wql up`,
183+
];
184+
note(notes.filter(line => line !== false).join("\n"), "Next steps:");
185+
186+
outro(`You're good to go!`);
187+
Deno.exit(0);
188+
}

0 commit comments

Comments
 (0)