diff --git a/CODEOWNERS b/.github/CODEOWNERS similarity index 100% rename from CODEOWNERS rename to .github/CODEOWNERS diff --git a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml index 5cd02fa..01276fa 100644 --- a/.github/ISSUE_TEMPLATE/01-command_bug_report.yml +++ b/.github/ISSUE_TEMPLATE/01-command_bug_report.yml @@ -15,6 +15,7 @@ body: options: - application - configuration + - docker - part-base - part-main - other diff --git a/.github/ISSUE_TEMPLATE/02-feature_request.yml b/.github/ISSUE_TEMPLATE/02-feature_request.yml index 03c37df..bdf13c4 100644 --- a/.github/ISSUE_TEMPLATE/02-feature_request.yml +++ b/.github/ISSUE_TEMPLATE/02-feature_request.yml @@ -13,6 +13,7 @@ body: options: - application - configuration + - docker - part-base - part-main - other diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml index 1ff3ad8..5d43900 100644 --- a/.github/issue-labeler.yml +++ b/.github/issue-labeler.yml @@ -6,6 +6,10 @@ schematics:configuration: - "### Which schematics is this (bug report|feature request) for\\?\\n\\nconfiguration\\n" +schematics:docker: + - "### Which schematics is this (bug + report|feature request) for\\?\\n\\ndocker\\n" + schematics:part-base: - "### Which schematics is this (bug report|feature request) for\\?\\n\\npart-base\\n" diff --git a/.github/labeler.yml b/.github/labeler.yml index 1c85eda..9949c96 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -10,6 +10,12 @@ schematics:configuration: - src/libs/configuration/* - src/libs/configuration/**/* +schematics:docker: + - changed-files: + - any-glob-to-any-file: + - src/libs/docker/* + - src/libs/docker/**/* + schematics:part-base: - changed-files: - any-glob-to-any-file: diff --git a/.github/labels.yml b/.github/labels.yml index 20e3daf..564838c 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -50,6 +50,10 @@ description: "Related to configuration schematics" color: "aaa3dc" +- name: "schematics:docker" + description: "Related to docker schematics" + color: "aaa3dc" + - name: "schematics:part-base" description: "Related to part-base schematics" color: "aaa3dc" diff --git a/e2e/configuration.test.ts b/e2e/configuration.test.ts index 2ce3afd..4753008 100644 --- a/e2e/configuration.test.ts +++ b/e2e/configuration.test.ts @@ -12,26 +12,29 @@ describe("configuration schematic", () => { let config: Record; beforeAll(async () => { - tree = await runner.runSchematic("configuration", { name: "my-app" }); - config = JSON.parse(tree.readContent("/my-app/nanoforge.config.json")); + tree = await runner.runSchematic("configuration", { + name: "my-name", + }); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); }); it("should generate nanoforge.config.json", () => { - expect(tree.files).toContain("/my-app/nanoforge.config.json"); + expect(tree.files).toContain("/nanoforge.config.json"); }); it("should create a valid JSON config file", () => { - expect(() => JSON.parse(tree.readContent("/my-app/nanoforge.config.json"))).not.toThrow(); + expect(() => JSON.parse(tree.readContent("/nanoforge.config.json"))).not.toThrow(); }); it("should include default client build config", () => { - expect(config.client).toBeDefined(); - expect(config.client.build.entryFile).toBe("client/main.ts"); - expect(config.client.build.outDir).toBe(".nanoforge/client"); + expect(config.name).toBe("my-name"); + expect(config.language).toBe("ts"); + expect(config.initFunctions).toBe(false); }); - it("should include default client runtime config", () => { - expect(config.client.runtime.dir).toBe(".nanoforge/client"); + it("should include default client build config", () => { + expect(config.client).toBeDefined(); + expect(config.client.enable).toBe(true); }); it("should have server disabled", () => { @@ -39,39 +42,57 @@ describe("configuration schematic", () => { }); }); - describe("with server enabled", () => { + describe("with js", () => { let tree: UnitTestTree; let config: Record; beforeAll(async () => { tree = await runner.runSchematic("configuration", { - name: "my-app", - server: true, + name: "my-name", + language: "js", + initFunctions: true, }); - config = JSON.parse(tree.readContent("/my-app/nanoforge.config.json")); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); }); - it("should generate config file", () => { - expect(tree.files).toContain("/my-app/nanoforge.config.json"); + it("should generate nanoforge.config.json", () => { + expect(tree.files).toContain("/nanoforge.config.json"); }); - it("should include server config with enable flag", () => { - expect(config.server).toBeDefined(); - expect(config.server.enable).toBe(true); + it("should include default client build config", () => { + expect(config.name).toBe("my-name"); + expect(config.language).toBe("js"); + expect(config.initFunctions).toBe(true); + }); + + it("should include default client build config", () => { + expect(config.client).toBeDefined(); + expect(config.client.build.entry).toBe("client/main.js"); }); - it("should include server build config", () => { - expect(config.server.build.entryFile).toBe("server/main.ts"); - expect(config.server.build.outDir).toBe(".nanoforge/server"); + it("should include default editor config", () => { + expect(config.client.editor.entry).toBe(".nanoforge/editor/client/main.js"); }); + }); - it("should include server runtime config", () => { - expect(config.server.runtime.dir).toBe(".nanoforge/server"); + describe("with server enabled", () => { + let tree: UnitTestTree; + let config: Record; + + beforeAll(async () => { + tree = await runner.runSchematic("configuration", { + server: true, + }); + config = JSON.parse(tree.readContent("/nanoforge.config.json")); }); - it("should still include client config", () => { - expect(config.client).toBeDefined(); - expect(config.client.build.entryFile).toBe("client/main.ts"); + it("should generate config file", () => { + expect(tree.files).toContain("/nanoforge.config.json"); + }); + + it("should include server config with enable flag", () => { + expect(config.server).toBeDefined(); + expect(config.server.enable).toBe(true); }); }); diff --git a/e2e/docker.test.ts b/e2e/docker.test.ts index 067a40a..f77fc19 100644 --- a/e2e/docker.test.ts +++ b/e2e/docker.test.ts @@ -15,7 +15,7 @@ describe("docker schematic", () => { (packageManager) => { beforeAll(async () => { tree = await runner.runSchematic("docker", { - name: `${packageManager}-test-app`, + directory: `${packageManager}-test-app`, packageManager, }); }); diff --git a/e2e/part-base.test.ts b/e2e/part-base.test.ts index 6e781c0..0e917f3 100644 --- a/e2e/part-base.test.ts +++ b/e2e/part-base.test.ts @@ -12,7 +12,7 @@ describe("part-base schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-base", { - name: "my-app", + directory: "my-app", part: "client", language: "ts", }); @@ -50,7 +50,7 @@ describe("part-base schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-base", { - name: "my-app", + directory: "my-app", part: "server", language: "ts", }); @@ -75,7 +75,7 @@ describe("part-base schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-base", { - name: "my-app", + directory: "my-app", part: "client", language: "ts", initFunctions: true, @@ -97,7 +97,7 @@ describe("part-base schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-base", { - name: "my-app", + directory: "my-app", part: "client", language: "js", }); diff --git a/e2e/part-main.test.ts b/e2e/part-main.test.ts index 694c1f2..253e850 100644 --- a/e2e/part-main.test.ts +++ b/e2e/part-main.test.ts @@ -60,7 +60,7 @@ describe("part-main schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-main", { - name: "my-app", + directory: "my-app", part: "client", language: "ts", saveFile: resolve(tmpDir, "client.save.json"), @@ -102,7 +102,7 @@ describe("part-main schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-main", { - name: "my-app", + directory: "my-app", part: "server", language: "ts", saveFile: resolve(tmpDir, "server.save.json"), @@ -124,7 +124,7 @@ describe("part-main schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-main", { - name: "my-app", + directory: "my-app", part: "client", language: "js", saveFile: resolve(tmpDir, "client.save.json"), @@ -147,7 +147,7 @@ describe("part-main schematic", () => { beforeAll(async () => { tree = await runner.runSchematic("part-main", { - name: "my-app", + directory: "my-app", part: "client", language: "ts", initFunctions: true, @@ -172,26 +172,12 @@ describe("part-main schematic", () => { }); }); - describe("with custom directory", () => { - it("should generate main file in the specified directory", async () => { - const tree = await runner.runSchematic("part-main", { - name: "my-app", - part: "client", - language: "ts", - directory: "custom-dir", - saveFile: resolve(tmpDir, "client.save.json"), - }); - expect(tree.files).toContain("/custom-dir/client/main.ts"); - expect(tree.files).not.toContain("/my-app/client/main.ts"); - }); - }); - describe("editor mode", () => { let tree: UnitTestTree; beforeAll(async () => { tree = await runner.runSchematic("part-main", { - name: "my-app", + directory: "my-app", part: "client", language: "ts", editor: true, diff --git a/src/defaults.ts b/src/defaults.ts index 3bf6038..5c44c97 100644 --- a/src/defaults.ts +++ b/src/defaults.ts @@ -4,26 +4,3 @@ export const DEFAULT_AUTHOR = ""; export const DEFAULT_DESCRIPTION = ""; export const DEFAULT_LANGUAGE = "ts"; export const DEFAULT_PACKAGE_MANAGER = "npm"; -export const DEFAULT_CONFIG = { - client: { - build: { - entryFile: "client/main.ts", - outDir: ".nanoforge/client", - }, - runtime: { - dir: ".nanoforge/client", - }, - }, -}; -export const DEFAULT_SERVER_CONFIG = { - server: { - enable: true, - build: { - entryFile: "server/main.ts", - outDir: ".nanoforge/server", - }, - runtime: { - dir: ".nanoforge/server", - }, - }, -}; diff --git a/src/libs/application/schema.json b/src/libs/application/schema.json index f0e514d..2e3f262 100644 --- a/src/libs/application/schema.json +++ b/src/libs/application/schema.json @@ -7,11 +7,7 @@ "name": { "type": "string", "description": "The name of the application", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use for the new project ?" + "default": "nanoforge-app" }, "version": { "type": "string", diff --git a/src/libs/configuration/configuration.factory.ts b/src/libs/configuration/configuration.factory.ts index 3460e8e..842330c 100644 --- a/src/libs/configuration/configuration.factory.ts +++ b/src/libs/configuration/configuration.factory.ts @@ -14,24 +14,16 @@ import { import { ConfigDeclarator } from "@utils/config/config.declarator"; import { ConfigFinder } from "@utils/config/config.finder"; +import { type Config } from "@utils/config/config.type"; +import { type DeepPartial } from "@utils/types"; import { type ConfigurationOptions } from "./configuration.options"; import { type ConfigurationSchema } from "./configuration.schema"; const transform = (schema: ConfigurationSchema): ConfigurationOptions => { - const res: ConfigurationOptions = { - server: { - enable: schema.server ?? false, - }, - }; - - if (schema.language === "js") { - res["client"] = { build: { entryFile: "client/main.js" } }; - if (schema.server && "server" in res && res.server) - res.server["build"] = { entryFile: "server/main.js" }; - } + void schema; - return res; + return {}; }; const generate = (options: ConfigurationOptions, path: string): Source => { @@ -44,7 +36,7 @@ const generate = (options: ConfigurationOptions, path: string): Source => { ]); }; -const addConfiguration = (options: ConfigurationOptions, path: Path) => { +const addConfiguration = (options: DeepPartial, path: Path) => { return (tree: Tree) => { const config = new ConfigFinder(tree).find(path); if (!config) return tree; @@ -59,11 +51,41 @@ const addConfiguration = (options: ConfigurationOptions, path: Path) => { }; }; +const getConfig = (schema: ConfigurationSchema): DeepPartial => { + const res: DeepPartial = { + name: schema.name, + language: schema.language, + initFunctions: schema.initFunctions, + client: { + enable: true, + }, + server: { + enable: schema.server ?? false, + }, + }; + + if (schema.language === "js") { + if ("client" in res && res.client) { + res.client["build"] = { entry: "client/main.js" }; + res.client["editor"] = { entry: ".nanoforge/editor/client/main.js" }; + } + if (schema.server && "server" in res && res.server) { + res.server["build"] = { entry: "server/main.js" }; + res.server["editor"] = { entry: ".nanoforge/editor/client/main.js" }; + } + } + + return res; +}; + export const main = (schema: ConfigurationSchema): Rule => { const options = transform(schema); - const directory = schema.directory ?? schema.name; + const directory = schema.directory; return branchAndMerge( - chain([mergeWith(generate(options, directory)), addConfiguration(options, directory as Path)]), + chain([ + mergeWith(generate(options, directory)), + addConfiguration(getConfig(schema), directory as Path), + ]), ); }; diff --git a/src/libs/configuration/configuration.options.d.ts b/src/libs/configuration/configuration.options.d.ts index 0eba510..3061ac5 100644 --- a/src/libs/configuration/configuration.options.d.ts +++ b/src/libs/configuration/configuration.options.d.ts @@ -1,70 +1 @@ -interface BuildConfigurationOptions { - /** - * Entry file of the application - */ - entryFile?: string; - - /** - * Output directory of the application - */ - outDir?: string; -} - -interface RunConfigurationOptions { - /** - * Directory of the application - */ - dir?: string; -} - -export interface ConfigurationOptions { - /** - * Client configuration - */ - client?: { - /** - * Client port - */ - port?: string; - - /** - * Game exposure port - */ - gameExposurePort?: string; - - /** - * Build configuration - */ - build?: BuildConfigurationSchema; - - /** - * Runtime configuration - */ - runtime?: RunConfigurationSchema; - }; - - /** - * Server configuration - */ - server?: { - /** - * Enable server configuration - */ - enable?: boolean; - - /** - * Server port - */ - port?: string; - - /** - * Build configuration - */ - build?: BuildConfigurationSchema; - - /** - * Runtime configuration - */ - runtime?: RunConfigurationSchema; - }; -} +export interface ConfigurationOptions {} diff --git a/src/libs/configuration/configuration.schema.d.ts b/src/libs/configuration/configuration.schema.d.ts index f8dfa54..fb569f6 100644 --- a/src/libs/configuration/configuration.schema.d.ts +++ b/src/libs/configuration/configuration.schema.d.ts @@ -7,15 +7,20 @@ export interface ConfigurationSchema { /** * NanoForge application destination directory */ - directory?: string; + directory: string; /** * Configure a server for the application */ - server?: boolean; + server: boolean; /** * NanoForge Application language */ - language?: "js" | "ts"; + language: "js" | "ts"; + + /** + * Add init functions to the application + */ + initFunctions: boolean; } diff --git a/src/libs/configuration/schema.json b/src/libs/configuration/schema.json index 975ed6c..427d3da 100644 --- a/src/libs/configuration/schema.json +++ b/src/libs/configuration/schema.json @@ -7,15 +7,12 @@ "name": { "type": "string", "description": "The name of the application", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use for the new project ?" + "default": "nanoforge-app" }, "directory": { "type": "string", - "description": "NanoForge application destination directory" + "description": "NanoForge application destination directory", + "default": "." }, "server": { "type": "boolean", @@ -27,7 +24,11 @@ "enum": ["ts", "js"], "description": "NanoForge application language", "default": "ts" + }, + "initFunctions": { + "type": "boolean", + "description": "Add init functions to the main application", + "default": false } - }, - "required": ["name"] + } } diff --git a/src/libs/docker/docker.factory.ts b/src/libs/docker/docker.factory.ts index 2e30efd..34909a4 100644 --- a/src/libs/docker/docker.factory.ts +++ b/src/libs/docker/docker.factory.ts @@ -9,21 +9,14 @@ import { url, } from "@angular-devkit/schematics"; -import { toKebabCase } from "@utils/formatting"; -import { resolvePackageName } from "@utils/name"; - -import { DEFAULT_APP_NAME, DEFAULT_PACKAGE_MANAGER } from "~/defaults"; +import { DEFAULT_PACKAGE_MANAGER } from "~/defaults"; import { type DockerOptions } from "./docker.options"; import { type DockerSchema } from "./docker.schema"; const transform = (schema: DockerSchema): DockerOptions => { - const name = resolvePackageName(toKebabCase(schema.name?.toString() ?? DEFAULT_APP_NAME)); - return { - name, packageManager: schema.packageManager ?? DEFAULT_PACKAGE_MANAGER, - directory: schema.directory, }; }; @@ -39,5 +32,5 @@ const generate = (options: DockerOptions, path: string): Source => { export const main = (schema: DockerSchema): Rule => { const options = transform(schema); - return mergeWith(generate(options, schema.directory ?? options.name)); + return mergeWith(generate(options, schema.directory)); }; diff --git a/src/libs/docker/docker.options.d.ts b/src/libs/docker/docker.options.d.ts index cf16c22..489d255 100644 --- a/src/libs/docker/docker.options.d.ts +++ b/src/libs/docker/docker.options.d.ts @@ -1,16 +1,6 @@ export interface DockerOptions { - /** - * NanoForge application name - */ - name: string; - /** * NanoForge application package manager */ packageManager: "npm" | "yarn" | "pnpm" | "bun"; - - /** - * Target directory for generated Docker files - */ - directory?: string; } diff --git a/src/libs/docker/docker.schema.d.ts b/src/libs/docker/docker.schema.d.ts index 7e60641..f3c29da 100644 --- a/src/libs/docker/docker.schema.d.ts +++ b/src/libs/docker/docker.schema.d.ts @@ -1,16 +1,11 @@ export interface DockerSchema { /** - * NanoForge application name + * Target directory for generated Docker files */ - name: string; + directory: string; /** * NanoForge application package manager */ packageManager: "npm" | "yarn" | "pnpm" | "bun"; - - /** - * Target directory for generated Docker files - */ - directory?: string; } diff --git a/src/libs/docker/files/.dockerignore b/src/libs/docker/files/.dockerignore index 3c3629e..c2658d7 100644 --- a/src/libs/docker/files/.dockerignore +++ b/src/libs/docker/files/.dockerignore @@ -1 +1 @@ -node_modules +node_modules/ diff --git a/src/libs/docker/files/Dockerfile b/src/libs/docker/files/Dockerfile index 3085c72..b0e9eba 100644 --- a/src/libs/docker/files/Dockerfile +++ b/src/libs/docker/files/Dockerfile @@ -1,36 +1,22 @@ -<% if (packageManager === 'bun') { %> -FROM oven/bun:1.3 -<% } else { %> -FROM node:25-alpine -<% } %> - +<% if (packageManager === 'bun') { %>FROM oven/bun:1.3<% } else { %>FROM node:25-alpine<% } %> <% if (packageManager === 'pnpm') { %> ENV PNPM_HOME="/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN npm install -g pnpm -<% } %> - -<% if (packageManager === 'yarn') { %> +<% } %><% if (packageManager === 'yarn') { %> RUN npm install -g yarn <% } %> - WORKDIR /app + COPY package.json ./ -<% if (packageManager === 'npm') { %> -COPY package-lock.json ./ -<% } else if (packageManager === 'yarn') { %> -COPY yarn.lock ./ -<% } else if (packageManager === 'pnpm') { %> -COPY pnpm-lock.yaml ./ -<% } else if (packageManager === 'bun') { %> -COPY bun.lock ./ +<% if (packageManager === 'npm') { %>COPY package-lock.json ./ +<% } else if (packageManager === 'yarn') { %>COPY yarn.lock ./ +<% } else if (packageManager === 'pnpm') { %>COPY pnpm-lock.yaml ./ +<% } else if (packageManager === 'bun') { %>COPY bun.lock ./ <% } %> -<% if (packageManager === 'npm') { %> -RUN npm install --no-lockfile -<% } if (packageManager === 'pnpm') { %> -RUN pnpm install --frozen-lockfile --allow-build=bun -<% } else { %> -RUN <%= packageManager %> install --frozen-lockfile +<% if (packageManager === 'npm') { %>RUN npm install --no-lockfile +<% } else if (packageManager === 'pnpm') { %>RUN pnpm install --frozen-lockfile --allow-build=bun +<% } else { %>RUN <%= packageManager %> install --frozen-lockfile <% } %> COPY . . diff --git a/src/libs/docker/schema.json b/src/libs/docker/schema.json index 4ff6ac5..5055696 100644 --- a/src/libs/docker/schema.json +++ b/src/libs/docker/schema.json @@ -4,25 +4,16 @@ "title": "NanoForge Base Docker Options Schema", "type": "object", "properties": { - "name": { + "directory": { "type": "string", - "description": "The name of the application", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use for the new project ?" + "description": "NanoForge application destination directory", + "default": "." }, "packageManager": { "type": "string", "enum": ["npm", "yarn", "pnpm", "bun"], "description": "NanoForge application package manager", "default": "npm" - }, - "directory": { - "type": "string", - "description": "NanoForge application destination directory" } - }, - "required": ["name"] + } } diff --git a/src/libs/part-base/part-base.factory.ts b/src/libs/part-base/part-base.factory.ts index eb1ad52..2144656 100644 --- a/src/libs/part-base/part-base.factory.ts +++ b/src/libs/part-base/part-base.factory.ts @@ -10,33 +10,28 @@ import { url, } from "@angular-devkit/schematics"; -import { toKebabCase } from "@utils/formatting"; -import { resolvePackageName } from "@utils/name"; - -import { DEFAULT_APP_NAME, DEFAULT_LANGUAGE } from "~/defaults"; - import { type PartBaseOptions } from "./part-base.options"; import { type PartBaseSchema } from "./part-base.schema"; const transform = (schema: PartBaseSchema): PartBaseOptions => { - const name = resolvePackageName(toKebabCase(schema.name?.toString() ?? DEFAULT_APP_NAME)); - return { - name, part: schema.part, server: schema.server, appClass: schema.part === "client" ? "NanoforgeClient" : "NanoforgeServer", - language: schema.language ?? DEFAULT_LANGUAGE, - initFunctions: schema.initFunctions ?? false, + nanoforgeFolder: ".nanoforge", }; }; -const generate = (options: PartBaseOptions, path: string): Source => { +const generate = ( + options: PartBaseOptions, + path: string, + initFunctions: boolean, + language: "ts" | "js", +): Source => { const rules = [ template({ ...strings, ...options, - nanoforgeFolder: ".nanoforge", }), move(normalize(path)), filter((path) => { @@ -44,13 +39,13 @@ const generate = (options: PartBaseOptions, path: string): Source => { return splited.at(-2) !== ".nanoforge" || splited.at(-1) === `${options.part}.save.json`; }), ]; - if (!options.initFunctions) rules.push(filter((path) => path.split("/").at(-2) !== "init")); + if (!initFunctions) rules.push(filter((path) => path.split("/").at(-2) !== "init")); - return apply(url(join("./files" as Path, options.language)), rules); + return apply(url(join("./files" as Path, language)), rules); }; export const main = (schema: PartBaseSchema): Rule => { const options = transform(schema); - return mergeWith(generate(options, schema.directory ?? options.name)); + return mergeWith(generate(options, schema.directory, schema.initFunctions, schema.language)); }; diff --git a/src/libs/part-base/part-base.options.d.ts b/src/libs/part-base/part-base.options.d.ts index 3d811a1..9e5934b 100644 --- a/src/libs/part-base/part-base.options.d.ts +++ b/src/libs/part-base/part-base.options.d.ts @@ -1,9 +1,4 @@ export interface PartBaseOptions { - /** - * NanoForge application name - */ - name: string; - /** * The part of the application to generate */ @@ -15,17 +10,12 @@ export interface PartBaseOptions { appClass: string; /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * Add init functions to the application + * Configure a server for the application */ - initFunctions: boolean; + server: boolean; /** - * Configure a server for the application + * Name of Nanoforge folder */ - server?: boolean; + nanoforgeFolder: string; } diff --git a/src/libs/part-base/part-base.schema.d.ts b/src/libs/part-base/part-base.schema.d.ts index d417958..d708ed0 100644 --- a/src/libs/part-base/part-base.schema.d.ts +++ b/src/libs/part-base/part-base.schema.d.ts @@ -1,31 +1,26 @@ export interface PartBaseSchema { /** - * NanoForge application name + * NanoForge application destination directory */ - name: string; + directory: string; /** * The part of the application to generate */ part: "client" | "server"; - /** - * NanoForge application destination directory - */ - directory?: string; - /** * NanoForge Application language */ - language?: "js" | "ts"; + language: "js" | "ts"; /** * Add init functions to the application */ - initFunctions?: boolean; + initFunctions: boolean; /** * Configure a server for the application */ - server?: boolean; + server: boolean; } diff --git a/src/libs/part-base/schema.json b/src/libs/part-base/schema.json index f10d0d3..95d3bc1 100644 --- a/src/libs/part-base/schema.json +++ b/src/libs/part-base/schema.json @@ -4,24 +4,16 @@ "title": "NanoForge Part Base Options Schema", "type": "object", "properties": { - "name": { + "directory": { "type": "string", - "description": "The name of the application", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use for the new project ?" + "description": "NanoForge application destination directory", + "default": "." }, "part": { "type": "string", "enum": ["client", "server"], "description": "The part of the application to generate" }, - "directory": { - "type": "string", - "description": "NanoForge application destination directory" - }, "language": { "type": "string", "enum": ["ts", "js"], @@ -39,5 +31,5 @@ "default": false } }, - "required": ["name", "part"] + "required": ["part"] } diff --git a/src/libs/part-main/part-main.factory.ts b/src/libs/part-main/part-main.factory.ts index 4af261f..533238e 100644 --- a/src/libs/part-main/part-main.factory.ts +++ b/src/libs/part-main/part-main.factory.ts @@ -9,31 +9,21 @@ import { move, template, } from "@angular-devkit/schematics"; -import * as fs from "node:fs"; +import * as fs from "fs"; -import { toKebabCase } from "@utils/formatting"; import { generateMain } from "@utils/main/main-functions"; import { type Save } from "@utils/main/save.type"; -import { resolvePackageName } from "@utils/name"; - -import { DEFAULT_APP_NAME, DEFAULT_LANGUAGE } from "~/defaults"; import { type PartMainOptions } from "./part-main.options"; import { type PartMainSchema } from "./part-main.schema"; const transform = (schema: PartMainSchema): PartMainOptions => { - const name = resolvePackageName(toKebabCase(schema.name?.toString() ?? DEFAULT_APP_NAME)); + void schema; - return { - name, - part: schema.part, - language: schema.language ?? DEFAULT_LANGUAGE, - initFunctions: schema.initFunctions ?? false, - saveFile: schema.saveFile, - }; + return {}; }; -const generate = (options: PartMainOptions, path: string, editor: boolean = false): Source => { +const generate = (options: PartMainOptions, path: string, schema: PartMainSchema): Source => { const rules = [ template({ ...strings, @@ -42,17 +32,23 @@ const generate = (options: PartMainOptions, path: string, editor: boolean = fals move(normalize(path)), ]; - return apply(asSource(writeMain(options, path, editor)), rules); + return apply(asSource(writeMain(schema, path)), rules); +}; + +const getMainPath = (schema: PartMainSchema): string => { + if (schema.outFile) return schema.outFile; + if (schema.editor) + return join(".nanoforge/editor" as Path, schema.part, `main.${schema.language}`); + return join(schema.part as Path, `main.${schema.language}`); }; -const writeMain = (options: PartMainOptions, path: string, editor: boolean) => { +const writeMain = (schema: PartMainSchema, path: string) => { return (tree: Tree) => { - const save = getSave(path, options.part, options.saveFile); + const save = getSave(path, schema.part, schema.saveFile); - let resPath = join(options.part as Path, `main.${options.language}`); - if (editor) resPath = join(".nanoforge/editor" as Path, resPath); + const resPath = getMainPath(schema); - const content = generateMain(options, save, editor); + const content = generateMain(schema, save); fs.rmSync(join(path as Path, resPath), { force: true }); tree.create(resPath, content); return tree; @@ -68,5 +64,5 @@ const getSave = (path: string, part: "client" | "server", saveFile?: string): Sa export const main = (schema: PartMainSchema): Rule => { const options = transform(schema); - return mergeWith(generate(options, schema.directory ?? options.name, schema.editor)); + return mergeWith(generate(options, schema.directory, schema)); }; diff --git a/src/libs/part-main/part-main.options.d.ts b/src/libs/part-main/part-main.options.d.ts index a7e9ec9..d16f512 100644 --- a/src/libs/part-main/part-main.options.d.ts +++ b/src/libs/part-main/part-main.options.d.ts @@ -1,26 +1 @@ -export interface PartMainOptions { - /** - * NanoForge application name - */ - name: string; - - /** - * The part of the application to generate - */ - part: "client" | "server"; - - /** - * NanoForge Application language - */ - language: "js" | "ts"; - - /** - * Add init functions to the application - */ - initFunctions: boolean; - - /** - * Save file path with components and systems in JSON format - */ - saveFile?: string; -} +export interface PartMainOptions {} diff --git a/src/libs/part-main/part-main.schema.d.ts b/src/libs/part-main/part-main.schema.d.ts index cf7f65e..f2a474a 100644 --- a/src/libs/part-main/part-main.schema.d.ts +++ b/src/libs/part-main/part-main.schema.d.ts @@ -1,36 +1,36 @@ export interface PartMainSchema { /** - * NanoForge application name + * NanoForge application destination directory */ - name: string; + directory: string; /** * The part of the application to generate */ part: "client" | "server"; - /** - * NanoForge application destination directory - */ - directory?: string; - /** * NanoForge Application language */ - language?: "js" | "ts"; + language: "js" | "ts"; /** * Add init functions to the application */ - initFunctions?: boolean; + initFunctions: boolean; /** * Save file path with components and systems in JSON format */ saveFile?: string; + /** + * Path of the out file + */ + outFile?: string; + /** * Build main editor */ - editor?: boolean; + editor: boolean; } diff --git a/src/libs/part-main/schema.json b/src/libs/part-main/schema.json index 0b79dd4..d2f84a8 100644 --- a/src/libs/part-main/schema.json +++ b/src/libs/part-main/schema.json @@ -4,24 +4,16 @@ "title": "NanoForge Part Main Options Schema", "type": "object", "properties": { - "name": { + "directory": { "type": "string", - "description": "The name of the application", - "$default": { - "$source": "argv", - "index": 0 - }, - "x-prompt": "What name would you like to use for the new project ?" + "description": "NanoForge application destination directory", + "default": "." }, "part": { "type": "string", "enum": ["client", "server"], "description": "The part of the application to generate" }, - "directory": { - "type": "string", - "description": "NanoForge application destination directory" - }, "language": { "type": "string", "enum": ["ts", "js"], @@ -37,11 +29,15 @@ "type": "string", "description": "Save file path with components and systems in JSON format" }, + "outFile": { + "type": "string", + "description": "Path of the out file" + }, "editor": { "type": "boolean", "description": "Build main editor", "default": false } }, - "required": ["name", "part"] + "required": ["part"] } diff --git a/src/utils/config/config.declarator.spec.ts b/src/utils/config/config.declarator.spec.ts index c7ff90e..6d4c34b 100644 --- a/src/utils/config/config.declarator.spec.ts +++ b/src/utils/config/config.declarator.spec.ts @@ -3,10 +3,6 @@ import { describe, expect, it } from "vitest"; import { ConfigDeclarator } from "./config.declarator"; describe("ConfigDeclarator", () => { - // NOTE: deepMerge mutates DEFAULT_CONFIG in place, so tests that enable - // server config will permanently pollute the global default for subsequent - // calls. Tests are ordered to account for this side-effect. - it("should return a valid JSON string", () => { const result = new ConfigDeclarator().declare("{}", {}); expect(() => JSON.parse(result)).not.toThrow(); @@ -14,10 +10,7 @@ describe("ConfigDeclarator", () => { it("should include default client config", () => { const result = JSON.parse(new ConfigDeclarator().declare("{}", {})); - expect(result.client).toBeDefined(); - expect(result.client.build.entryFile).toBe("client/main.ts"); - expect(result.client.build.outDir).toBe(".nanoforge/client"); - expect(result.client.runtime.dir).toBe(".nanoforge/client"); + expect(result.client).toBeUndefined(); }); it("should not include server config when server is not enabled", () => { @@ -26,7 +19,7 @@ describe("ConfigDeclarator", () => { }); it("should format output with 2-space indentation", () => { - const result = new ConfigDeclarator().declare("{}", {}); + const result = new ConfigDeclarator().declare('{ "server": { "enable": true } }', {}); expect(result).toContain("\n "); }); @@ -35,7 +28,6 @@ describe("ConfigDeclarator", () => { const result = JSON.parse(new ConfigDeclarator().declare(baseContent, {})); expect(result.server).toBeDefined(); expect(result.server.enable).toBe(true); - expect(result.server.build.entryFile).toBe("server/main.ts"); }); it("should include server config when options has server.enable true", () => { diff --git a/src/utils/config/config.declarator.ts b/src/utils/config/config.declarator.ts index a9101f2..1ad2405 100644 --- a/src/utils/config/config.declarator.ts +++ b/src/utils/config/config.declarator.ts @@ -2,21 +2,9 @@ import { type Config } from "@utils/config/config.type"; import { deepMerge } from "@utils/object"; import { type DeepPartial } from "@utils/types"; -import { DEFAULT_CONFIG, DEFAULT_SERVER_CONFIG } from "~/defaults"; - export class ConfigDeclarator { public declare(baseContent: string, options: DeepPartial): string { const content = JSON.parse(baseContent); - const hasServer = content.server?.enable ?? options.server?.enable ?? false; - return JSON.stringify( - deepMerge( - DEFAULT_CONFIG, - hasServer ? DEFAULT_SERVER_CONFIG : undefined, - baseContent, - options, - ), - null, - 2, - ); + return JSON.stringify(deepMerge(content, options), null, 2); } } diff --git a/src/utils/config/config.type.ts b/src/utils/config/config.type.ts index 4779b7f..a0df082 100644 --- a/src/utils/config/config.type.ts +++ b/src/utils/config/config.type.ts @@ -1,27 +1,31 @@ -interface BuildConfig { - entryFile: string; - outDir: string; +export interface BuildConfig { + entry: string; } -interface RunConfig { - dir: string; +export interface EditorConfig { + entry: string; + save: string; } -interface ClientConfig { +export interface ClientConfig { + enable: boolean; port: string; - gameExposurePort: string; + outDir: string; build: BuildConfig; - runtime: RunConfig; + editor: EditorConfig; } -interface ServerConfig { +export interface ServerConfig { enable: boolean; - port: string; + outDir: string; build: BuildConfig; - runtime: RunConfig; + editor: EditorConfig; } export interface Config { + name: string; + language: "ts" | "js"; + initFunctions: boolean; client: ClientConfig; server: ServerConfig; } diff --git a/src/utils/main/main-functions.ts b/src/utils/main/main-functions.ts index 973f237..dcf50f7 100644 --- a/src/utils/main/main-functions.ts +++ b/src/utils/main/main-functions.ts @@ -9,14 +9,26 @@ export interface MainOptions { part: "client" | "server"; language: "js" | "ts"; initFunctions: boolean; + editor: boolean; + outFile?: string; } -export const generateMain = (options: MainOptions, save: Save, editor: boolean = false) => { +export const generateMain = (options: MainOptions, save: Save) => { const isServer = options.part === "server"; const hasTypes = options.language === "ts"; const initFunctions = options.initFunctions; - return (!editor ? new MainGenerator() : new MainGenerator(true, join("../../..", options.part))) + return ( + !options.editor + ? new MainGenerator() + : new MainGenerator( + true, + join( + "../".repeat(options.outFile ? options.outFile.split("/").length - 1 : 3), + options.part, + ), + ) + ) .generateBaseImports(hasTypes) .generateLibsImports(save.libraries) .generateInitFunctionsImportsIfNeeded(initFunctions)