Skip to content

Commit c59ed27

Browse files
authored
feat: add editor option to part main schematic (#88)
1 parent 6bf3c89 commit c59ed27

9 files changed

Lines changed: 142 additions & 31 deletions

File tree

e2e/part-main.test.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,4 +171,62 @@ describe("part-main schematic", () => {
171171
expect(content).toContain("await afterRun(app);");
172172
});
173173
});
174+
175+
describe("with custom directory", () => {
176+
it("should generate main file in the specified directory", async () => {
177+
const tree = await runner.runSchematic("part-main", {
178+
name: "my-app",
179+
part: "client",
180+
language: "ts",
181+
directory: "custom-dir",
182+
saveFile: resolve(tmpDir, "client.save.json"),
183+
});
184+
expect(tree.files).toContain("/custom-dir/client/main.ts");
185+
expect(tree.files).not.toContain("/my-app/client/main.ts");
186+
});
187+
});
188+
189+
describe("editor mode", () => {
190+
let tree: UnitTestTree;
191+
192+
beforeAll(async () => {
193+
tree = await runner.runSchematic("part-main", {
194+
name: "my-app",
195+
part: "client",
196+
language: "ts",
197+
editor: true,
198+
saveFile: resolve(tmpDir, "client.save.json"),
199+
});
200+
});
201+
202+
it("should generate the editor main file under .nanoforge/editor", () => {
203+
expect(tree.files).toContain("/my-app/.nanoforge/editor/client/main.ts");
204+
});
205+
206+
it("should import from @nanoforge-dev/core-editor", () => {
207+
const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts");
208+
expect(content).toContain('from "@nanoforge-dev/core-editor"');
209+
});
210+
211+
it("should use IEditorRunOptions type", () => {
212+
const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts");
213+
expect(content).toContain("IEditorRunOptions");
214+
expect(content).toContain("export async function main(options: IEditorRunOptions)");
215+
});
216+
217+
it("should use entity params from editor save", () => {
218+
const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts");
219+
expect(content).toContain("options.editor.save.entities[0].components[0].params[0]");
220+
});
221+
222+
it("should import components and systems with relative path to root", () => {
223+
const content = tree.readContent("/my-app/.nanoforge/editor/client/main.ts");
224+
expect(content).toContain(
225+
'import { ExampleComponent } from "../../../client/components/example.component"',
226+
);
227+
expect(content).toContain(
228+
'import { exampleSystem } from "../../../client/systems/example.system"',
229+
);
230+
});
231+
});
174232
});

src/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
export * from "./utils";
21
export * from "./defaults";

src/libs/part-main/part-main.factory.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const transform = (schema: PartMainSchema): PartMainOptions => {
3333
};
3434
};
3535

36-
const generate = (options: PartMainOptions, path: string): Source => {
36+
const generate = (options: PartMainOptions, path: string, editor: boolean = false): Source => {
3737
const rules = [
3838
template({
3939
...strings,
@@ -42,14 +42,17 @@ const generate = (options: PartMainOptions, path: string): Source => {
4242
move(normalize(path)),
4343
];
4444

45-
return apply(asSource(writeMain(options, path)), rules);
45+
return apply(asSource(writeMain(options, path, editor)), rules);
4646
};
4747

48-
const writeMain = (options: PartMainOptions, path: string) => {
48+
const writeMain = (options: PartMainOptions, path: string, editor: boolean) => {
4949
return (tree: Tree) => {
5050
const save = getSave(path, options.part, options.saveFile);
51-
const resPath = join(options.part as Path, `main.${options.language}`);
52-
const content = generateMain(options, save);
51+
52+
let resPath = join(options.part as Path, `main.${options.language}`);
53+
if (editor) resPath = join(".nanoforge/editor" as Path, resPath);
54+
55+
const content = generateMain(options, save, editor);
5356
fs.rmSync(join(path as Path, resPath), { force: true });
5457
tree.create(resPath, content);
5558
return tree;
@@ -65,5 +68,5 @@ const getSave = (path: string, part: "client" | "server", saveFile?: string): Sa
6568
export const main = (schema: PartMainSchema): Rule => {
6669
const options = transform(schema);
6770

68-
return mergeWith(generate(options, schema.directory ?? options.name));
71+
return mergeWith(generate(options, schema.directory ?? options.name, schema.editor));
6972
};

src/libs/part-main/part-main.schema.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,9 @@ export interface PartMainSchema {
2828
* Save file path with components and systems in JSON format
2929
*/
3030
saveFile?: string;
31+
32+
/**
33+
* Build main editor
34+
*/
35+
editor?: boolean;
3136
}

src/libs/part-main/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
"saveFile": {
3737
"type": "string",
3838
"description": "Save file path with components and systems in JSON format"
39+
},
40+
"editor": {
41+
"type": "boolean",
42+
"description": "Build main editor",
43+
"default": false
3944
}
4045
},
4146
"required": ["name", "part"]

src/utils/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/utils/main/main-functions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { join } from "path";
2+
13
import { InitFunctionEnum } from "@utils/main/enums";
24

35
import { MainGenerator } from "./main.generator";
@@ -9,12 +11,12 @@ export interface MainOptions {
911
initFunctions: boolean;
1012
}
1113

12-
export const generateMain = (options: MainOptions, save: Save) => {
14+
export const generateMain = (options: MainOptions, save: Save, editor: boolean = false) => {
1315
const isServer = options.part === "server";
1416
const hasTypes = options.language === "ts";
1517
const initFunctions = options.initFunctions;
1618

17-
return new MainGenerator()
19+
return (!editor ? new MainGenerator() : new MainGenerator(true, join("../../..", options.part)))
1820
.generateBaseImports(hasTypes)
1921
.generateLibsImports(save.libraries)
2022
.generateInitFunctionsImportsIfNeeded(initFunctions)

src/utils/main/main.generator.ts

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { InitFunctionEnum } from "@utils/main/enums";
2+
import { joinRelative } from "@utils/path";
23

34
import { LIBS_FUNCTIONS_NAME } from "./const";
45
import {
@@ -12,32 +13,52 @@ import {
1213
export class MainGenerator {
1314
private buffer = "";
1415
private indentation = 0;
16+
private readonly editor: boolean;
17+
private readonly pathToRoot: string;
18+
19+
constructor(editor: boolean = false, pathToRoot: string = "") {
20+
this.editor = editor;
21+
this.pathToRoot = pathToRoot;
22+
}
1523

1624
toString(): string {
1725
return this.buffer;
1826
}
1927

2028
generateBaseImports(hasTypes: boolean): MainGenerator {
21-
if (hasTypes) this.writeLine(`import { type IRunOptions } from "@nanoforge-dev/common";`);
22-
this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core";`);
29+
if (this.editor) {
30+
if (hasTypes)
31+
this.writeLine(
32+
`import { type IEditorRunOptions, NanoforgeFactory } from "@nanoforge-dev/core-editor";`,
33+
);
34+
else this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core-editor";`);
35+
} else {
36+
if (hasTypes) this.writeLine(`import { type IRunOptions } from "@nanoforge-dev/common";`);
37+
this.writeLine(`import { NanoforgeFactory } from "@nanoforge-dev/core";`);
38+
}
39+
2340
this.endSection();
2441
return this;
2542
}
2643

2744
generateLibsImports(libs: SaveLibrary[]): MainGenerator {
28-
return this.generateImports(libs);
45+
return this.generateImports(libs, false);
2946
}
3047

3148
generateComponentsImports(libs: SaveComponent[]): MainGenerator {
32-
return this.generateImports(libs);
49+
return this.generateImports(libs, true);
3350
}
3451

3552
generateSystemsImports(libs: SaveSystem[]): MainGenerator {
36-
return this.generateImports(libs);
53+
return this.generateImports(libs, true);
3754
}
3855

3956
generateMainFunction(hasTypes: boolean, cb: (generator: MainGenerator) => void): MainGenerator {
40-
this.writeLine(`export async function main(options${hasTypes ? ": IRunOptions" : ""}) {`);
57+
if (this.editor)
58+
this.writeLine(
59+
`export async function main(options${hasTypes ? ": IEditorRunOptions" : ""}) {`,
60+
);
61+
else this.writeLine(`export async function main(options${hasTypes ? ": IRunOptions" : ""}) {`);
4162
this.indentation += 1;
4263
cb(this);
4364
this.indentation -= 1;
@@ -84,7 +105,7 @@ export class MainGenerator {
84105
}
85106

86107
generateEntities(entities: SaveEntity[]): MainGenerator {
87-
entities.forEach((entity) => this.generateEntity(entity));
108+
entities.forEach((entity, index) => this.generateEntity(entity, index));
88109
return this;
89110
}
90111

@@ -101,14 +122,17 @@ export class MainGenerator {
101122

102123
generateInitFunctionsImportsIfNeeded(needed: boolean): MainGenerator {
103124
if (!needed) return this;
104-
return this.generateImports([
105-
{ name: "afterInit", path: "./init/after-init" },
106-
{ name: "afterRegistryInit", path: "./init/after-registry-init" },
107-
{ name: "afterRun", path: "./init/after-run" },
108-
{ name: "beforeInit", path: "./init/before-init" },
109-
{ name: "beforeRegistryInit", path: "./init/before-registry-init" },
110-
{ name: "beforeRun", path: "./init/before-run" },
111-
]);
125+
return this.generateImports(
126+
[
127+
{ name: "afterInit", path: "./init/after-init" },
128+
{ name: "afterRegistryInit", path: "./init/after-registry-init" },
129+
{ name: "afterRun", path: "./init/after-run" },
130+
{ name: "beforeInit", path: "./init/before-init" },
131+
{ name: "beforeRegistryInit", path: "./init/before-registry-init" },
132+
{ name: "beforeRun", path: "./init/before-run" },
133+
],
134+
true,
135+
);
112136
}
113137

114138
private generateInitFunction(func: InitFunctionEnum): MainGenerator {
@@ -122,19 +146,29 @@ export class MainGenerator {
122146
return this;
123147
}
124148

125-
private generateImports(els: { name: string; path: string }[]): MainGenerator {
149+
private generateImports(els: { name: string; path: string }[], relative: boolean): MainGenerator {
126150
els
127151
.sort((a, b) => a.path.localeCompare(b.path))
128-
.forEach(({ name, path }) => this.writeLine(`import { ${name} } from "${path}";`));
152+
.forEach(({ name, path }) =>
153+
this.writeLine(
154+
`import { ${name} } from "${relative ? joinRelative(this.pathToRoot, path) : path}";`,
155+
),
156+
);
129157
this.endSection();
130158
return this;
131159
}
132160

133-
private generateEntity(entity: SaveEntity): void {
161+
private generateEntity(entity: SaveEntity, entityIndex: number): void {
134162
this.writeLine(`const ${entity.id} = registry.spawnEntity();`);
135-
entity.components.forEach(({ name, params }) =>
136-
this.writeLine(`registry.addComponent(${entity.id}, new ${name}(${params.join(", ")}));`),
137-
);
163+
entity.components.forEach(({ name, params: rawParams }, componentIndex) => {
164+
const params = !this.editor
165+
? rawParams
166+
: rawParams.map(
167+
(_param, index) =>
168+
`options.editor.save.entities[${entityIndex}].components[${componentIndex}].params[${index}]`,
169+
);
170+
this.writeLine(`registry.addComponent(${entity.id}, new ${name}(${params.join(", ")}));`);
171+
});
138172
this.endSection();
139173
}
140174

src/utils/path.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { join } from "path";
2+
3+
export const joinRelative = (...paths: [string, ...string[]]): string => {
4+
const path = join(...paths);
5+
return path.startsWith("./") || path.startsWith("../") ? path : `./${path}`;
6+
};

0 commit comments

Comments
 (0)