Skip to content
prod e051e98
Browse

Dev environment & tooling

Your dev environment is the floor everything else stands on. Get the runtime versions wrong and the same code that runs locally throws fatal errors on the server; get the editor tooling wrong and your AI agent ignores the rules that keep vendor code upgrade-safe. This guide teaches the four moving parts — version management, server runtimes, CLI config updates, and the AI editor setup — as mental models. The playbooks are where you actually run the commands.

A CodeCanyon Laravel app runs in at least three places — your machine, staging, and production — and each one controls its own PHP and Node versions differently. The “works on my machine” bug is almost always a version mismatch, and the fix is to make versions explicit and per-project instead of relying on whatever the system default happens to be.

flowchart LR
subgraph local["Local — your machine"]
L["nvm + Herd<br/>per-project versions"]
end
subgraph staging["Staging server"]
S["hPanel + full PHP paths"]
end
subgraph prod["Production server"]
P["hPanel + full PHP paths"]
end
local -->|"must match"| staging
staging -->|"must match"| prod

Four habits keep that floor level: pin runtime versions per project, set server versions explicitly, edit DB-backed config from the CLI safely, and configure your editor so the AI follows the project’s rules. The rest of this guide is those four.

Version managers — one runtime per project

Section titled “Version managers — one runtime per project”

Different projects need different runtimes. A legacy app might be stuck on older PHP and Node releases; the current project wants the versions declared in composer.json and package.json; a new feature may target newer runtimes. Without version managers these collide constantly. A version manager lets each project declare the runtime it needs and switch to it automatically.

First, clear up the single most common beginner confusion — nvm and npm are not the same tool:

ToolWhat it managesThink of it as
nvmWhich Node.js version is installed”Which Node do I have?“
npmWhich packages are installed for that Node”Which libraries does this project use?”

npm ships bundled with Node — install Node 22 and you automatically get the npm that matches it. nvm is the switcher that sits one level above, choosing which Node (and therefore which npm) is active.

Each language has its own manager and its own per-project version file:

LanguageVersion managerPer-project fileUniversal?
Node.jsnvm.nvmrcYes — works anywhere nvm is installed
PHPHerd, phpbrew, asdfvariesNo universal standard
Pythonpyenv.python-versionFairly standard
Rubyrbenv, rvm.ruby-versionWidely supported

The payoff is that walking into a project folder switches Node for you:

  1. Create the file. Put a .nvmrc in the project root containing the version, e.g. 22. Commit it — it’s part of the project.
  2. nvm reads it on cd. With an auto-switch hook in your shell config (~/.zshrc / ~/.bashrc), entering the folder runs nvm use for you.
  3. The right Node is active. Every npm command now uses the version this project expects, not the system default.

For PHP under Herd, the equivalent is “Isolate with PHP” on a site — Herd remembers that version per site forever, so php inside that project’s folder uses the isolated version.

Don’t pin to a version that’s about to lose support, and for Node never use odd numbers:

RuntimeUseAvoid
Node.jsEven-numbered LTS (20, 22, 24)Odd versions (21, 23) — unstable, short-lived
PHPActive releases (8.2, 8.3, 8.4)EOL versions (8.0, 7.x)

The rule of thumb: even Node, supported PHP. A package that demands node: '20 || >=22' will warn (EBADENGINE) the moment you run it on an odd version — the fix is always nvm install the right LTS, never forcing the package to accept the wrong one.

The full local-vs-server install steps and the shell auto-switch hook live in the local-dev setup playbook and the version-management handbook.

PHP on the server — web and CLI are different

Section titled “PHP on the server — web and CLI are different”

Here is the gotcha that costs people an afternoon: on a server, the PHP that runs your website and the PHP that runs your SSH commands are often different versions. The hosting panel sets the web version; the bare php command in SSH uses the system default. So a migration that runs fine in the browser-driven installer can fail when you SSH in and run php artisan migrate.

flowchart TD
req["Incoming request"] --> kind{"Web request<br/>or CLI?"}
kind -->|"Web (browser)"| web["Uses hPanel / .htaccess<br/>PHP version"]
kind -->|"SSH / CLI"| cli["Uses system default PHP<br/>(often older!)"]
cli --> fix["Fix: full path<br/>/opt/alt/php83/usr/bin/php"]

There are three levers, and you usually need two of them:

You want to set…UseScope
PHP for web requestsHosting panel (hPanel / cPanel MultiPHP) or an .htaccess handlerWeb only — committed .htaccess auto-deploys
PHP for CLI / artisan / composerThe full path to the version, e.g. /opt/alt/php83/usr/bin/php artisan migrateThat one command
PHP for the deploy pipelinePoint Deployer at the path: set('bin/php', '/opt/alt/php83/usr/bin/php')Every deploy command

On CloudLinux hosts (Hostinger, many cPanel hosts) the versions live at predictable paths — /opt/alt/php82/usr/bin/php, /opt/alt/php83/usr/bin/php, and so on. List what’s available with ls /opt/alt/php*/usr/bin/php, then either prefix commands with the full path or add a shell alias (alias php83='/opt/alt/php83/usr/bin/php').

For Node on a server that doesn’t ship it, install nvm in your home directory (no root needed), then nvm install 22 and rely on the same committed .nvmrc you use locally. One version file, every environment.

CLI config updates — fast, but handle with care

Section titled “CLI config updates — fast, but handle with care”

Workdo-style CodeCanyon apps keep a lot of configuration — site name, pricing, image paths, feature flags — in database rows, not files. Clicking through the admin panel to change fifty of them is slow. Laravel’s Tinker REPL lets you update those rows from the command line in seconds.

Updating via…Admin panelCLI (Tinker)
LoginRequiredNone
One valueFineFine
Fifty valuesPainfulOne script
Speed for bulkSlowFast

The pattern is a small PHP script you run through Tinker:

Terminal window
php artisan tinker --execute="include 'scripts/update_settings.php';"

…where the script does an updateOrInsert / update against a table like settings or plans. That speed is real, but a raw DB::table() write skips the model’s observers, events, and cache invalidation that the real admin form fires — which is exactly how “one fix” becomes “three bugs” (a slug that never regenerates, a cache that never clears, a derived *_html column that stays stale).

So treat the CLI as a power tool with guardrails:

  1. Read before you write. DESCRIBE the table and count matching rows first — DB::table('settings')->where('key','x')->count(). If it returns 0, your WHERE is wrong, not the data.
  2. Back up before bulk changes. mysqldump the database (or the affected rows) so a bad update has an undo.
  3. Wrap multi-row changes in a transaction. DB::transaction(fn () => …) rolls everything back if any step fails.
  4. Prefer the model over raw SQL when hooks matter. Use Model::updateOrCreate() instead of DB::table() so observers and cache-busting fire — or drive the real superadmin form when the change has side-effects the CLI can’t reproduce.

The decision rule for UI-first vs CLI write and the full pre-flight for vendor-table writes are taught in the customizations decision-rule playbook.

AI editor & agent tooling — rules vs workflows

Section titled “AI editor & agent tooling — rules vs workflows”

The last piece is the editor. Modern AI IDEs (Antigravity, Cursor, Claude Code) all share one mental model that’s worth learning once, because it decides whether your AI agent respects the project’s conventions or fights them. There are two kinds of instruction:

flowchart TD
start["You give the AI<br/>an instruction"] --> kind{"Always-on<br/>or on-demand?"}
kind -->|"Always-on"| rules["RULE<br/>code style, security,<br/>architecture, vendor-safety"]
kind -->|"On-demand"| flows["WORKFLOW<br/>/deploy, /security-audit,<br/>/code-review"]
rules --> agent["Agent behaves<br/>consistently"]
flows --> agent
  • Rules are persistent guidelines applied automatically to every interaction — code style, naming, testing requirements, security standards, and the vendor-customization patterns that keep CodeCanyon code upgrade-safe. You set them once and they’re always active.
  • Workflows are reusable prompts you invoke on demand with a / prefix — /generate-unit-tests, /security-audit, /deploy-production, /code-review. They’re for one-off tasks, not standing policy.

The decision is simple:

You need…Make it a…
Consistent code style across all workRule
Architecture / module-structure patternsRule
Vendor-customization safety (don’t edit vendor core)Rule
Generate tests on requestWorkflow
Run a security audit when askedWorkflow
Deploy to productionWorkflow

Automatic + always → Rule. Manual + on-demand → Workflow. Where the files live differs per editor (Antigravity uses .agent/rules/ and .agent/workflows/, with global rules in ~/.gemini/GEMINI.md; other editors use their own folders), but the model is identical — and ZajLibrary’s own rules layer is built the same way.

The concrete per-editor setup — which folders to create, which rules to seed, how to wire your CodeCanyon-Laravel conventions in — lives in the AI-system → editors & IDEs playbook.

This guide is the mental model for the four parts of a dev environment. When you’re ready to do the setup, the commands and per-environment steps live in the playbooks and handbook:

Set these four parts up once and the rest of the project — deploys, customizations, schema work — sits on a floor that behaves the same everywhere.