@webmate-studio/cli 0.3.62 → 0.4.0

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.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # @webmate-studio/cli
2
+
3
+ Command-line tool for Webmate Studio component development and synchronisation.
4
+
5
+ ```bash
6
+ npm install -g @webmate-studio/cli
7
+ ```
8
+
9
+ ## Command overview
10
+
11
+ | Command | Purpose |
12
+ |---|---|
13
+ | `wm init [dir]` | Bootstrap a new component project |
14
+ | `wm generate [type] [name]` | Scaffold a new component or island |
15
+ | `wm dev` | Run the local preview server |
16
+ | `wm install` | Install npm deps in the project and per-component `package.json`s |
17
+ | `wm login` / `wm logout` / `wm whoami` | CLI authentication |
18
+ | `wm components ls` | Browse components in your org (or another, with `--org`) |
19
+ | `wm versions [component]` | List published versions of a component |
20
+ | `wm status [dir]` | Show local vs remote sync state of a component |
21
+ | `wm pull [dir]` | Download a component version from the CMS |
22
+ | `wm push [dir]` | Upload local changes as a new version |
23
+ | `wm clone --from <uuid>[@<ver>]` | Duplicate a component into another (or the same) org |
24
+
25
+ ## Authentication
26
+
27
+ Most sync commands require a Webmate session. The CLI looks for credentials in this order:
28
+
29
+ 1. `WEBMATE_TOKEN` environment variable (with optional `WEBMATE_BASE_URL`)
30
+ 2. `~/.webmate/auth.json` (created by `wm login`)
31
+ 3. `.webmate/config.json` in the current workspace (with `apiToken`)
32
+
33
+ `wm login` itself resolves the target `baseUrl` in this order:
34
+
35
+ 1. `--base-url` flag
36
+ 2. `WEBMATE_BASE_URL` environment variable
37
+ 3. `baseUrl` field in the nearest `.webmate/config.json` (workspace default)
38
+ 4. The built-in default
39
+
40
+ A workspace-level `.webmate/config.json` with just `{ "baseUrl": "https://app.webmate-studio.com" }` is enough to pin all subsequent `wm login` calls in that directory to the right environment.
41
+
42
+ ### Interactive login (recommended)
43
+
44
+ ```bash
45
+ wm login
46
+ ```
47
+
48
+ Opens your browser at the active Webmate instance, shows a verification code, and waits while you click **Authorize**. The resulting token is saved to `~/.webmate/auth.json` with `0600` permissions.
49
+
50
+ ```bash
51
+ wm login --base-url https://app.webmate-studio.io # switch environment
52
+ wm login --force # overwrite an existing session
53
+ ```
54
+
55
+ ### Non-interactive (CI/CD)
56
+
57
+ ```bash
58
+ export WEBMATE_TOKEN="wms_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxx"
59
+ export WEBMATE_BASE_URL="https://app.webmate-studio.io"
60
+ wm push ./my-hero
61
+ ```
62
+
63
+ Create tokens under **Settings → API Tokens** in the CMS.
64
+
65
+ ## The sync workflow
66
+
67
+ A typical component lifecycle:
68
+
69
+ ```bash
70
+ # Start from an existing component
71
+ wm components ls # find the UUID you want
72
+ wm pull ./my-hero --id <uuid> # download it
73
+ # … edit files …
74
+ wm status # see what changed
75
+ wm push # publish a new version
76
+ ```
77
+
78
+ Or from scratch:
79
+
80
+ ```bash
81
+ wm init my-components
82
+ cd my-components
83
+ wm generate my-hero # scaffolds component.json with a fresh UUID
84
+ wm dev # iterate locally
85
+ wm push --force # first push needs --force (no baseVersion yet)
86
+ ```
87
+
88
+ ### `.webmate.json` — local manifest
89
+
90
+ After `wm pull`, `wm push`, or `wm clone` the CLI writes a `.webmate.json` next to your component:
91
+
92
+ ```json
93
+ {
94
+ "componentId": "f97e376c-f8bd-42e4-9b9a-c2d62d4dcc9a",
95
+ "baseVersion": "cmn7b9r7p00014i013dk007hw",
96
+ "version": "1.0.5",
97
+ "pulledAt": "2026-05-20T18:31:46.791Z",
98
+ "fileHashes": {
99
+ "component.html": "c2689e…",
100
+ "component.json": "088e8d…"
101
+ }
102
+ }
103
+ ```
104
+
105
+ The CLI uses `baseVersion` for optimistic locking on push and `fileHashes` for change detection. The Workbench desktop app shares the same file format.
106
+
107
+ ### Conflict handling
108
+
109
+ `wm push` checks the remote head before uploading. If the remote moved since your last pull, the push is refused:
110
+
111
+ ```
112
+ [ERROR] Remote moved: your baseVersion is cmABC… but remote latest is cmDEF… (1.0.6).
113
+ Run wm pull first, then push again.
114
+ ```
115
+
116
+ Resolve by pulling, merging locally, and pushing again.
117
+
118
+ `wm pull` warns about uncommitted local changes and prompts before overwriting, unless `--force` is given.
119
+
120
+ ### Git snapshot
121
+
122
+ If the component directory is a git repo, `wm pull` and `wm push` automatically `git add . && git commit` a snapshot of the synced state. This is purely a local backup — nothing is pushed to a remote. Disable with `--no-git`.
123
+
124
+ ## Commands
125
+
126
+ ### `wm login`
127
+
128
+ ```
129
+ wm login [--token <wms_...>] [--base-url <url>] [-f|--force]
130
+ ```
131
+
132
+ Default flow opens the browser and waits for a verification click — no copy/paste needed. With `--token` it accepts a pre-generated token directly (useful when scripting or behind firewalls).
133
+
134
+ ### `wm whoami`
135
+
136
+ ```
137
+ wm whoami [--local]
138
+ ```
139
+
140
+ Shows the active token source, base URL, user, and organisation. `--local` skips the `/api/auth/me` round-trip.
141
+
142
+ ### `wm components ls`
143
+
144
+ ```
145
+ wm components ls [--org <slug>] [--org-id <id>]
146
+ [--category <cat>] [--search <q>]
147
+ ```
148
+
149
+ Lists components in your active org. With `--org` or `--org-id` you can browse another org you are a member of (gated server-side by `UserOrganizationRole`).
150
+
151
+ ### `wm versions [component]`
152
+
153
+ ```
154
+ wm versions [<uuid> | <dir>] [--limit <n>]
155
+ ```
156
+
157
+ Lists all published versions, newest first. The positional argument can be a UUID, a directory containing `component.json` or `.webmate.json`, or omitted to use the current directory.
158
+
159
+ ### `wm status [component]`
160
+
161
+ ```
162
+ wm status [<dir>] [--offline]
163
+ ```
164
+
165
+ Reports one of:
166
+ - `in sync` — local file hashes match the manifest and the manifest points at the remote head
167
+ - `local changes` — you have uncommitted edits
168
+ - `behind remote` — remote has moved past your `baseVersion`
169
+ - `diverged` — both local edits **and** a moved remote
170
+ - `unlinked` — no `.webmate.json` yet
171
+
172
+ `--offline` skips the network call and only reports the local diff.
173
+
174
+ ### `wm pull [component]`
175
+
176
+ ```
177
+ wm pull [<dir>] [--id <uuid>] [--version <ver|cuid|latest>]
178
+ [--merge] [--force] [--no-git]
179
+ ```
180
+
181
+ Downloads the source bundle of the requested version and rewrites `.webmate.json`. Defaults to `latest`. With `--merge`, local files not present in the remote bundle are kept; without it the directory is brought into a 1:1 match.
182
+
183
+ ### `wm push [component]`
184
+
185
+ ```
186
+ wm push [<dir>] [-m|--message <msg>] [--force] [--no-git]
187
+ ```
188
+
189
+ Uploads the current file tree as a new patch version. The server bumps SemVer automatically (e.g. `1.0.5 → 1.0.6`). The CLI:
190
+
191
+ - runs a pre-flight `GET /versions` to detect a moved remote and refuses to upload on mismatch
192
+ - skips uploads when nothing changed since the last pull/push (override with `--force`)
193
+ - treats HTTP 409 as a late-conflict signal and HTTP 422 as a build failure
194
+
195
+ ### `wm clone --from`
196
+
197
+ ```
198
+ wm clone --from <component-uuid>[@<version|cuid|latest>]
199
+ [--target-org <slug>] [--target-org-id <id>]
200
+ [--name <displayName>] [--category <category>]
201
+ [--to <dir>] [--merge] [--force] [--no-git]
202
+ ```
203
+
204
+ Duplicates an existing component into another (or the same) org. You must be a member of both the source and the target org. The server mints a fresh UUID, copies the source `source.json` into a patched file map (new id, optional new name), and creates the new component row without a published version yet. The CLI writes the files locally; the first real publish happens when you run `wm push --force`.
205
+
206
+ ## Where things live
207
+
208
+ - Token store: `~/.webmate/auth.json` (chmod `0600`)
209
+ - Workspace org config: `<your-apps-root>/.webmate/config.json`
210
+ - Per-component manifest: `<component>/.webmate.json`
211
+
212
+ ## Troubleshooting
213
+
214
+ **`Not logged in.`** → Run `wm login`, or check `WEBMATE_TOKEN` is set.
215
+
216
+ **`Remote moved: your baseVersion is …`** → Someone else (or the Workbench, or another machine) pushed a new version. Run `wm pull` to re-sync, resolve any conflicts, then `wm push` again.
217
+
218
+ **`Build failed (HTTP 422)`** → The build service rejected the artifact. Check the server logs in the CMS for details; common causes are island files that fail to bundle or invalid `component.json`.
219
+
220
+ **`Token rejected (HTTP 401)`** → The token has been revoked or expired. Run `wm login --force` to refresh it.
221
+
222
+ **`No .webmate.json found`** (`wm push`) → The directory has not been linked to a component yet. Either run `wm pull --id <uuid>` first, or push with `--force` to skip the safety check (useful right after `wm generate` or `wm clone`).
package/bin/wm.mjs CHANGED
@@ -4,6 +4,30 @@ import { Command } from 'commander';
4
4
  import { devCommand } from '../src/commands/dev.js';
5
5
  import { initCommand } from '../src/commands/init.js';
6
6
  import { generate } from '../src/commands/generate.js';
7
+ import { installCommand } from '../src/commands/install.js';
8
+ import { loginCommand } from '../src/commands/login.js';
9
+ import { logoutCommand } from '../src/commands/logout.js';
10
+ import { whoamiCommand } from '../src/commands/whoami.js';
11
+ import { pullCommand } from '../src/commands/pull.js';
12
+ import { pushCommand } from '../src/commands/push.js';
13
+ import { buildCommand } from '../src/commands/build.js';
14
+ import { statusCommand } from '../src/commands/status.js';
15
+ import { resetCommand } from '../src/commands/reset.js';
16
+ import { doctorCommand } from '../src/commands/doctor.js';
17
+ import { cloneCommand } from '../src/commands/clone.js';
18
+ import { componentsListCommand } from '../src/commands/components.js';
19
+ import { projectsListCommand } from '../src/commands/projects.js';
20
+ import { versionsCommand } from '../src/commands/versions.js';
21
+ import {
22
+ coreListCommand,
23
+ coreCloneCommand,
24
+ coreInitCommand,
25
+ coreInitAllCommand,
26
+ coreAttachCommand,
27
+ corePushCommand,
28
+ coreStatusCommand
29
+ } from '../src/commands/core.js';
30
+ import { isLocallyKnownSuperAdmin } from '../src/utils/auth-storage.js';
7
31
  import { readFileSync } from 'fs';
8
32
  import { fileURLToPath } from 'url';
9
33
  import { dirname, join } from 'path';
@@ -45,4 +69,188 @@ program
45
69
  .option('-o, --open', 'Open browser automatically')
46
70
  .action(devCommand);
47
71
 
72
+ // wm install - Install component + project dependencies
73
+ program
74
+ .command('install')
75
+ .description('Install npm dependencies for the project and every component that ships its own package.json')
76
+ .option('-f, --force', 'Re-install even if node_modules already exists')
77
+ .option('--prune', 'Remove leftover package-lock.json + node_modules under stub package.json files (keeps the package.json itself)')
78
+ .action(installCommand);
79
+
80
+ // wm login - Authenticate against Webmate Studio
81
+ program
82
+ .command('login')
83
+ .description('Authenticate against Webmate Studio and store credentials in ~/.webmate/auth.json')
84
+ .option('--token <token>', 'API token (wms_...). If omitted, prompts interactively.')
85
+ .option('--base-url <url>', 'Webmate base URL (overrides WEBMATE_BASE_URL env and workspace .webmate/config.json)')
86
+ .option('-f, --force', 'Overwrite existing credentials without confirmation')
87
+ .action(loginCommand);
88
+
89
+ // wm logout - Remove stored credentials
90
+ program
91
+ .command('logout')
92
+ .description('Remove stored Webmate credentials')
93
+ .action(logoutCommand);
94
+
95
+ // wm whoami - Show active identity
96
+ program
97
+ .command('whoami')
98
+ .description('Show the active Webmate identity')
99
+ .option('--local', 'Skip server round-trip; show stored values only')
100
+ .action(whoamiCommand);
101
+
102
+ // wm pull - Download a component version from the CMS
103
+ program
104
+ .command('pull [component]')
105
+ .description('Download component source from the CMS into the local directory')
106
+ .option('--id <uuid>', 'Component UUID (overrides .webmate.json and component.json)')
107
+ .option('--version <ver>', 'Version to pull (SemVer like 1.0.5, CUID, or "latest")', 'latest')
108
+ .option('--merge', 'Keep local files that are not present in the remote version')
109
+ .option('--force', 'Overwrite local changes without prompting')
110
+ .option('--no-git', 'Do not auto-commit the pulled state (when in a git repo)')
111
+ .action(pullCommand);
112
+
113
+ // wm push - Upload local component source as a new version
114
+ program
115
+ .command('push [component]')
116
+ .description('Upload local component files to the CMS as a new version')
117
+ .option('-m, --message <msg>', 'Commit message for the new version')
118
+ .option('--force', 'Bypass conflict and unlinked-component checks')
119
+ .option('--no-git', 'Do not auto-commit the pushed state (when in a git repo)')
120
+ .action(pushCommand);
121
+
122
+ // wm build - Local dry-run of the cloud build pipeline (no push, no upload)
123
+ program
124
+ .command('build [component]')
125
+ .description('Run the cloud build pipeline locally to validate a component before push')
126
+ .option('--out <dir>', 'Write built artefacts (component.html/css, islands/*) to <dir>')
127
+ .option('--verbose', 'Show all islands in the summary, not just the first five')
128
+ .action(buildCommand);
129
+
130
+ // wm status - Show local vs remote sync state
131
+ program
132
+ .command('status [component]')
133
+ .description('Show local vs remote sync state for a component')
134
+ .option('--offline', 'Skip the remote round-trip')
135
+ .action(statusCommand);
136
+
137
+ // wm reset - Drop .webmate.json sync pointers so the next push registers fresh
138
+ program
139
+ .command('reset [dir]')
140
+ .description('Reset sync pointers in .webmate.json — use after copying components into a new workspace')
141
+ .action(resetCommand);
142
+
143
+ // wm doctor - Run a local health check on the workspace setup
144
+ program
145
+ .command('doctor [dir]')
146
+ .description('Scan the workspace for common setup issues (auth, bindings, stale metas)')
147
+ .action(doctorCommand);
148
+
149
+ // wm components - Browse remote components
150
+ const componentsCmd = program
151
+ .command('components')
152
+ .description('Browse components in the current or another organization');
153
+
154
+ componentsCmd
155
+ .command('ls')
156
+ .description('List components in an organization')
157
+ .option('--org <slug>', 'Organization slug (defaults to your current org)')
158
+ .option('--org-id <id>', 'Explicit organization ID (overrides --org)')
159
+ .option('--category <category>', 'Filter by category')
160
+ .option('--search <query>', 'Free-text search across name/displayName/description')
161
+ .action(componentsListCommand);
162
+
163
+ // wm projects - Browse remote projects (= ComponentRepositories)
164
+ const projectsCmd = program
165
+ .command('projects')
166
+ .alias('repos')
167
+ .description('Browse projects (= component repositories) in the current or another organization');
168
+
169
+ projectsCmd
170
+ .command('ls')
171
+ .description('List projects in an organization')
172
+ .option('--org <slug>', 'Organization slug (defaults to your current org)')
173
+ .option('--org-id <id>', 'Explicit organization ID (overrides --org)')
174
+ .action(projectsListCommand);
175
+
176
+ // wm versions - List versions of a component
177
+ program
178
+ .command('versions [component]')
179
+ .description('List versions of a component (UUID, directory, or current dir)')
180
+ .option('--limit <n>', 'Maximum number of versions to show')
181
+ .action(versionsCommand);
182
+
183
+ // wm clone - Two modes:
184
+ // * `wm clone <project>` — bootstrap a workspace from a project (= ComponentRepository)
185
+ // * `wm clone --from <uuid>[@version]` — server-side single-component clone (legacy)
186
+ program
187
+ .command('clone [project...]')
188
+ .description('Clone a project workspace (positional, spaces allowed), or a single component with --from')
189
+ .option('--from <spec>', '[Component mode] Source as <component-uuid>[@<version|cuid|latest>]')
190
+ .option('--to <dir>', 'Target directory (project mode: defaults to slug; component mode: current dir)')
191
+ .option('--target-org <slug>', '[Component mode] Slug of the target organization')
192
+ .option('--target-org-id <id>', '[Component mode] Explicit target organization ID')
193
+ .option('--name <displayName>', '[Component mode] New display name for the cloned component')
194
+ .option('--category <category>', '[Component mode] Override category on the clone')
195
+ .option('--merge', '[Component mode] Keep local files that are not in the cloned source')
196
+ .option('--force', 'Overwrite/continue without prompting')
197
+ .option('--no-git', 'Do not auto-commit the cloned files (when in a git repo)')
198
+ .action(cloneCommand);
199
+
200
+ // wm core - SuperAdmin-only authoring of global Core components.
201
+ // The command stays registered for everyone so that anyone whose cached
202
+ // token is stale can still invoke it (server enforces the real check)
203
+ // — but for normal users it's hidden from `wm --help` so the help
204
+ // output stays focused on the everyday org-component workflow.
205
+ const coreCmd = program
206
+ .command('core', { hidden: !isLocallyKnownSuperAdmin() })
207
+ .description('SuperAdmin authoring of global Core components (platform owners only)');
208
+
209
+ coreCmd
210
+ .command('ls')
211
+ .description('List all Core components registered on the platform')
212
+ .action(coreListCommand);
213
+
214
+ coreCmd
215
+ .command('clone [name]')
216
+ .description('No name: clone every Core component into a fresh workspace. With name: pull just that one.')
217
+ .option('--to <dir>', 'Target directory (workspace mode default: ./webmate-core; single mode default: current)')
218
+ .option('--merge', 'Keep local files that are not in the cloned source (single mode)')
219
+ .option('--force', 'Overwrite local content without prompting')
220
+ .action(coreCloneCommand);
221
+
222
+ coreCmd
223
+ .command('init [dir]')
224
+ .description('Register the local component (from component.json) as a new Core component on the server')
225
+ .option('--display-name <name>', 'Override the displayName')
226
+ .option('--category <category>', 'Override the category')
227
+ .option('--description <description>', 'Override the description')
228
+ .action(coreInitCommand);
229
+
230
+ coreCmd
231
+ .command('attach <name>')
232
+ .description('Bind an existing (UI-created) Core record to the current folder without pushing')
233
+ .option('--dir <dir>', 'Target directory (default: current)')
234
+ .option('--force', 'Overwrite an existing .webmate.json in this folder')
235
+ .action(coreAttachCommand);
236
+
237
+ coreCmd
238
+ .command('init-all [dir]')
239
+ .description('Bulk: init + push every component subfolder under <dir>/components/ (or <dir> directly)')
240
+ .option('-y, --yes', 'Skip the confirmation prompt')
241
+ .action(coreInitAllCommand);
242
+
243
+ coreCmd
244
+ .command('push [dir]')
245
+ .description('Build and push a new version. From a workspace root: bulk-push every changed component.')
246
+ .option('-m, --message <msg>', 'Commit message for the new version')
247
+ .option('--force', 'Push even when local hashes match the last recorded state')
248
+ .option('--no-git', 'Do not auto-commit the pushed state (when in a git repo)')
249
+ .action(corePushCommand);
250
+
251
+ coreCmd
252
+ .command('status [name]')
253
+ .description('Workspace overview (in-sync / changes-local / behind-remote per component) or single-component info')
254
+ .action(coreStatusCommand);
255
+
48
256
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webmate-studio/cli",
3
- "version": "0.3.62",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Webmate Studio CLI - Build and manage your Webmate components",
6
6
  "keywords": [
@@ -27,6 +27,10 @@
27
27
  ".": "./src/index.js",
28
28
  "./package.json": "./package.json"
29
29
  },
30
+ "scripts": {
31
+ "prepublishOnly": "git diff --quiet HEAD && git fetch && git merge-base --is-ancestor HEAD origin/main",
32
+ "postversion": "git push --follow-tags"
33
+ },
30
34
  "publishConfig": {
31
35
  "access": "public"
32
36
  },