diff --git a/e2e/cli-generate.test.ts b/e2e/cli-generate.test.ts index cdf1b57..a104a26 100644 --- a/e2e/cli-generate.test.ts +++ b/e2e/cli-generate.test.ts @@ -32,7 +32,7 @@ describe("nf generate (TypeScript, no server)", () => { "--strict", "--no-server", "--init-functions", - "--no-skip-install", + "--skip-install", "--no-docker", "-d", projectDir, @@ -208,6 +208,16 @@ describe("nf generate (TypeScript, no server)", () => { it("should not generate server directory", async () => { expect(existsSync(resolve(appDir, "server"))).toBe(false); }); + + it("should generate client editor main.ts", async () => { + await runCli(["generate", "-d", appDir, "--editor"]); + + const content = readFileSync(resolve(appDir, ".nanoforge/editor/client/main.ts"), "utf-8"); + + expect(content).toContain("NanoforgeFactory.createClient()"); + expect(content).toContain('import { ECSClientLibrary } from "@nanoforge-dev/ecs-client"'); + expect(content).toContain('from "@nanoforge-dev/core-editor"'); + }); }); describe("nf generate (TypeScript, with server)", () => { @@ -228,7 +238,7 @@ describe("nf generate (TypeScript, with server)", () => { "--no-strict", "--server", "--init-functions", - "--no-skip-install", + "--skip-install", "--no-docker", "-d", projectDir, diff --git a/e2e/cli-new-config.test.ts b/e2e/cli-new-config.test.ts index c8943df..26fa1b8 100644 --- a/e2e/cli-new-config.test.ts +++ b/e2e/cli-new-config.test.ts @@ -43,13 +43,8 @@ describe("nf new config output (no server)", () => { ); }); - it("should have client build config", () => { - expect(config.client.build.entryFile).toBe("client/main.ts"); - expect(config.client.build.outDir).toBe(".nanoforge/client"); - }); - - it("should have client runtime config", () => { - expect(config.client.runtime.dir).toBe(".nanoforge/client"); + it("should have client config", () => { + expect(config.client.enable).toBe(true); }); it("should not have server enabled", () => { @@ -90,18 +85,8 @@ describe("nf new config output (with server)", () => { expect(config.server.enable).toBe(true); }); - it("should have server build config", () => { - expect(config.server.build.entryFile).toBe("server/main.ts"); - expect(config.server.build.outDir).toBe(".nanoforge/server"); - }); - - it("should have server runtime config", () => { - expect(config.server.runtime.dir).toBe(".nanoforge/server"); - }); - it("should have client build config", () => { - expect(config.client.build.entryFile).toBe("client/main.ts"); - expect(config.client.build.outDir).toBe(".nanoforge/client"); + expect(config.client.enable).toBe(true); }); }); @@ -125,6 +110,7 @@ describe("nf new package.json output", () => { "--no-init-functions", "--skip-install", "--no-docker", + "--editor", "-d", projectDir, ]); @@ -139,5 +125,6 @@ describe("nf new package.json output", () => { it("should have nanoforge dependencies", () => { const allDeps = { ...pkg.dependencies, ...pkg.devDependencies }; expect(allDeps).toHaveProperty("@nanoforge-dev/core"); + expect(allDeps).toHaveProperty("@nanoforge-dev/core-editor"); }); }); diff --git a/e2e/cli-new.test.ts b/e2e/cli-new.test.ts index c867a16..b214510 100644 --- a/e2e/cli-new.test.ts +++ b/e2e/cli-new.test.ts @@ -54,7 +54,7 @@ describe("nf new (TypeScript, no server)", () => { const config = JSON.parse(readFileSync(configPath, "utf-8")); expect(config.client).toBeDefined(); - expect(config.client.build.entryFile).toBe("client/main.ts"); + expect(config.client.enable).toBe(true); }); it("should generate package.json", () => { @@ -228,7 +228,7 @@ describe("nf new (with typescript with docker option)", () => { const config = JSON.parse(readFileSync(configPath, "utf-8")); expect(config.client).toBeDefined(); - expect(config.client.build.entryFile).toBe("client/main.ts"); + expect(config.client.enable).toBe(true); }); it("should generate package.json", () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a175d9..63716bd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,10 +14,10 @@ catalogs: version: 5.9.3 ci: '@commitlint/cli': - specifier: ^20.4.4 + specifier: ^20.5.0 version: 20.5.0 '@commitlint/config-conventional': - specifier: ^20.4.4 + specifier: ^20.5.0 version: 20.5.0 '@favware/cliff-jumper': specifier: ^6.0.0 @@ -33,7 +33,7 @@ catalogs: version: 16.4.0 cli: '@inquirer/prompts': - specifier: ^8.3.0 + specifier: ^8.3.2 version: 8.3.2 '@types/inquirer': specifier: ^9.0.9 @@ -107,8 +107,8 @@ catalogs: specifier: ^21.2.2 version: 21.2.2 '@nanoforge-dev/schematics': - specifier: ^1.2.2 - version: 1.2.2 + specifier: ^2.0.0 + version: 2.0.0 tests: '@vitest/coverage-v8': specifier: ^4.1.0 @@ -138,7 +138,7 @@ importers: version: 1.2.0 '@nanoforge-dev/schematics': specifier: catalog:schematics - version: 1.2.2(chokidar@5.0.0) + version: 2.0.0(chokidar@5.0.0) ansis: specifier: catalog:cli version: 4.2.0 @@ -202,7 +202,7 @@ importers: version: 25.5.0 '@vitest/coverage-v8': specifier: catalog:tests - version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2))) + version: 4.1.0(vitest@4.1.0(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2))) eslint: specifier: catalog:lint version: 10.0.3(jiti@2.6.1) @@ -223,7 +223,7 @@ importers: version: 5.9.3 vitest: specifier: catalog:tests - version: 4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2)) + version: 4.1.0(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2)) packages: @@ -406,15 +406,6 @@ packages: '@dprint/toml@0.7.0': resolution: {integrity: sha512-eFaQTcfxKHB+YyTh83x7GEv+gDPuj9q5NFOTaoj5rZmQTbj6OgjjMxUicmS1R8zYcx8YAq5oA9J3YFa5U6x2gA==} - '@emnapi/core@1.9.0': - resolution: {integrity: sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==} - - '@emnapi/runtime@1.9.0': - resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==} - - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} - '@esbuild/aix-ppc64@0.27.4': resolution: {integrity: sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==} engines: {node: '>=18'} @@ -927,8 +918,8 @@ packages: resolution: {integrity: sha512-gKFEQFt1+RIQyRzpVyjuaXib4nBLa4g3LNfy9TEMPlXe21IZy346L3RO5PmlVslPAYJuy5chUXfBE+BmE9BedA==} engines: {node: '25'} - '@nanoforge-dev/schematics@1.2.2': - resolution: {integrity: sha512-OMw6k8eh5TlwPIwlbem/SozPCb+lkmHtSIcxI8LqDG0CLwC0YzJ3WIZ0uLuEhATbVSHZXy7tstS92XZV4ZlEFg==} + '@nanoforge-dev/schematics@2.0.0': + resolution: {integrity: sha512-mt0Uvi+GQE/B+TPg+u7dbc1i2bS/5RFSH0q4WnWaPVD4pEHhh1c9BPxT4QKo4uWgR6JPNuATGQ6minbUJytgcQ==} engines: {node: '25'} '@nanoforge-dev/utils-eslint-config@1.0.2': @@ -939,9 +930,6 @@ packages: resolution: {integrity: sha512-dTa2ixPJmWVD+L13Iuxx51JIm3gaX+gnRCe8jA0PFw0xjMjLYv+0ZgbWSSVlNmowp6fqpOzfPdWADkkPNQs8LQ==} engines: {node: '25'} - '@napi-rs/wasm-runtime@1.1.1': - resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@octokit/auth-token@5.1.2': resolution: {integrity: sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==} engines: {node: '>= 18'} @@ -1080,13 +1068,6 @@ packages: cpu: [x64] os: [win32] - '@oxc-project/runtime@0.115.0': - resolution: {integrity: sha512-Rg8Wlt5dCbXhQnsXPrkOjL1DTSvXLgb2R/KYfnf1/K+R0k6UMLEmbQXPM+kwrWqSmWA2t0B1EtHy2/3zikQpvQ==} - engines: {node: ^20.19.0 || >=22.12.0} - - '@oxc-project/types@0.115.0': - resolution: {integrity: sha512-4n91DKnebUS4yjUHl2g3/b2T+IUdCfmoZGhmwsovZCDaJSs+QkVAM+0AqqTxHSsHfeiMuueT75cZaZcT/m0pSw==} - '@oxfmt/binding-android-arm-eabi@0.35.0': resolution: {integrity: sha512-BaRKlM3DyG81y/xWTsE6gZiv89F/3pHe2BqX2H4JbiB8HNVlWWtplzgATAE5IDSdwChdeuWLDTQzJ92Lglw3ZA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1205,98 +1186,6 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-lHVNUG/8nlF1IQk1C0Ci574qKYyty2goMiPlRqkC5R+3LkXDkL5Dhx8ytbxq35m+pkHVIvIxviD+TWLdfeuadA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.0-rc.9': - resolution: {integrity: sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw==} - '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -1471,9 +1360,6 @@ packages: svelte: optional: true - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} - '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -1860,10 +1746,6 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -2062,8 +1944,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.4.1: - resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} @@ -2296,76 +2178,6 @@ packages: libphonenumber-js@1.12.40: resolution: {integrity: sha512-HKGs7GowShNls3Zh+7DTr6wYpPk5jC78l508yQQY3e8ZgJChM3A9JZghmMJZuK+5bogSfuTafpjksGSR3aMIEg==} - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -2647,11 +2459,6 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rolldown@1.0.0-rc.9: - resolution: {integrity: sha512-9EbgWge7ZH+yqb4d2EnELAntgPTWbfL8ajiTW+SyhJEC4qhBbkCKbqFV4Ge4zmu5ziQuVbWxb/XwLZ+RIO7E8Q==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2878,16 +2685,15 @@ packages: resolution: {integrity: sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==} engines: {node: '>= 0.10'} - vite@8.0.0: - resolution: {integrity: sha512-fPGaRNj9Zytaf8LEiBhY7Z6ijnFKdzU/+mL8EFBaKr7Vw1/FWcTBAMW0wLPJAGMPX38ZPVCVgLceWiEqeoqL2Q==} + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.0.0-alpha.31 - esbuild: ^0.27.0 jiti: '>=1.21.0' less: ^4.0.0 + lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -2898,14 +2704,12 @@ packages: peerDependenciesMeta: '@types/node': optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true jiti: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true sass-embedded: @@ -3260,22 +3064,6 @@ snapshots: '@dprint/toml@0.7.0': {} - '@emnapi/core@1.9.0': - dependencies: - '@emnapi/wasi-threads': 1.2.0 - tslib: 2.8.1 - optional: true - - '@emnapi/runtime@1.9.0': - dependencies: - tslib: 2.8.1 - optional: true - - '@emnapi/wasi-threads@1.2.0': - dependencies: - tslib: 2.8.1 - optional: true - '@esbuild/aix-ppc64@0.27.4': optional: true @@ -3694,7 +3482,7 @@ snapshots: '@nanoforge-dev/loader-website@1.2.0': {} - '@nanoforge-dev/schematics@1.2.2(chokidar@5.0.0)': + '@nanoforge-dev/schematics@2.0.0(chokidar@5.0.0)': dependencies: '@angular-devkit/core': 21.2.2(chokidar@5.0.0) '@angular-devkit/schematics': 21.2.2(chokidar@5.0.0) @@ -3721,13 +3509,6 @@ snapshots: '@nanoforge-dev/utils-prettier-config@1.0.2': {} - '@napi-rs/wasm-runtime@1.1.1': - dependencies: - '@emnapi/core': 1.9.0 - '@emnapi/runtime': 1.9.0 - '@tybys/wasm-util': 0.10.1 - optional: true - '@octokit/auth-token@5.1.2': {} '@octokit/auth-token@6.0.0': {} @@ -3864,10 +3645,6 @@ snapshots: '@oven/bun-windows-x64@1.3.10': optional: true - '@oxc-project/runtime@0.115.0': {} - - '@oxc-project/types@0.115.0': {} - '@oxfmt/binding-android-arm-eabi@0.35.0': optional: true @@ -3927,55 +3704,6 @@ snapshots: '@pkgr/core@0.2.9': {} - '@rolldown/binding-android-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-darwin-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-freebsd-x64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': - optional: true - - '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': - optional: true - - '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': - optional: true - - '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': - dependencies: - '@napi-rs/wasm-runtime': 1.1.1 - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': - optional: true - - '@rolldown/pluginutils@1.0.0-rc.9': {} - '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -4083,11 +3811,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@tybys/wasm-util@0.10.1': - dependencies: - tslib: 2.8.1 - optional: true - '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -4214,7 +3937,7 @@ snapshots: '@typescript-eslint/types': 8.57.1 eslint-visitor-keys: 5.0.1 - '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2)))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2)))': dependencies: '@bcoe/v8-coverage': 1.0.2 '@vitest/utils': 4.1.0 @@ -4226,7 +3949,7 @@ snapshots: obug: 2.1.1 std-env: 4.0.0 tinyrainbow: 3.1.0 - vitest: 4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2)) + vitest: 4.1.0(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2)) '@vitest/expect@4.1.0': dependencies: @@ -4237,13 +3960,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2))': + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2) '@vitest/pretty-format@4.1.0': dependencies: @@ -4490,8 +4213,6 @@ snapshots: destr@2.0.5: {} - detect-libc@2.1.2: {} - dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -4732,10 +4453,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.4.1 + flatted: 3.4.2 keyv: 4.5.4 - flatted@3.4.1: {} + flatted@3.4.2: {} fsevents@2.3.3: optional: true @@ -4907,55 +4628,6 @@ snapshots: libphonenumber-js@1.12.40: {} - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -5233,27 +4905,6 @@ snapshots: rfdc@1.4.1: {} - rolldown@1.0.0-rc.9: - dependencies: - '@oxc-project/types': 0.115.0 - '@rolldown/pluginutils': 1.0.0-rc.9 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.9 - '@rolldown/binding-darwin-x64': 1.0.0-rc.9 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.9 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.9 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.9 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.9 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.9 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.9 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.9 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.9 - rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -5481,25 +5132,24 @@ snapshots: validator@13.15.26: {} - vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2): + vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2): dependencies: - '@oxc-project/runtime': 0.115.0 - lightningcss: 1.32.0 + esbuild: 0.27.4 + fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.8 - rolldown: 1.0.0-rc.9 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.5.0 - esbuild: 0.27.4 fsevents: 2.3.3 jiti: 2.6.1 yaml: 2.8.2 - vitest@4.1.0(@types/node@25.5.0)(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2)): + vitest@4.1.0(@types/node@25.5.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2)) + '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.0 '@vitest/runner': 4.1.0 '@vitest/snapshot': 4.1.0 @@ -5516,7 +5166,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.0(@types/node@25.5.0)(esbuild@0.27.4)(jiti@2.6.1)(yaml@2.8.2) + vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index eec42d0..d890146 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,14 +3,14 @@ catalogs: tsup: ^8.5.1 typescript: ^5.9.3 ci: - '@commitlint/cli': ^20.4.4 - '@commitlint/config-conventional': ^20.4.4 + '@commitlint/cli': ^20.5.0 + '@commitlint/config-conventional': ^20.5.0 '@favware/cliff-jumper': ^6.0.0 '@nanoforge-dev/actions': ^1.2.3 husky: ^9.1.7 lint-staged: ^16.4.0 cli: - '@inquirer/prompts': ^8.3.0 + '@inquirer/prompts': ^8.3.2 '@types/inquirer': ^9.0.9 ansis: ^4.2.0 commander: ^14.0.3 @@ -38,7 +38,7 @@ catalogs: schematics: '@angular-devkit/schematics': ^21.2.2 '@angular-devkit/schematics-cli': ^21.2.2 - '@nanoforge-dev/schematics': ^1.2.2 + '@nanoforge-dev/schematics': ^2.0.0 tests: '@vitest/coverage-v8': ^4.1.0 vitest: ^4.1.0 diff --git a/src/action/actions/build.action.ts b/src/action/actions/build.action.ts index 25b646a..4ab80be 100644 --- a/src/action/actions/build.action.ts +++ b/src/action/actions/build.action.ts @@ -1,8 +1,14 @@ import { watch } from "chokidar"; import { dirname, join } from "node:path"; -import { type BuildConfig, type Config } from "@lib/config"; -import { type Input, getDirectoryInput, getStringInput, getWatchInput } from "@lib/input"; +import { type Config } from "@lib/config"; +import { + type Input, + getDirectoryInput, + getEditorInput, + getStringInputWithDefault, + getWatchInput, +} from "@lib/input"; import { PackageManagerFactory, PackageManagerName } from "@lib/package-manager"; import { Messages } from "@lib/ui"; @@ -28,9 +34,10 @@ export class BuildAction extends AbstractAction { public async handle(_args: Input, options: Input): Promise { const directory = getDirectoryInput(options); const config = await getConfig(options, directory); + const isEditor = getEditorInput(options); const isWatch = getWatchInput(options); - const targets = this.resolveTargets(config, options); + const targets = this.resolveTargets(config, options, isEditor); const results = await this.buildAll(targets, directory, isWatch); if (isWatch) { @@ -40,40 +47,50 @@ export class BuildAction extends AbstractAction { return { success: results.every(Boolean) }; } - private resolveTargets(config: Config, options: Input): BuildTarget[] { - const targets: BuildTarget[] = [ - this.createTarget( - "Client", - config.client.build, - "browser", - getStringInput(options, "clientDirectory"), - ), - ]; - - if (config.server.enable) { + private resolveTargets(config: Config, options: Input, isEditor: boolean): BuildTarget[] { + const targets: BuildTarget[] = []; + + if (config.client.enable) + targets.push( + this.createTarget( + "Client", + + "browser", + getStringInputWithDefault( + options, + "clientEntry", + !isEditor ? config.client.build.entry : config.client.editor.entry, + ), + getStringInputWithDefault(options, "clientOutDir", config.client.outDir), + ), + ); + if (config.server.enable) targets.push( this.createTarget( "Server", - config.server.build, "node", - getStringInput(options, "serverDirectory"), + getStringInputWithDefault( + options, + "serverEntry", + !isEditor ? config.server.build.entry : config.server.editor.entry, + ), + getStringInputWithDefault(options, "serverOutDir", config.server.outDir), ), ); - } return targets; } private createTarget( name: string, - config: BuildConfig, platform: "browser" | "node", - outDirOverride?: string, + entryFile: string, + outDir: string, ): BuildTarget { return { name, - entry: config.entryFile, - output: outDirOverride || config.outDir, + entry: entryFile, + output: outDir, platform, }; } diff --git a/src/action/actions/generate.action.ts b/src/action/actions/generate.action.ts index ac101cf..06389cf 100644 --- a/src/action/actions/generate.action.ts +++ b/src/action/actions/generate.action.ts @@ -1,23 +1,19 @@ import { join } from "node:path"; import { type Config } from "@lib/config"; -import { NANOFORGE_DIR } from "@lib/constants"; -import { type Input, getDirectoryInput, getWatchInput } from "@lib/input"; +import { type Input, getDirectoryInput, getEditorInput, getWatchInput } from "@lib/input"; import { Collection, CollectionFactory } from "@lib/schematics"; import { Messages } from "@lib/ui"; import { getCwd } from "@utils/path"; -import { getConfig } from "~/action/common/config"; - import { AbstractAction, type HandleResult } from "../abstract.action"; +import { getConfig } from "../common/config"; import { executeSchematic } from "../common/schematics"; interface GenerateValues { - name: string; directory: string; language: string; - server: boolean; initFunctions: boolean; } @@ -29,10 +25,10 @@ export class GenerateAction extends AbstractAction { public async handle(_args: Input, options: Input): Promise { const directory = getDirectoryInput(options); const config = await getConfig(options, directory); + const isEditor = getEditorInput(options); const isWatch = getWatchInput(options); - const values = this.extractValues(config); - await this.generateParts(values, directory, isWatch); + await this.generateParts(config, directory, isEditor, isWatch); if (isWatch) { return this.enterWatchMode(); @@ -43,52 +39,54 @@ export class GenerateAction extends AbstractAction { private extractValues(config: Config): GenerateValues { return { - name: config.name, directory: ".", language: config.language, - server: config.server.enable, initFunctions: config.initFunctions, }; } private async generateParts( - values: GenerateValues, + config: Config, directory: string, + isEditor: boolean, watch: boolean, ): Promise { const collection = CollectionFactory.create(Collection.NANOFORGE, directory); - const baseOptions = this.baseSchematicOptions(values); + const values = this.extractValues(config); - await executeSchematic( - "Client main file", - collection, - "part-main", - { ...baseOptions, part: "client" }, - watch ? this.watchPath(directory, values.directory, "client") : undefined, - ); + if (config.client.enable) + await executeSchematic( + "Client main file", + collection, + "part-main", + { + ...values, + part: "client", + outFile: !isEditor ? config.client.build.entry : config.client.editor.entry, + saveFile: config.client.editor.save, + editor: isEditor, + }, + watch ? this.watchPath(directory, values.directory, config.client.editor.save) : undefined, + ); - if (values.server) { + if (config.server.enable) await executeSchematic( "Server main file", collection, "part-main", - { ...baseOptions, part: "server" }, - this.watchPath(directory, values.directory, "server"), + { + ...values, + part: "server", + outFile: !isEditor ? config.server.build.entry : config.server.editor.entry, + saveFile: config.server.editor.save, + editor: isEditor, + }, + this.watchPath(directory, values.directory, config.server.editor.save), ); - } - } - - private baseSchematicOptions(values: GenerateValues) { - return { - name: values.name, - directory: values.directory, - language: values.language, - initFunctions: values.initFunctions, - }; } - private watchPath(directory: string, subDir: string, part: string): string { - return join(getCwd(directory), subDir, NANOFORGE_DIR, `${part}.save.json`); + private watchPath(directory: string, subDir: string, saveFile: string): string { + return join(getCwd(directory), subDir, saveFile); } private enterWatchMode(): HandleResult { diff --git a/src/action/actions/new.action.ts b/src/action/actions/new.action.ts index d102813..9f7aeb9 100644 --- a/src/action/actions/new.action.ts +++ b/src/action/actions/new.action.ts @@ -3,6 +3,7 @@ import { join } from "node:path"; import { type Input, getDirectoryInput, + getEditorInput, getNewDockerOrAsk, getNewInitFunctionsWithDefault, getNewLanguageInputOrAsk, @@ -32,6 +33,7 @@ interface NewValues { skipInstall: boolean; docker: boolean; lint: boolean; + editor: boolean; } export class NewAction extends AbstractAction { @@ -69,6 +71,7 @@ export class NewAction extends AbstractAction { skipInstall: await getNewSkipInstallOrAsk(inputs), docker: await getNewDockerOrAsk(inputs), lint: getNewLintInput(inputs), + editor: getEditorInput(inputs), }; } @@ -100,6 +103,7 @@ export class NewAction extends AbstractAction { strict: values.strict, server: values.server, lint: values.lint, + editor: values.editor, }); } @@ -109,8 +113,10 @@ export class NewAction extends AbstractAction { ) { return executeSchematic("Configuration", collection, "configuration", { name: values.name, - directory: values.directory, + directory: values.directory ?? values.name, server: values.server, + language: values.language, + initFunctions: values.initFunctions, }); } @@ -124,7 +130,9 @@ export class NewAction extends AbstractAction { ...partOptions, server: values.server, }); - await executeSchematic("Client main file", collection, "part-main", partOptions); + await executeSchematic("Client main file", collection, "part-main", { + ...partOptions, + }); } private async generateServerParts( @@ -137,7 +145,9 @@ export class NewAction extends AbstractAction { ...partOptions, server: values.server, }); - await executeSchematic("Server main file", collection, "part-main", partOptions); + await executeSchematic("Server main file", collection, "part-main", { + ...partOptions, + }); } private async generateDocker( @@ -145,17 +155,15 @@ export class NewAction extends AbstractAction { values: NewValues, ) { await executeSchematic("Docker", collection, "docker", { - name: values.name, - directory: values.directory, + directory: values.directory ?? values.name, packageManager: values.packageManager, }); } private partOptions(values: NewValues, part: "client" | "server") { return { - name: values.name, part, - directory: values.directory, + directory: values.directory ?? values.name, language: values.language, initFunctions: values.initFunctions, }; diff --git a/src/action/actions/start.action.ts b/src/action/actions/start.action.ts index 3c0e6d1..3fec3d0 100644 --- a/src/action/actions/start.action.ts +++ b/src/action/actions/start.action.ts @@ -37,11 +37,19 @@ export class StartAction extends AbstractAction { public async handle(_args: Input, options: Input): Promise { const directory = getDirectoryInput(options); const config = await getConfig(options, directory); + const clientDir = getStringInputWithDefault(options, "clientDir", config.client.outDir); + const serverDir = getStringInputWithDefault(options, "serverDir", config.server.outDir); const watch = getWatchInput(options); const port = getStringInputWithDefault(options, "port", config.client.port); const ssl = this.resolveSSL(options); - const tasks = this.buildStartTasks(config, directory, watch, port, ssl); + const tasks = this.buildStartTasks(config, directory, { + clientDir, + serverDir, + watch, + port, + ssl, + }); await Promise.all(tasks); return { keepAlive: true }; @@ -65,18 +73,25 @@ export class StartAction extends AbstractAction { private buildStartTasks( config: Config, directory: string, - watch: boolean, - port: string, - ssl?: SSLOptions, + options: { + clientDir: string; + serverDir: string; + watch: boolean; + port: string; + ssl?: SSLOptions; + }, ): Promise[] { const env = this.parseEnv(directory); const tasks: Promise[] = []; + const { clientDir, serverDir, watch, port, ssl } = options; - if (config.server.enable) { - tasks.push(this.startServer(directory, config, { watch }, env)); - } + if (config.server.enable) + tasks.push(this.startServer(directory, config, { serverDir, watch }, env)); - tasks.push(this.startClient(directory, config, { watch, port, ssl }, env)); + if (config.client.enable) + tasks.push( + this.startClient(directory, config, { clientDir, serverDir, watch, port, ssl }, env), + ); return tasks; } @@ -84,7 +99,13 @@ export class StartAction extends AbstractAction { private async startClient( directory: string, config: Config, - options: { watch: boolean; port: string; ssl?: SSLOptions }, + options: { + clientDir: string; + serverDir: string; + watch: boolean; + port: string; + ssl?: SSLOptions; + }, env: FullEnv, ): Promise { const loaderPath = getModulePath("@nanoforge-dev/loader-client/package.json", true); @@ -96,7 +117,7 @@ export class StartAction extends AbstractAction { private async startServer( directory: string, config: Config, - options: { watch: boolean }, + options: { serverDir: string; watch: boolean }, env: FullEnv, ): Promise { const loaderPath = getModulePath("@nanoforge-dev/loader-server/package.json", true); @@ -108,10 +129,16 @@ export class StartAction extends AbstractAction { private buildClientParams( directory: string, config: Config, - options: { watch: boolean; port: string; ssl?: SSLOptions }, + options: { + clientDir: string; + serverDir: string; + watch: boolean; + port: string; + ssl?: SSLOptions; + }, ): string[] { const params: Record = { - "-d": getCwd(join(directory, config.client.runtime.dir)), + "-d": getCwd(join(directory, options.clientDir)), "-p": options.port, }; if (options.watch) params["--watch"] = true; @@ -119,7 +146,7 @@ export class StartAction extends AbstractAction { if (options.watch) { params["--watch"] = true; if (config.server.enable) { - params["--watch-server-dir"] = getCwd(join(directory, config.server.runtime.dir)); + params["--watch-server-dir"] = getCwd(join(directory, options.serverDir)); } } @@ -133,11 +160,14 @@ export class StartAction extends AbstractAction { private buildServerParams( directory: string, - config: Config, - options: { watch: boolean }, + _config: Config, + options: { + serverDir: string; + watch: boolean; + }, ): string[] { const params: Record = { - "-d": getCwd(join(directory, config.server.runtime.dir)), + "-d": getCwd(join(directory, options.serverDir)), }; if (options.watch) params["--watch"] = true; diff --git a/src/command/commands/build.command.ts b/src/command/commands/build.command.ts index 7e569ab..c9dd883 100644 --- a/src/command/commands/build.command.ts +++ b/src/command/commands/build.command.ts @@ -7,8 +7,11 @@ import { AbstractCommand } from "../abstract.command"; interface BuildOptions { directory?: string; config?: string; + clientEntry?: string; + serverEntry?: string; clientOutDir?: string; serverOutDir?: string; + editor?: boolean; watch?: boolean; } @@ -19,15 +22,21 @@ export class BuildCommand extends AbstractCommand { .description("build your game") .option("-d, --directory [directory]", "specify the working directory of the command") .option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME) - .option("--client-outDir [clientDirectory]", "specify the output directory of the client") - .option("--server-outDir [serverDirectory]", "specify the output directory of the server") + .option("--client-entry [clientEntry]", "specify the entry file of the client") + .option("--server-entry [serverEntry]", "specify the entry file of the server") + .option("--client-outDir [clientOutDir]", "specify the output directory of the client") + .option("--server-outDir [serverOutDir]", "specify the output directory of the server") + .option("--editor", "specify if the project must build with editor config") .option("--watch", "build app in watching mode", false) .action(async (rawOptions: BuildOptions) => { const options = AbstractCommand.mapToInput({ directory: rawOptions.directory, config: rawOptions.config, - clientDirectory: rawOptions.clientOutDir, - serverDirectory: rawOptions.serverOutDir, + clientEntry: rawOptions.clientEntry, + serverEntry: rawOptions.serverEntry, + clientOutDir: rawOptions.clientOutDir, + serverOutDir: rawOptions.serverOutDir, + editor: rawOptions.editor, watch: rawOptions.watch, }); diff --git a/src/command/commands/generate.command.ts b/src/command/commands/generate.command.ts index 4ad8c9f..980c9cb 100644 --- a/src/command/commands/generate.command.ts +++ b/src/command/commands/generate.command.ts @@ -7,6 +7,7 @@ import { AbstractCommand } from "../abstract.command"; interface GenerateOptions { directory?: string; config?: string; + editor?: boolean; watch?: boolean; } @@ -17,11 +18,13 @@ export class GenerateCommand extends AbstractCommand { .description("generate nanoforge files from config") .option("-d, --directory [directory]", "specify the working directory of the command") .option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME) + .option("--editor", "specify if the project must generate editor main file") .option("--watch", "generate app in watching mode", false) .action(async (rawOptions: GenerateOptions) => { const options = AbstractCommand.mapToInput({ directory: rawOptions.directory, config: rawOptions.config, + editor: rawOptions.editor, watch: rawOptions.watch, }); diff --git a/src/command/commands/new.command.ts b/src/command/commands/new.command.ts index 395d4b2..f8d5166 100644 --- a/src/command/commands/new.command.ts +++ b/src/command/commands/new.command.ts @@ -14,6 +14,7 @@ interface NewOptions { skipInstall?: boolean; docker?: boolean; lint?: boolean; + editor?: boolean; } export class NewCommand extends AbstractCommand { @@ -40,6 +41,7 @@ export class NewCommand extends AbstractCommand { .option("--docker", "generate docker files") .option("--no-docker", "do not generate docker files") .option("--no-lint", "do not generate lint files") + .option("--editor", "do add editor dependencies") .action(async (rawOptions: NewOptions) => { const options = AbstractCommand.mapToInput({ directory: rawOptions.directory, @@ -53,6 +55,7 @@ export class NewCommand extends AbstractCommand { skipInstall: rawOptions.skipInstall, docker: rawOptions.docker, lint: rawOptions.lint, + editor: rawOptions.editor, }); await this.action.run(new Map(), options); diff --git a/src/command/commands/start.command.ts b/src/command/commands/start.command.ts index eeba0e4..ee3433e 100644 --- a/src/command/commands/start.command.ts +++ b/src/command/commands/start.command.ts @@ -8,6 +8,8 @@ interface StartOptions { directory?: string; config?: string; port?: string; + clientDir?: string; + serverDir?: string; watch?: boolean; cert?: string; key?: string; @@ -21,6 +23,8 @@ export class StartCommand extends AbstractCommand { .option("-d, --directory [directory]", "specify the working directory of the command") .option("-c, --config [config]", "path to the config file", CONFIG_FILE_NAME) .option("-p, --port [port]", "specify the port of the loader (the website to load the game)") + .option("--client-dir [clientDirectory]", "specify the directory of the client") + .option("--server-dir [serverDirectory]", "specify the directory of the server") .option("--watch", "run app in watching mode", false) .option("--cert [cert]", "path to the SSL certificate for HTTPS") .option("--key [key]", "path to the SSL key for HTTPS") @@ -29,6 +33,8 @@ export class StartCommand extends AbstractCommand { directory: rawOptions.directory, config: rawOptions.config, port: rawOptions.port, + clientDir: rawOptions.clientDir, + serverDir: rawOptions.serverDir, watch: rawOptions.watch, cert: rawOptions.cert, key: rawOptions.key, diff --git a/src/lib/config/config-defaults.ts b/src/lib/config/config-defaults.ts index 2f21c06..46b785c 100644 --- a/src/lib/config/config-defaults.ts +++ b/src/lib/config/config-defaults.ts @@ -5,23 +5,26 @@ export const CONFIG_DEFAULTS: Config = { language: "ts", initFunctions: true, client: { + enable: true, port: "3000", + outDir: ".nanoforge/client", build: { - entryFile: "client/main.ts", - outDir: ".nanoforge/client", + entry: "client/main.ts", }, - runtime: { - dir: ".nanoforge/client", + editor: { + entry: ".nanoforge/editor/client/main.ts", + save: ".nanoforge/client.save.json", }, }, server: { enable: false, + outDir: ".nanoforge/server", build: { - entryFile: "server/main.ts", - outDir: ".nanoforge/server", + entry: "server/main.ts", }, - runtime: { - dir: ".nanoforge/server", + editor: { + entry: ".nanoforge/editor/server/main.ts", + save: ".nanoforge/server.save.json", }, }, }; diff --git a/src/lib/config/config.type.ts b/src/lib/config/config.type.ts index 657cacf..58c57d9 100644 --- a/src/lib/config/config.type.ts +++ b/src/lib/config/config.type.ts @@ -5,35 +5,44 @@ export class BuildConfig { @Expose() @IsString() @IsNotEmpty() - entryFile!: string; + entry!: string; +} +export class EditorConfig { @Expose() @IsString() @IsNotEmpty() - outDir!: string; -} + entry!: string; -export class RunConfig { @Expose() @IsString() @IsNotEmpty() - dir!: string; + save!: string; } export class ClientConfig { + @Expose() + @IsBoolean() + enable!: boolean; + @Expose() @IsPort() port!: string; + @Expose() + @IsString() + @IsNotEmpty() + outDir!: string; + @Expose() @Type(() => BuildConfig) @ValidateNested() build!: BuildConfig; @Expose() - @Type(() => RunConfig) + @Type(() => EditorConfig) @ValidateNested() - runtime!: RunConfig; + editor!: EditorConfig; } export class ServerConfig { @@ -41,15 +50,20 @@ export class ServerConfig { @IsBoolean() enable!: boolean; + @Expose() + @IsString() + @IsNotEmpty() + outDir!: string; + @Expose() @Type(() => BuildConfig) @ValidateNested() build!: BuildConfig; @Expose() - @Type(() => RunConfig) + @Type(() => EditorConfig) @ValidateNested() - runtime!: RunConfig; + editor!: EditorConfig; } export class Config { diff --git a/src/lib/input/inputs/editor.input.spec.ts b/src/lib/input/inputs/editor.input.spec.ts new file mode 100644 index 0000000..a61bcb9 --- /dev/null +++ b/src/lib/input/inputs/editor.input.spec.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; + +import { type Input } from "../input.type"; +import { getEditorInput } from "./editor.input"; + +const createInput = (entries: [string, any][]): Input => { + return new Map(entries.map(([key, value]) => [key, { value }])); +}; + +describe("getEditorInput", () => { + it("should return the editor value when provided", () => { + const input = createInput([["editor", true]]); + expect(getEditorInput(input)).toBe(true); + }); + + it("should return false as default when editor is missing", () => { + const input = createInput([]); + expect(getEditorInput(input)).toBe(false); + }); +}); diff --git a/src/lib/input/inputs/editor.input.ts b/src/lib/input/inputs/editor.input.ts new file mode 100644 index 0000000..a275cca --- /dev/null +++ b/src/lib/input/inputs/editor.input.ts @@ -0,0 +1,6 @@ +import { getBooleanInputWithDefault } from "../base-inputs"; +import { type Input } from "../input.type"; + +export const getEditorInput = (inputs: Input): boolean => { + return getBooleanInputWithDefault(inputs, "editor", false); +}; diff --git a/src/lib/input/inputs/index.ts b/src/lib/input/inputs/index.ts index 585a2ca..955cf6f 100644 --- a/src/lib/input/inputs/index.ts +++ b/src/lib/input/inputs/index.ts @@ -1,5 +1,6 @@ export * from "./directory.input"; export * from "./config.input"; +export * from "./editor.input"; export * from "./watch.input"; export * from "./dev";