@studiometa/forge-cli 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.md +105 -1
  2. package/dist/cli.js +1204 -44
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/completion.d.ts.map +1 -1
  5. package/dist/commands/servers/command.d.ts.map +1 -1
  6. package/dist/commands/servers/handlers.d.ts +5 -0
  7. package/dist/commands/servers/handlers.d.ts.map +1 -1
  8. package/dist/commands/servers/help.d.ts.map +1 -1
  9. package/dist/commands/sites/apply/apply.d.ts +61 -0
  10. package/dist/commands/sites/apply/apply.d.ts.map +1 -0
  11. package/dist/commands/sites/apply/handler.d.ts +8 -0
  12. package/dist/commands/sites/apply/handler.d.ts.map +1 -0
  13. package/dist/commands/sites/apply/nginx.d.ts +31 -0
  14. package/dist/commands/sites/apply/nginx.d.ts.map +1 -0
  15. package/dist/commands/sites/apply/ownership.d.ts +88 -0
  16. package/dist/commands/sites/apply/ownership.d.ts.map +1 -0
  17. package/dist/commands/sites/apply/plan.d.ts +147 -0
  18. package/dist/commands/sites/apply/plan.d.ts.map +1 -0
  19. package/dist/commands/sites/apply/site-file.d.ts +94 -0
  20. package/dist/commands/sites/apply/site-file.d.ts.map +1 -0
  21. package/dist/commands/sites/apply/test-fixtures.d.ts +104 -0
  22. package/dist/commands/sites/apply/test-fixtures.d.ts.map +1 -0
  23. package/dist/commands/sites/command.d.ts.map +1 -1
  24. package/dist/commands/sites/help.d.ts.map +1 -1
  25. package/dist/commands/ssh-keys/sync.d.ts.map +1 -1
  26. package/dist/context.d.ts +15 -1
  27. package/dist/context.d.ts.map +1 -1
  28. package/dist/{error-handler-Adi84RcF.js → error-handler-s-BOJ1-h.js} +16 -4
  29. package/dist/error-handler-s-BOJ1-h.js.map +1 -0
  30. package/dist/index.js +1 -1
  31. package/dist/utils/args.d.ts.map +1 -1
  32. package/dist/utils/resolve.d.ts +19 -13
  33. package/dist/utils/resolve.d.ts.map +1 -1
  34. package/package.json +6 -4
  35. package/schemas/site.schema.json +149 -0
  36. package/dist/error-handler-Adi84RcF.js.map +0 -1
package/README.md CHANGED
@@ -45,7 +45,7 @@ forge <command> [subcommand] [options]
45
45
  | ------------------- | ------- | ---------------------------------------------------- |
46
46
  | `config` | | Manage CLI configuration (`set`, `get`, `delete`) |
47
47
  | `servers` | `s` | List, get, reboot servers |
48
- | `sites` | | List, get sites on a server |
48
+ | `sites` | | List, get sites; apply a site file |
49
49
  | `deployments` | `d` | List deployments, trigger deploys, deploy script |
50
50
  | `databases` | `db` | List, get databases on a server |
51
51
  | `database-users` | | List, get, create, delete database users on a server |
@@ -121,6 +121,7 @@ forge config delete # Delete all stored config
121
121
  ```bash
122
122
  forge servers list # List all servers
123
123
  forge servers get <id> # Get server details
124
+ forge servers public-key <id> # Print the public key of the forge user, for the git host
124
125
  forge servers reboot <id> # Reboot a server
125
126
  forge servers create --name <name> --ip-address <ip> [--php-version php84 --database mariadb1011]
126
127
  # Create a server (Custom VPS by default)
@@ -131,6 +132,97 @@ forge servers create --name <name> --ip-address <ip> [--php-version php84 --data
131
132
  ```bash
132
133
  forge sites list --server <id> # List sites on a server
133
134
  forge sites get <site_id> --server <id> # Get site details
135
+ forge sites apply --file site.json --check # Print the plan, change nothing
136
+ forge sites apply --file site.json # Make the site match the file
137
+ ```
138
+
139
+ #### Declare a site in a file with `sites apply`
140
+
141
+ `sites apply` makes a Forge site match a JSON file kept in the project, for example `config/forge/production/site.json`. Only `server` and `domain` are required. A key that is absent is not managed: `apply` does not read it, compare it or change it.
142
+
143
+ ```json
144
+ {
145
+ "$schema": "https://unpkg.com/@studiometa/forge-cli/schemas/site.schema.json",
146
+ "server": "web-1",
147
+ "domain": "www.example.com",
148
+ "aliases": ["example.com"],
149
+ "type": "php",
150
+ "php": "php83",
151
+ "webDirectory": "/public",
152
+ "repository": {
153
+ "provider": "custom",
154
+ "url": "https://git.example.com/group/project.git",
155
+ "branch": "main"
156
+ },
157
+ "database": { "name": "example", "user": "example" },
158
+ "env": { "APP_ENV": "production" },
159
+ "nginx": "nginx.conf",
160
+ "workers": [{ "command": "php artisan queue:work database --queue=default", "processes": 4 }],
161
+ "schedule": [{ "command": "php artisan schedule:run", "frequency": "minutely" }]
162
+ }
163
+ ```
164
+
165
+ | Key | What `apply` does |
166
+ | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
167
+ | `server`, `domain` | Find the server and the site by exact name (never a partial match, with or without `--exact`). `server` can also be an ID |
168
+ | `type`, `php`, `webDirectory`, `repository` | Create the site when it is missing (`type` is then required) and wait until Forge installs it. On an existing site, update `php`, `webDirectory` and the branch |
169
+ | `aliases` | Add the missing aliases. Never remove one |
170
+ | `database` | Create the database and the user when they are missing. A new user gets a random password, written only into the site `.env` (`DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`, see below) |
171
+ | `env` | Add the missing keys to the site `.env`. A key with another value on Forge is reported, never overwritten |
172
+ | `nginx` | Nginx file, relative to the site file, with Forge template variables. Rendered for the site and written when it differs from Forge (see below) |
173
+ | `workers` | Forge background processes of the site. Create the missing ones, delete the ones of the site that the file does not declare. The directory defaults to the site root |
174
+ | `schedule` | Forge scheduled jobs of the site (`minutely`, `hourly`, `nightly`, `weekly`, `monthly`, `reboot`, or `custom` with `cron`). A relative `artisan` becomes the absolute path of the site |
175
+
176
+ For an application that does not read the Laravel names, `database.env` maps the database name, the user and the password to other `.env` keys. Each key is optional and defaults to the Laravel name. Other values, such as the host, go in `env`:
177
+
178
+ ```json
179
+ {
180
+ "database": {
181
+ "name": "example",
182
+ "user": "example",
183
+ "env": { "name": "DB_NAME", "user": "DB_USER", "password": "DB_PASSWD" }
184
+ },
185
+ "env": { "DB_SERVER": "127.0.0.1" }
186
+ }
187
+ ```
188
+
189
+ `nginx` points to an Nginx file that uses the variables of [Forge Nginx templates](https://forge.laravel.com/docs/servers/nginx-templates). `apply` renders the file locally with the values of the site, so one file works on any server, without a template on the server:
190
+
191
+ | Variable | Value |
192
+ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
193
+ | `{{SITE}}`, `{{SITE_ID}}`, `{{USER}}` | Site name (`www.example.com`), site ID (`456`), site user (`forge`) |
194
+ | `{{DOMAINS}}` | Site name and aliases, separated by spaces |
195
+ | `{{ROOT_PATH}}`, `{{PATH}}`, `{{DIRECTORY}}` | Site root, web directory (`/home/forge/www.example.com/public`) and its relative path (`/public`) |
196
+ | `{{PROXY_PASS}}` | PHP-FPM socket, e.g. `unix:/var/run/php/php8.3-fpm.sock` (`php8.3-fpm-<user>.sock` for an isolated site) |
197
+ | `{{PORT}}`, `{{PORT_V6}}` | `80` and `[::]:80` (with `default_server` for the site named `default`) |
198
+ | `{{SERVER_PUBLIC_IP}}`, `{{SERVER_PRIVATE_IP}}` | IP addresses of the server (empty when there is no private IP) |
199
+
200
+ Spaces inside the braces are allowed (`{{ SITE_ID }}`). An unknown variable is an error (exit `3`). On a new site, `apply` writes the file after Forge installs the site. On an existing site, the plan shows the diff from Forge to the rendered file (CRLF and the final newline do not count), and `apply` writes it. When `apply` also changes the site or its aliases, it reads the site again and renders the file with the new values. Forge returns the full `server` block on old servers and only its content on recent ones: write the file in the same form as `forge nginx get` prints it.
201
+
202
+ Forge clones the repository when it creates the site, with the key of the `forge` user of the server. Add this key to the git host (for example as a deploy key) before `apply` creates the site:
203
+
204
+ ```bash
205
+ forge servers public-key web-1 > server.pub # The key only; --format json gives { "public_key": "..." }
206
+ ```
207
+
208
+ A worker or a scheduled job matches the file when its command, directory, processes (or frequency) and user are the same. Commands are compared after normalization: quotes (`'database'` and `database`), repeated spaces and a relative `artisan` (`php artisan` and `php /home/forge/www.example.com/artisan`) do not count as differences. Forge cannot update a worker: a changed worker is created again, then the old one is deleted.
209
+
210
+ `workers` and `schedule` delete what the file does not declare, so `apply` deletes only what surely belongs to the site, because a server often hosts several sites:
211
+
212
+ - A scheduled job belongs to the site when Forge lists it on the site, or when its command uses the site path and no other site path.
213
+ - A worker belongs to the site when its directory is the site path or inside it. Without a directory, its command must use the site path and no other site path, and Forge must associate it with the site.
214
+ - Paths are compared on whole segments: `/home/forge/example.com` does not own `/home/forge/example.com-staging` or `/home/forge/example.com.old`.
215
+ - Workers and scheduled jobs that run as `root` are never deleted, and neither are the ones whose owner is not certain: they are reported as kept.
216
+ - Without the `workers` key, no worker is read or deleted. With `"workers": []`, all the workers of the site are deleted. The same applies to `schedule`.
217
+ - The site, its aliases, its database and its `.env` keys are never deleted.
218
+
219
+ With `--check`, `apply` prints the plan and changes nothing. It exits `0` when Forge matches the file, `1` when it differs, and another code on an error (`3` for an invalid file). Every deletion shows its ID and full command. The plan gives `.env` key names only, never values, and shows a generated password as `<generated>`. With `--format json`, it prints `{ "inSync": false, "changes": [...], "warnings": [...] }`.
220
+
221
+ Run `--check` in merge request pipelines and `apply` on the main branch:
222
+
223
+ ```bash
224
+ forge sites apply --file config/forge/production/site.json --check
225
+ forge sites apply --file config/forge/production/site.json
134
226
  ```
135
227
 
136
228
  ### `deployments` (`d`) — Manage deployments
@@ -340,11 +432,23 @@ All write operations (`deploy`, `create`, `delete`, `reboot`, `restart`, `update
340
432
  | `--org <slug>` | | Organization slug (overrides config and env) |
341
433
  | `--server <id>` | | Server ID or name (auto-resolved if non-numeric) |
342
434
  | `--site <id>` | | Site ID or domain (auto-resolved if non-numeric) |
435
+ | `--exact` | | Match `--server` and `--site` names exactly |
343
436
  | `--format <fmt>` | `-f` | Output format: `json`, `human`, `table` |
344
437
  | `--no-color` | | Disable colored output |
345
438
  | `--help` | `-h` | Show help |
346
439
  | `--version` | `-v` | Show version |
347
440
 
441
+ ### Exact name matching
442
+
443
+ `--server` and `--site` accept a numeric ID or a name. A name resolves to the server or site with that exact name first, then to a single partial match: `--site example.com` resolves to `shop.example.com` when no site is named `example.com`.
444
+
445
+ In CI and scripts, turn on exact matching with the `--exact` flag or the `FORGE_EXACT_MATCH=1` environment variable. A name must then match exactly (case-insensitive); otherwise the command fails and lists the near matches.
446
+
447
+ ```bash
448
+ FORGE_EXACT_MATCH=1 forge deployments deploy --server 123 --site example.com
449
+ forge deployments deploy --server 123 --site example.com --exact
450
+ ```
451
+
348
452
  ## AI Agent Integration
349
453
 
350
454
  The CLI is designed for programmatic use by AI agents: