Skip to content

Release v2#76

Open
xolott-ark wants to merge 78 commits into
masterfrom
develop
Open

Release v2#76
xolott-ark wants to merge 78 commits into
masterfrom
develop

Conversation

@xolott-ark

@xolott-ark xolott-ark commented Jul 6, 2026

Copy link
Copy Markdown

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

  • Per-campaign content now lives in campaigns/<campaign-path>/, the entry SCSS file is now main.scss, and shared components stay at the repo root.
  • All readers and writers switched to the v2 layout; v1 compatibility read paths are removed.
  • Legacy-layout refusal gate on update, deploy, and local so v2 commands fail clearly instead of misreading old repos.

Migration

  • New raisely migrate command upgrades existing v1 repositories to the v2 layout, with a report that correctly classifies moved-vs-renamed files.

Auth

  • OAuth 2.0 login flow with PKCE replaces the previous login/logout path.
  • Credentials stored via the OS keyring (@napi-rs/keyring).
  • OAuth scopes updated to include pages:update.

Validation

  • Pre-flight validation gate on raisely deploy (opt out with --no-validate), with bounded concurrency and hardened error handling.
  • Per-save validation for raisely start.
  • Shared SCSS and component validators; SASS transpilation moved to an external API (drops the node-sass dependency).

raisely local resilience

  • Serves the last successful CSS when compilation fails.
  • Removes the 5-second retry loop and the process exit on transpiler 401 responses.
  • Adds --port to override the default port and --no-open to skip opening the browser.
  • Supports opening a specific campaign by UUID.

New commands

  • raisely list — list all campaigns (--json / --tsv output).
  • raisely media list, raisely media upload, raisely media delete — manage campaign media.
  • raisely init --uuid <uuid> — initialize a campaign directly by UUID.

Telemetry

  • Command telemetry events with request timeouts, flush-safe delivery, and descriptive payload fields.

Tooling and tests

  • Migrated the test suite from node:test to vitest and added coverage across campaigns, deploy, layout, local, migrate, start, telemetry, and validate.
  • Swapped yarn.lock for package-lock.json + pnpm-lock.yaml; pinned Node 18-compatible dependency versions.

Breaking changes

  • Repositories must use the v2 layout. Run raisely migrate on existing v1 repos before using v2 commands.
  • v1 read paths are gone; v2 commands refuse legacy layouts.
  • node-sass removed in favor of external SCSS transpilation.

Test plan

  • npm test (vitest) passes
  • raisely migrate upgrades a v1 repo and produces a correct report
  • raisely local serves cached CSS on transpiler failure and honors --port / --no-open
  • raisely deploy blocks on validation errors and passes with --no-validate
  • OAuth login/logout round-trips and credentials persist in the keyring
  • raisely list and the raisely media commands work against a test campaign

Note

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>/ with main.scss and pages/), drops v1 read paths, and blocks update, deploy, local, and start on legacy or mixed layouts with guidance to run raisely migrate.

Sync and deploy now pull/push page JSON alongside styles and components; raisely deploy runs pre-flight SCSS (remote transpiler) and component validation by default, with --no-validate and --force for CI. raisely start validates on save. Local node-sass is removed in favor of the transpiler API.

Authentication moves to OAuth PKCE with tokens in the OS keychain (credentials.js); api.js resolves RAISELY_TOKEN → keychain → legacy .raisely.json token, refreshes on 401, and uses x-raisely-client: cli.

New surface area includes raisely list, raisely media (list/upload/delete), raisely init --uuid, raisely local options (--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.

xolott-ark and others added 30 commits April 17, 2026 13:20
- 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>
KeinerM and others added 23 commits May 8, 2026 09:23
…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`
@xolott-ark xolott-ark self-assigned this Jul 6, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Open in Web View Automation 

Sent by Cursor Approval Agent: Nemo - Pull Request Router and Approver

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Fix All in Cursor

❌ 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.

Comment thread src/deploy.js
continue;
}
pageTasks.push(limit(() => uploadPage(pageData)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e709be4. Configure here.

Comment thread src/actions/sync.js

const pages = await api({
path: `/campaigns/${uuid}/pages?private=1&includeBody=1&limit=999`,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e709be4. Configure here.

Comment thread src/local.js
let nextStylesRequestId = 0;
let lastAppliedStylesRequestId = 0;

const transpileEndpoint = `${transpilerUrl.replace(/\/$/, '')}/transpile`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit e709be4. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants