From 74690aae861348b6e8e00f3ac6b5eaa0f4d24807 Mon Sep 17 00:00:00 2001 From: Yassine EL OUAFI Date: Wed, 18 Mar 2026 14:57:21 +0000 Subject: [PATCH 1/4] chore: prepare repo for public release --- .github/workflows/ci.yml | 37 ++++++++++++++ CONTRIBUTING.md | 2 +- README.md | 2 +- .../src/__tests__/cli-integration.test.ts | 32 ++++++++++++ packages/create-oncyber-app/src/index.ts | 50 +++++++++++++++++-- 5 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5676c0d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.9.0 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run type checks + run: pnpm -r --if-present check + + - name: Run tests + run: pnpm -r --if-present test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75d8737..2d618f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Thanks for your interest in contributing to oncyberio! This guide will help you ### Setup ```bash -git clone +git clone https://github.com/oncyberio/awe.git cd awe pnpm install ``` diff --git a/README.md b/README.md index bbc21f2..e0f6170 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ This scaffolds a full Next.js project with the oncyberio engine pre-configured, ### Monorepo Development ```bash -git clone +git clone https://github.com/oncyberio/awe.git cd awe pnpm install ``` diff --git a/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts b/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts index 4c5cf71..df12c58 100644 --- a/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts +++ b/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts @@ -115,6 +115,38 @@ describe("CLI integration", { timeout: 30000 }, () => { expect(rootPkg.scripts["dev"]).toBeDefined(); }); + it("writes npm workspace scripts for generated projects", () => { + runCli("npm-script-test --template starter --use-npm --skip-install --skip-git", tmpDir); + + const rootPkg = JSON.parse( + fs.readFileSync(path.join(tmpDir, "npm-script-test", "package.json"), "utf-8"), + ); + + expect(rootPkg.workspaces).toEqual(["packages/*", "apps/*"]); + expect(rootPkg.scripts["dev"]).toBe( + "npm run dev --workspace npm-script-test", + ); + expect(rootPkg.scripts["build"]).toBe( + "npm run build --workspace npm-script-test", + ); + }); + + it("writes yarn workspace scripts for generated projects", () => { + runCli("yarn-script-test --template starter --use-yarn --skip-install --skip-git", tmpDir); + + const rootPkg = JSON.parse( + fs.readFileSync(path.join(tmpDir, "yarn-script-test", "package.json"), "utf-8"), + ); + + expect(rootPkg.workspaces).toEqual(["packages/*", "apps/*"]); + expect(rootPkg.scripts["dev"]).toBe( + "yarn workspace yarn-script-test dev", + ); + expect(rootPkg.scripts["build"]).toBe( + "yarn workspace yarn-script-test build", + ); + }); + it("removes examples/* from pnpm-workspace.yaml and keeps apps/*", () => { runCli("workspace-test --template starter --use-pnpm --skip-install --skip-git", tmpDir); diff --git a/packages/create-oncyber-app/src/index.ts b/packages/create-oncyber-app/src/index.ts index b449b09..9158ff3 100644 --- a/packages/create-oncyber-app/src/index.ts +++ b/packages/create-oncyber-app/src/index.ts @@ -9,7 +9,7 @@ import { detectPackageManager } from "./detect-package-manager"; import { createSpinner, createSteps } from "./spinner"; const REPO_URL = - process.env.AWE_REPO_URL || "https://github.com/runthefun/awe-dev.git"; + process.env.AWE_REPO_URL || "https://github.com/oncyberio/awe.git"; const REPO_BRANCH = process.env.AWE_REPO_BRANCH || "main"; const TEMPLATES = [ @@ -109,10 +109,27 @@ function sparseClone(repoUrl: string, dest: string, template: string) { ]); } +function getWorkspaceScriptCommand( + packageManager: PackageManager, + workspaceName: string, + scriptName: string, +) { + switch (packageManager) { + case "npm": + return `npm run ${scriptName} --workspace ${workspaceName}`; + case "yarn": + return `yarn workspace ${workspaceName} ${scriptName}`; + case "pnpm": + default: + return `pnpm --filter ${workspaceName} ${scriptName}`; + } +} + function cleanupScaffold( projectDir: string, projectName: string, template: string, + packageManager: PackageManager, ) { // Remove .git from the clone const gitDir = path.join(projectDir, ".git"); @@ -148,11 +165,22 @@ function cleanupScaffold( writeJson(gamePkgPath, gamePkg); } - // Update root package.json — set name + // Update root package.json — set name and generated-project scripts const rootPkgPath = path.join(projectDir, "package.json"); if (fs.existsSync(rootPkgPath)) { const rootPkg = readJson(rootPkgPath); rootPkg.name = projectName; + rootPkg.workspaces = ["packages/*", "apps/*"]; + rootPkg.scripts = { + ...rootPkg.scripts, + dev: getWorkspaceScriptCommand(packageManager, projectName, "dev"), + build: getWorkspaceScriptCommand(packageManager, projectName, "build"), + start: getWorkspaceScriptCommand(packageManager, projectName, "start"), + check: getWorkspaceScriptCommand(packageManager, projectName, "check"), + }; + delete rootPkg.scripts["create-game"]; + delete rootPkg.scripts["template:dev"]; + delete rootPkg.scripts["template:check"]; writeJson(rootPkgPath, rootPkg); } @@ -702,7 +730,7 @@ async function scaffold(options: CliOptions) { // Set up project const setupSpinner = createSpinner("Setting up project..."); setupSpinner.start(); - cleanupScaffold(projectDir, projectName, template); + cleanupScaffold(projectDir, projectName, template, packageManager); setupSpinner.stop("Project set up."); // Git initialization @@ -785,4 +813,18 @@ async function main() { await scaffold(options); } -main(); +function isDirectExecution() { + const entryPath = process.argv[1]; + if (!entryPath) { + return false; + } + + return path.resolve(entryPath) === __filename; +} + +if (isDirectExecution()) { + void main().catch((error) => { + console.error(error); + process.exit(1); + }); +} From 22c863967a662c8875a574b3865586e6a6238806 Mon Sep 17 00:00:00 2001 From: Yassine EL OUAFI Date: Wed, 18 Mar 2026 15:02:02 +0000 Subject: [PATCH 2/4] chore: remove github workflow --- .github/workflows/ci.yml | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 5676c0d..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - -jobs: - verify: - runs-on: ubuntu-latest - timeout-minutes: 20 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - run_install: false - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 20.9.0 - cache: pnpm - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Run type checks - run: pnpm -r --if-present check - - - name: Run tests - run: pnpm -r --if-present test From 42d33e66f14f68c7b059fcedf726dec415651cc7 Mon Sep 17 00:00:00 2001 From: Yassine EL OUAFI Date: Wed, 18 Mar 2026 15:46:23 +0000 Subject: [PATCH 3/4] feat(create-oncyber-app): require pnpm --- package.json | 1 + packages/create-oncyber-app/README.md | 19 ++- .../src/__tests__/cli-integration.test.ts | 53 ++++---- .../__tests__/detect-package-manager.test.ts | 41 ------ .../src/__tests__/parse-args.test.ts | 14 +- .../src/detect-package-manager.ts | 11 -- packages/create-oncyber-app/src/index.ts | 120 +++++++++++------- 7 files changed, 115 insertions(+), 144 deletions(-) delete mode 100644 packages/create-oncyber-app/src/__tests__/detect-package-manager.test.ts delete mode 100644 packages/create-oncyber-app/src/detect-package-manager.ts diff --git a/package.json b/package.json index bfe7320..4f3a223 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "awe", "private": true, + "packageManager": "pnpm@10.10.0", "engines": { "node": ">=20.9.0" }, diff --git a/packages/create-oncyber-app/README.md b/packages/create-oncyber-app/README.md index 14b67da..9b11fdc 100644 --- a/packages/create-oncyber-app/README.md +++ b/packages/create-oncyber-app/README.md @@ -7,7 +7,7 @@ Scaffold a new 3D game powered by the oncyberio engine. ```bash npx create-oncyber-app my-game cd my-game -npm run dev +pnpm dev ``` Open [http://localhost:3000](http://localhost:3000) to see your game, or [http://localhost:3000/_studio](http://localhost:3000/_studio) for the visual editor. @@ -18,34 +18,33 @@ Open [http://localhost:3000](http://localhost:3000) to see your game, or [http:/ npx create-oncyber-app [project-name] [options] ``` -When run without a project name, the CLI starts in interactive mode and prompts for a name and package manager. +When run without a project name, the CLI starts in interactive mode and prompts for a name and template. The generated project uses pnpm. ## Options | Flag | Description | | --- | --- | -| `--use-npm` | Use npm as the package manager | -| `--use-pnpm` | Use pnpm as the package manager | -| `--use-yarn` | Use yarn as the package manager | | `--skip-install` | Skip automatic dependency installation | | `--skip-git` | Skip git repository initialization | | `--help` | Show the help message | | `--version` | Show the CLI version | +The CLI uses pnpm automatically. If pnpm is missing, it will try to enable it with Corepack. + ## Examples ```bash -# Interactive mode — prompts for name and package manager +# Interactive mode — prompts for name and template npx create-oncyber-app # Create a project with a specific name npx create-oncyber-app my-game -# Use pnpm and skip git init -npx create-oncyber-app my-game --use-pnpm --skip-git +# Skip git init +npx create-oncyber-app my-game --skip-git # Scaffold only — no install, no git -npx create-oncyber-app my-game --use-npm --skip-install --skip-git +npx create-oncyber-app my-game --skip-install --skip-git ``` ## What's Included @@ -102,7 +101,7 @@ my-game/ ## Next Steps -1. Run `npm run dev` to start the dev server +1. Run `pnpm dev` to start the dev server 2. Open [http://localhost:3000](http://localhost:3000) to see your game 3. Open [http://localhost:3000/_studio](http://localhost:3000/_studio) to edit the scene visually 4. Edit `src/components/game-script.tsx` to add game logic diff --git a/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts b/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts index df12c58..37f8e07 100644 --- a/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts +++ b/packages/create-oncyber-app/src/__tests__/cli-integration.test.ts @@ -115,35 +115,20 @@ describe("CLI integration", { timeout: 30000 }, () => { expect(rootPkg.scripts["dev"]).toBeDefined(); }); - it("writes npm workspace scripts for generated projects", () => { - runCli("npm-script-test --template starter --use-npm --skip-install --skip-git", tmpDir); + it("writes pnpm workspace scripts for generated projects", () => { + runCli("pnpm-script-test --template starter --skip-install --skip-git", tmpDir); const rootPkg = JSON.parse( - fs.readFileSync(path.join(tmpDir, "npm-script-test", "package.json"), "utf-8"), + fs.readFileSync(path.join(tmpDir, "pnpm-script-test", "package.json"), "utf-8"), ); + expect(rootPkg.packageManager).toBe("pnpm@10.10.0"); expect(rootPkg.workspaces).toEqual(["packages/*", "apps/*"]); expect(rootPkg.scripts["dev"]).toBe( - "npm run dev --workspace npm-script-test", + "pnpm --filter pnpm-script-test dev", ); expect(rootPkg.scripts["build"]).toBe( - "npm run build --workspace npm-script-test", - ); - }); - - it("writes yarn workspace scripts for generated projects", () => { - runCli("yarn-script-test --template starter --use-yarn --skip-install --skip-git", tmpDir); - - const rootPkg = JSON.parse( - fs.readFileSync(path.join(tmpDir, "yarn-script-test", "package.json"), "utf-8"), - ); - - expect(rootPkg.workspaces).toEqual(["packages/*", "apps/*"]); - expect(rootPkg.scripts["dev"]).toBe( - "yarn workspace yarn-script-test dev", - ); - expect(rootPkg.scripts["build"]).toBe( - "yarn workspace yarn-script-test build", + "pnpm --filter pnpm-script-test build", ); }); @@ -179,6 +164,26 @@ describe("CLI integration", { timeout: 30000 }, () => { expect(stdout).toContain("nonexistent"); }); + it("fails fast when --use-npm is passed", () => { + const { exitCode, stdout } = runCli( + "bad-package-manager --use-npm --skip-install --skip-git", + tmpDir, + ); + + expect(exitCode).not.toBe(0); + expect(stdout).toContain("pnpm only"); + }); + + it("fails fast when --use-yarn is passed", () => { + const { exitCode, stdout } = runCli( + "bad-package-manager --use-yarn --skip-install --skip-git", + tmpDir, + ); + + expect(exitCode).not.toBe(0); + expect(stdout).toContain("pnpm only"); + }); + it("initializes git repo when --skip-git is not passed", () => { runCli("git-test --template starter --use-pnpm --skip-install", tmpDir); @@ -216,11 +221,11 @@ describe("CLI integration", { timeout: 30000 }, () => { expect(exitCode).toBe(0); expect(stdout).toContain("Usage:"); expect(stdout).toContain("--template"); - expect(stdout).toContain("--use-npm"); - expect(stdout).toContain("--use-pnpm"); - expect(stdout).toContain("--use-yarn"); expect(stdout).toContain("--skip-install"); expect(stdout).toContain("--skip-git"); + expect(stdout).toContain("Uses pnpm automatically"); + expect(stdout).not.toContain("--use-npm"); + expect(stdout).not.toContain("--use-yarn"); expect(stdout).toContain("update"); expect(stdout).toContain("Templates:"); expect(stdout).toContain("starter"); diff --git a/packages/create-oncyber-app/src/__tests__/detect-package-manager.test.ts b/packages/create-oncyber-app/src/__tests__/detect-package-manager.test.ts deleted file mode 100644 index 313e8a9..0000000 --- a/packages/create-oncyber-app/src/__tests__/detect-package-manager.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { describe, it, expect, afterEach } from "vitest"; -import { detectPackageManager } from "../detect-package-manager"; - -describe("detectPackageManager", () => { - const originalAgent = process.env.npm_config_user_agent; - - afterEach(() => { - if (originalAgent === undefined) { - delete process.env.npm_config_user_agent; - } else { - process.env.npm_config_user_agent = originalAgent; - } - }); - - it("detects pnpm from user agent", () => { - process.env.npm_config_user_agent = - "pnpm/9.0.0 npm/? node/v20.0.0 darwin arm64"; - expect(detectPackageManager()).toBe("pnpm"); - }); - - it("detects yarn from user agent", () => { - process.env.npm_config_user_agent = "yarn/1.22.0 npm/? node/v20.0.0"; - expect(detectPackageManager()).toBe("yarn"); - }); - - it("detects npm from user agent", () => { - process.env.npm_config_user_agent = - "npm/10.0.0 node/v20.0.0 darwin arm64"; - expect(detectPackageManager()).toBe("npm"); - }); - - it("falls back to npm when no user agent is set", () => { - delete process.env.npm_config_user_agent; - expect(detectPackageManager()).toBe("npm"); - }); - - it("falls back to npm for unknown agent", () => { - process.env.npm_config_user_agent = "bun/1.0.0"; - expect(detectPackageManager()).toBe("npm"); - }); -}); diff --git a/packages/create-oncyber-app/src/__tests__/parse-args.test.ts b/packages/create-oncyber-app/src/__tests__/parse-args.test.ts index 48c5652..ec1f78d 100644 --- a/packages/create-oncyber-app/src/__tests__/parse-args.test.ts +++ b/packages/create-oncyber-app/src/__tests__/parse-args.test.ts @@ -25,21 +25,11 @@ describe("parseArgs", () => { expect(result.projectName).toBe("my-game"); }); - it("parses --use-npm flag", () => { - const result = parseArgs(["node", "create-oncyber-app", "--use-npm"]); - expect(result.packageManager).toBe("npm"); - }); - it("parses --use-pnpm flag", () => { const result = parseArgs(["node", "create-oncyber-app", "--use-pnpm"]); expect(result.packageManager).toBe("pnpm"); }); - it("parses --use-yarn flag", () => { - const result = parseArgs(["node", "create-oncyber-app", "--use-yarn"]); - expect(result.packageManager).toBe("yarn"); - }); - it("parses --skip-install flag", () => { const result = parseArgs(["node", "create-oncyber-app", "--skip-install"]); expect(result.skipInstall).toBe(true); @@ -117,11 +107,11 @@ describe("parseArgs", () => { "create-oncyber-app", "--skip-git", "my-game", - "--use-npm", + "--use-pnpm", ]); expect(result.projectName).toBe("my-game"); expect(result.skipGit).toBe(true); - expect(result.packageManager).toBe("npm"); + expect(result.packageManager).toBe("pnpm"); }); it("parses --local flag", () => { diff --git a/packages/create-oncyber-app/src/detect-package-manager.ts b/packages/create-oncyber-app/src/detect-package-manager.ts deleted file mode 100644 index a2a7eb3..0000000 --- a/packages/create-oncyber-app/src/detect-package-manager.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { PackageManager } from "./index"; - -export function detectPackageManager(): PackageManager { - const agent = process.env.npm_config_user_agent; - if (!agent) return "npm"; - - const name = agent.split("/")[0]; - if (name === "pnpm") return "pnpm"; - if (name === "yarn") return "yarn"; - return "npm"; -} diff --git a/packages/create-oncyber-app/src/index.ts b/packages/create-oncyber-app/src/index.ts index 9158ff3..6455455 100644 --- a/packages/create-oncyber-app/src/index.ts +++ b/packages/create-oncyber-app/src/index.ts @@ -5,12 +5,15 @@ import path from "path"; import { execSync } from "child_process"; import { input, select } from "@inquirer/prompts"; import pc from "picocolors"; -import { detectPackageManager } from "./detect-package-manager"; import { createSpinner, createSteps } from "./spinner"; const REPO_URL = process.env.AWE_REPO_URL || "https://github.com/oncyberio/awe.git"; const REPO_BRANCH = process.env.AWE_REPO_BRANCH || "main"; +const PNPM_VERSION = "10.10.0"; +const PNPM_PACKAGE_MANAGER = `pnpm@${PNPM_VERSION}`; +const PNPM_BOOTSTRAP_COMMAND = + `corepack enable && corepack prepare ${PNPM_PACKAGE_MANAGER} --activate`; const TEMPLATES = [ { name: "starter", description: "Minimal game setup with player controls" }, @@ -109,19 +112,46 @@ function sparseClone(repoUrl: string, dest: string, template: string) { ]); } -function getWorkspaceScriptCommand( - packageManager: PackageManager, - workspaceName: string, - scriptName: string, -) { - switch (packageManager) { - case "npm": - return `npm run ${scriptName} --workspace ${workspaceName}`; - case "yarn": - return `yarn workspace ${workspaceName} ${scriptName}`; - case "pnpm": - default: - return `pnpm --filter ${workspaceName} ${scriptName}`; +function getWorkspaceScriptCommand(workspaceName: string, scriptName: string) { + return `pnpm --filter ${workspaceName} ${scriptName}`; +} + +function canRunCommand(command: string) { + try { + execSync(command, { stdio: "pipe" }); + return true; + } catch { + return false; + } +} + +function ensurePnpmAvailable() { + if (canRunCommand("pnpm --version")) { + return; + } + + console.log(pc.dim("pnpm was not found. Trying to set it up with Corepack...")); + + if (!canRunCommand("corepack --version")) { + console.error("Error: pnpm is required to work with this project."); + console.error(`Run:\n ${PNPM_BOOTSTRAP_COMMAND}`); + process.exit(1); + } + + try { + execSync("corepack enable", { stdio: "pipe" }); + execSync(`corepack prepare ${PNPM_PACKAGE_MANAGER} --activate`, { + stdio: "pipe", + }); + execSync("pnpm --version", { stdio: "pipe" }); + console.log(pc.dim("pnpm is ready.")); + } catch (err: any) { + console.error("Failed to set up pnpm automatically."); + console.error(`Run:\n ${PNPM_BOOTSTRAP_COMMAND}`); + if (err.stderr) { + console.error(err.stderr.toString()); + } + process.exit(1); } } @@ -129,7 +159,6 @@ function cleanupScaffold( projectDir: string, projectName: string, template: string, - packageManager: PackageManager, ) { // Remove .git from the clone const gitDir = path.join(projectDir, ".git"); @@ -170,13 +199,14 @@ function cleanupScaffold( if (fs.existsSync(rootPkgPath)) { const rootPkg = readJson(rootPkgPath); rootPkg.name = projectName; + rootPkg.packageManager = PNPM_PACKAGE_MANAGER; rootPkg.workspaces = ["packages/*", "apps/*"]; rootPkg.scripts = { ...rootPkg.scripts, - dev: getWorkspaceScriptCommand(packageManager, projectName, "dev"), - build: getWorkspaceScriptCommand(packageManager, projectName, "build"), - start: getWorkspaceScriptCommand(packageManager, projectName, "start"), - check: getWorkspaceScriptCommand(packageManager, projectName, "check"), + dev: getWorkspaceScriptCommand(projectName, "dev"), + build: getWorkspaceScriptCommand(projectName, "build"), + start: getWorkspaceScriptCommand(projectName, "start"), + check: getWorkspaceScriptCommand(projectName, "check"), }; delete rootPkg.scripts["create-game"]; delete rootPkg.scripts["template:dev"]; @@ -218,15 +248,15 @@ ${pc.bold("Commands:")} ${pc.bold("Options:")} --template NAME Choose a project template (default: ${DEFAULT_TEMPLATE}) - --use-npm Use npm for dependency installation - --use-pnpm Use pnpm for dependency installation (default) - --use-yarn Use yarn for dependency installation --skip-install Skip automatic dependency installation --skip-git Skip git repository initialization --local Create app inside the monorepo's apps/ directory --help Show this help message --version Show the CLI version +${pc.bold("Package Manager:")} + Uses pnpm automatically. If pnpm is missing, the CLI will try to enable it with Corepack. + ${pc.bold("Templates:")} ${templateList} @@ -240,8 +270,8 @@ ${pc.bold("Examples:")} ${pc.dim("# Create a multiplayer project")} npx create-oncyber-app my-game --template multiplayer - ${pc.dim("# Use pnpm and skip git init")} - npx create-oncyber-app my-game --use-pnpm --skip-git + ${pc.dim("# Skip git init")} + npx create-oncyber-app my-game --skip-git ${pc.dim("# Create an app inside the monorepo")} create-oncyber-app my-game --local @@ -265,6 +295,17 @@ function printVersion() { console.log(pkg.version); } +function validatePackageManager(options: CliOptions) { + if (!options.packageManager || options.packageManager === "pnpm") { + return; + } + + console.error("Error: create-oncyber-app currently supports pnpm only."); + console.error("Remove the package manager flag and run the command again."); + console.error(`If pnpm is missing, run:\n ${PNPM_BOOTSTRAP_COMMAND}`); + process.exit(1); +} + export function parseArgs(argv: string[]): CliOptions { const args = argv.slice(2); const options: CliOptions = { @@ -477,12 +518,12 @@ async function update(options: CliOptions) { updateSpinner.stop("Packages updated."); // Install deps - const packageManager = options.packageManager || "pnpm"; if (!options.skipInstall) { + ensurePnpmAvailable(); const installSpinner = createSpinner("Installing dependencies..."); installSpinner.start(); try { - execSync(`${packageManager} install`, { + execSync("pnpm install", { cwd: projectDir, stdio: "pipe", }); @@ -615,6 +656,7 @@ async function scaffoldLocal(options: CliOptions, monorepoRoot: string) { // Install deps if (!options.skipInstall) { + ensurePnpmAvailable(); steps.startStep(); try { execSync("pnpm install", { @@ -682,22 +724,6 @@ async function scaffold(options: CliOptions) { }); } - let packageManager: PackageManager; - if (options.packageManager) { - packageManager = options.packageManager; - } else { - const detected = detectPackageManager(); - packageManager = await select({ - message: "Which package manager would you like to use?", - choices: [ - { name: "npm", value: "npm" as const }, - { name: "pnpm", value: "pnpm" as const }, - { name: "yarn", value: "yarn" as const }, - ], - default: detected, - }); - } - const projectDir = path.resolve(process.cwd(), projectName); // Register SIGINT handler to clean up partial project directory @@ -730,7 +756,7 @@ async function scaffold(options: CliOptions) { // Set up project const setupSpinner = createSpinner("Setting up project..."); setupSpinner.start(); - cleanupScaffold(projectDir, projectName, template, packageManager); + cleanupScaffold(projectDir, projectName, template); setupSpinner.stop("Project set up."); // Git initialization @@ -754,10 +780,11 @@ async function scaffold(options: CliOptions) { // Dependency installation let depsInstalled = false; if (!options.skipInstall) { + ensurePnpmAvailable(); const installSpinner = createSpinner("Installing dependencies..."); installSpinner.start(); try { - execSync(`${packageManager} install`, { + execSync("pnpm install", { cwd: projectDir, stdio: "pipe", }); @@ -783,14 +810,15 @@ async function scaffold(options: CliOptions) { console.log("Next steps:"); console.log(` ${pc.cyan(`cd ${projectName}`)}`); if (!depsInstalled) { - console.log(` ${pc.cyan(`${packageManager} install`)}`); + console.log(` ${pc.cyan("pnpm install")}`); } - console.log(` ${pc.cyan(`${packageManager} dev`)}`); + console.log(` ${pc.cyan("pnpm dev")}`); console.log(); } async function main() { const options = parseArgs(process.argv); + validatePackageManager(options); if (options.projectName === "update") { options.projectName = undefined; From 47b7c5d5d53a8728ff360179e5b45e7fd2b39120 Mon Sep 17 00:00:00 2001 From: Yassine EL OUAFI Date: Wed, 18 Mar 2026 15:51:14 +0000 Subject: [PATCH 4/4] chore: hide particles from editor --- packages/engine-edit/src/editors/index.ts | 2 -- packages/engine/src/space/abstract/component-info.ts | 1 + packages/engine/src/space/components/particles/index.ts | 1 + packages/studio/src/hooks/component-hooks.ts | 3 ++- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/engine-edit/src/editors/index.ts b/packages/engine-edit/src/editors/index.ts index c062d81..e10e124 100644 --- a/packages/engine-edit/src/editors/index.ts +++ b/packages/engine-edit/src/editors/index.ts @@ -20,7 +20,6 @@ import { MeshComponentEditor } from "./mesh/editor"; import { ModelEditor } from "./model/editor"; import { NavmeshComponentEditor } from "./navmesh/editor"; import { ObjectEditor } from "./object/editor"; -import { ParticlesEditor } from "./particles/editor"; import { PostProcessingEditor } from "./postprocessing/editor"; import { QuarksComponentEditor } from "./quarks/editor"; import { ReflectorEditor } from "./reflector/editor"; @@ -53,7 +52,6 @@ export function registerAllEditors() { registerEditor("model", ModelEditor); registerEditor("navmesh", NavmeshComponentEditor); registerEditor("object", ObjectEditor); - registerEditor("particles", ParticlesEditor); registerEditor("postprocessing", PostProcessingEditor); registerEditor("quarks", QuarksComponentEditor); registerEditor("reflector", ReflectorEditor); diff --git a/packages/engine/src/space/abstract/component-info.ts b/packages/engine/src/space/abstract/component-info.ts index 3b943e5..cc0e701 100644 --- a/packages/engine/src/space/abstract/component-info.ts +++ b/packages/engine/src/space/abstract/component-info.ts @@ -25,6 +25,7 @@ export interface ComponentInfo { studioTab?: string; singleton?: boolean; required?: boolean; + hidden?: boolean; draggable?: boolean; priority?: number; custom?: boolean; diff --git a/packages/engine/src/space/components/particles/index.ts b/packages/engine/src/space/components/particles/index.ts index 2dd410b..2bbbbb7 100644 --- a/packages/engine/src/space/components/particles/index.ts +++ b/packages/engine/src/space/components/particles/index.ts @@ -16,6 +16,7 @@ export class ParticlesComponentFactory extends DefaultComponentFactory { - return !exludedComponents[factory.info.type]; + return !exludedComponents[factory.info.type] && !factory.info.hidden; }); // }, [options]);