Release v2#76
Conversation
…s for JSON and TSV formats
…nhancing the 'raisely init' command
…UID and improve response buffer decoding
…le in 'raisely init'
… redirect script for local development
…n via external API
… to conditionally open the development site
- Wire node --test through the npm test script (no new dependencies) - Create tests/fixtures/ with legacy, v2, mixed, empty, legacy-multi, and v2-multi repo structures - Add .gitignore negation so fixture stylesheets/ and components/ dirs are tracked - Implement src/actions/layout.js exporting resolveCampaignPaths and detectLayout - Add tests/layout.test.js with 12 tests covering all fixtures, hyphenated paths, missing dirs, and multi-campaign repos Made-with: Cursor
…st-infra-and-path-resolver-actionslayoutjs cli v2 test infra and path resolver actionslayoutjs
Co-authored-by: Cursor <cursoragent@cursor.com>
Adds a one-shot, idempotent `raisely migrate` command that moves a repo
from the v1 layout (pages/<campaign>/*, stylesheets/<campaign>/*) to the
v2 layout (campaigns/<campaign>/pages/*, campaigns/<campaign>/stylesheets/*).
- src/migrate.js: pure migrator (named export) accepting repoRoot,
campaign UUIDs, and an injectable getCampaign resolver; returns a
structured { moved, renamed, deleted, orphans } report
- Renames <campaign>.scss to main.scss at the top level only; recursive
calls pass null for entryScssPrefix so nested files with the same name
are never incorrectly renamed
- Preserves partials as-is; detects and surfaces orphan folders with a reason
- Processes each campaign independently — one failure does not block others
- Idempotent: skips files already at their destination, safe to re-run
- Deletes empty root pages/ and stylesheets/ directories after migration
- src/cli.js: registers the migrate command via actionBuilder
- tests/migrate.test.js: 7 fixture-based tests covering single-campaign
migration, partial preservation, nested-scss rename safety, multi-campaign
+ empty-root cleanup, idempotency rerun, orphan handling, and getCampaign
failure isolation
Co-authored-by: Cursor <cursoragent@cursor.com>
…IR-1517)
`dstName === 'main.scss'` was too broad: any file whose destination
happened to be main.scss (e.g. a source file already called main.scss)
was pushed into report.renamed instead of report.moved.
Replace the condition with `entryScssPrefix && entry.name ===
\`${entryScssPrefix}.scss\`` so the rename tag only applies to the one
file that was actually renamed.
Add a dedicated fixture (legacy-only-main-scss) and regression test to
cover this path.
Co-authored-by: Cursor <cursoragent@cursor.com>
- fetchStyles / processStyles / uploadStyles resolve paths via resolveCampaignPaths instead of hard-coded stylesheets/<path> joins - syncStyles writes to campaigns/<path>/stylesheets/main.scss; syncPages writes to campaigns/<path>/pages/ - compileAllLocalPages globs campaigns/*/pages/**/*.json - deploy page glob and uploadStyles call updated to v2 paths - start watcher moved from stylesheets/ to campaigns/, filtering on the stylesheets/ segment Co-authored-by: Cursor <cursoragent@cursor.com>
- 16 tests across fetchStyles, processStyles, compileAllLocalPages, and the deploy-time page glob - Fixture page JSON files updated with body and campaignUuid fields to enable full compilation assertions - Multi-campaign coverage via v2-multi fixture throughout Co-authored-by: Cursor <cursoragent@cursor.com>
…isely-migrate-command SIR-1517 - cli v2 raisely migrate command
…itch-readers-and-writers-to-v2-layout SIR-1519 - cli v2 switch readers and writers to v2 layout
Keep raisely local styled during SCSS failures by caching and serving the last successful CSS, retrying transient transpiler issues once, and avoiding process exit on 401. Co-authored-by: Cursor <cursoragent@cursor.com>
Protect the local styles route behavior with mocked transpiler coverage for cold cache, warm cache, 401 fallback, and single-retry 5xx handling. Co-authored-by: Cursor <cursoragent@cursor.com>
Use a single retry budget for network and 5xx failures, simplify fallback flow, and keep retry failure logging behavior consistent. Co-authored-by: Cursor <cursoragent@cursor.com>
Add coverage for network-error-plus-5xx single-retry behavior and assert post-retry 5xx body/status logs for better diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
…1520) Deduplicate setTimeout wrappers by introducing a shared sleep helper and replace the unreachable retry fallback with an explicit invariant error for clearer maintenance. Co-authored-by: Cursor <cursoragent@cursor.com>
Detect legacy and mixed layouts before running update, deploy, local, and start so these commands fail with a clear upgrade path. Co-authored-by: Cursor <cursoragent@cursor.com>
Add helper and command guard tests for legacy refusal behavior and v2 pass-through across update, deploy, local, and start. Co-authored-by: Cursor <cursoragent@cursor.com>
…tion-gate-on-raisely-deploy-no-validate SIR-1522 - cli v2 pre flight validation gate on raisely deploy no validate
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…on-in-raisely-start SIR-1523 - Validate watched saves before upload
Co-authored-by: Cursor <cursoragent@cursor.com>
…gelog-readme-ai-contextclimd SIR-1524 - Update v2 release docs
Capture per-command outcome, duration, and error metadata so CLI usage can be measured reliably. Flush pending telemetry before command exit and cover payload/flush behavior with focused tests.
Prevent CLI commands from hanging after completion by aborting telemetry metadata and event requests after 10 seconds before flush waits. Co-authored-by: Cursor <cursoragent@cursor.com>
Remove the redundant pending.clear() in flushTelemetry so newly queued telemetry promises cannot be cleared while a flush is in progress. Co-authored-by: Cursor <cursoragent@cursor.com>
Switch CLI API calls to the new x-raisely-client: 'cli' header value across telemetry, logout token revoke, and shared request transport. Co-authored-by: Cursor <cursoragent@cursor.com>
…etry-to-the-raisely-cli SIR-1556 - Track command telemetry events
Rename telemetry payload shorthand keys to explicit field names and attach a generated sessionId to each event for per-session correlation. Co-authored-by: Cursor <cursoragent@cursor.com>
Align the logout import with jwt-decode v4 module exports so the command loads without runtime import errors. Co-authored-by: Cursor <cursoragent@cursor.com>
…elemetry SIR-1556 - Improve telemetry payload
chore: bump version to 2.0.0-alpha.3 in package.json
…655) Scaffolds the `media` command group with two subcommands: - `list [--campaign <slug>] [--organisation <uuid>]` — outputs the media array for a campaign or organisation; falls back to the first campaign in .raisely.json when no flag is passed - `delete <uuid> [--campaign <slug>] [--organisation <uuid>]` — removes a media asset by UUID with no confirmation prompt Both commands use the existing auth flow and are registered via the actionBuilder lazy-load pattern in cli.js.
- Null-guard getResponseContentType to handle responses with no Content-Type header - Add rawBody support to the request body chain in api.js - Add uploadCampaignMedia and uploadOrganisationMedia with a manual multipart builder (avoids native Blob/FormData incompatibility with node-fetch v3 on Node 18)
- New `media upload <file-or-url>` subcommand with --campaign, --organisation, --force, and --json flags - Auto-detects file path vs URL by http(s):// prefix - Confirmation prompt shows resolved target and campaign source (from .raisely.json); skipped by --force or --json - Registers the command under the existing media command group in cli.js - Documents new commands in README
…656) - Replace hand-built multipart body with the form-data package - Fix Content-Type casing: extract header value from form.getHeaders() and assign it with the uppercase key so it correctly overwrites the application/json Content-Type set by api.js - Pin form-data to 4.0.6
…ly-media-upload SIR-1656: Add `raisely media upload`
…ly-media-list-and-raisely-media-delete SIR-1655: Add `raisely media list` and `raisely media delete`
There was a problem hiding this comment.
Risk: high. Escalating for human review — this v2 release rewrites authentication (OAuth PKCE, keychain storage) and removes v1 layout compatibility, which are Tier 1 sensitive-domain changes. Reviewers were not assigned per automation policy.
Risk Assessment (Framework v2.0)
Framework (v2.0)
| Dimension | Tier | Score | Rationale |
|---|---|---|---|
| Security | 1 | Medium | OAuth PKCE rewrite with OS keychain token storage; standard S256 flow, no hardcoded secrets or eval usage |
| Sensitive domains | 1 | High | Modifies authentication/authorization: login, logout, credentials, OAuth token exchange, org switching |
| Data integrity | 1 | Low | CLI file operations only; no database queries or multi-tenancy scoping concerns |
| Deployment | 1 | Medium | deploy.js overhauled with preflight validation; breaking v1 layout removal affects deploy/update/local/start paths |
| Scope | 2 | High | 71 files changed (~16.6k additions); substantial modifications across core src modules |
| Architecture | 2 | Medium | New action modules (layout, pages, validate, media), CLI command restructuring |
| Feature flags | 2 | Low | CLI dev tool release; feature flags not applicable for internal tooling |
| Dependencies | 2 | High | New dependencies (@napi-rs/keyring, jwt-decode); lockfile migration from yarn to npm/pnpm |
Mitigating Factors
- Comprehensive test suite added covering migrate, deploy, local, validate, layout, and telemetry
- OAuth implementation uses industry-standard PKCE with S256
- Documented one-shot upgrade path via
raisely migrate - CHANGELOG explicitly documents breaking layout changes
Findings
| # | Finding | Status | Resolution |
|---|
Outstanding findings: 0
Overall risk: HIGH
Decision: ESCALATE
Rationale: Tier 1 Sensitive Domains is High due to the full OAuth PKCE authentication rewrite and credential management changes. Per decision rule 1, any Tier 1 High dimension requires human review before merge.
Sent by Cursor Approval Agent: Nemo - Pull Request Router and Approver
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e709be4. Configure here.
| continue; | ||
| } | ||
| pageTasks.push(limit(() => uploadPage(pageData))); | ||
| } |
There was a problem hiding this comment.
Deploy uploads out-of-scope pages
High Severity
During deploy, page JSON is uploaded whenever it has a uuid, but entries without campaignUuid are not checked against config.campaigns. Local page files under other campaigns (or with the field omitted) can still be PATCHed to the API when deploy is meant to target only configured campaign UUIDs.
Reviewed by Cursor Bugbot for commit e709be4. Configure here.
|
|
||
| const pages = await api({ | ||
| path: `/campaigns/${uuid}/pages?private=1&includeBody=1&limit=999`, | ||
| }); |
There was a problem hiding this comment.
Page sync truncates at 999
Medium Severity
syncPages loads campaign pages with a fixed limit=999 and no pagination loop. Campaigns with more than 999 pages only partially sync locally, so raisely update can leave page files missing without any warning.
Reviewed by Cursor Bugbot for commit e709be4. Configure here.
| let nextStylesRequestId = 0; | ||
| let lastAppliedStylesRequestId = 0; | ||
|
|
||
| const transpileEndpoint = `${transpilerUrl.replace(/\/$/, '')}/transpile`; |
There was a problem hiding this comment.
Local transpiler URL doubled
Medium Severity
raisely local always appends /transpile to SASS_TRANSPILER_URL. If the env var already includes /transpile (as validateCampaignSass allows), the local server requests a wrong path such as …/transpile/transpile, breaking SCSS compilation while deploy validation may still succeed.
Reviewed by Cursor Bugbot for commit e709be4. Configure here.




Raisely CLI v2
Ships the v2 CLI. This is a breaking release: it introduces a new repository layout, migration tooling, deploy-time validation, OAuth login, telemetry, media commands, and a full test suite.
Highlights
Breaking: new v2 repository layout
campaigns/<campaign-path>/, the entry SCSS file is nowmain.scss, and shared components stay at the repo root.update,deploy, andlocalso v2 commands fail clearly instead of misreading old repos.Migration
raisely migratecommand upgrades existing v1 repositories to the v2 layout, with a report that correctly classifies moved-vs-renamed files.Auth
@napi-rs/keyring).pages:update.Validation
raisely deploy(opt out with--no-validate), with bounded concurrency and hardened error handling.raisely start.node-sassdependency).raisely localresilience401responses.--portto override the default port and--no-opento skip opening the browser.New commands
raisely list— list all campaigns (--json/--tsvoutput).raisely media list,raisely media upload,raisely media delete— manage campaign media.raisely init --uuid <uuid>— initialize a campaign directly by UUID.Telemetry
Tooling and tests
node:testtovitestand added coverage across campaigns, deploy, layout, local, migrate, start, telemetry, and validate.yarn.lockforpackage-lock.json+pnpm-lock.yaml; pinned Node 18-compatible dependency versions.Breaking changes
raisely migrateon existing v1 repos before using v2 commands.node-sassremoved in favor of external SCSS transpilation.Test plan
npm test(vitest) passesraisely migrateupgrades a v1 repo and produces a correct reportraisely localserves cached CSS on transpiler failure and honors--port/--no-openraisely deployblocks on validation errors and passes with--no-validateraisely listand theraisely mediacommands work against a test campaignNote
High Risk
Major version bump with breaking layout and auth changes, deploy/start gates, and broad command/API behavior changes that can block existing workflows until migration and re-login.
Overview
Raisely CLI 2.0.0 is a breaking release that standardizes on a v2 repo layout (
campaigns/<path>/withmain.scssandpages/), drops v1 read paths, and blocksupdate,deploy,local, andstarton legacy or mixed layouts with guidance to runraisely migrate.Sync and deploy now pull/push page JSON alongside styles and components;
raisely deployruns pre-flight SCSS (remote transpiler) and component validation by default, with--no-validateand--forcefor CI.raisely startvalidates on save. Localnode-sassis removed in favor of the transpiler API.Authentication moves to OAuth PKCE with tokens in the OS keychain (
credentials.js);api.jsresolvesRAISELY_TOKEN→ keychain → legacy.raisely.jsontoken, refreshes on 401, and usesx-raisely-client: cli.New surface area includes
raisely list,raisely media(list/upload/delete),raisely init --uuid,raisely localoptions (--uuid,--port,--no-open,proxyUrl), and per-command telemetry in the CLI wrapper. Tests run via vitest; dependencies are pinned and pnpm-lock.yaml is added.Reviewed by Cursor Bugbot for commit e709be4. Bugbot is set up for automated code reviews on this repo. Configure here.