Skip to content

feat(organizations): add commands for listing, creating, updating, and deleting organizations#762

Open
rexxars wants to merge 18 commits into
mainfrom
feat/org-commands
Open

feat(organizations): add commands for listing, creating, updating, and deleting organizations#762
rexxars wants to merge 18 commits into
mainfrom
feat/org-commands

Conversation

@rexxars

@rexxars rexxars commented Mar 23, 2026

Copy link
Copy Markdown
Member

Description

Adds a new organizations topic to the CLI with five commands for managing organizations:

  • sanity organizations list - list all organizations you're a member of
  • sanity organizations get <organizationId> - show details of a specific organization
  • sanity organizations create - create a new organization (interactive or --name/--default-role flags)
  • sanity organizations update <organizationId> - update name, slug, or default role
  • sanity organizations delete <organizationId> - delete an organization (with confirmation prompt)

Hidden topic aliases so all of these also work: organization, organisations, organisation, org, orgs. Uses the two-layer alias approach from #714 (hiddenAliases on each command class + topicAliases.ts entry).

What to review

  • src/services/organizations.ts - extended with getOrganization, updateOrganization, deleteOrganization; createOrganization now accepts optional defaultRoleName; fixed ProjectOrganization.slug to be string | null
  • src/commands/organizations/ - five command files, all thin: parse flags, call service, format output
  • src/util/apiError.ts - shared hasStatusCode type guard to avoid as assertions in catch blocks
  • src/topicAliases.ts and oclif.config.js - alias wiring for the organizations topic
  • src/commands/organizations/__tests__/ - 23 tests across all five commands

Testing

  • 23 unit tests covering all five commands (success, error, edge cases)
  • Tests use module-level client mocking (getGlobalCliClient), no service file mocks
  • Success tests use if (error) throw error; error tests assert toBeInstanceOf(Error) + message
  • All tests pass locally

Notes

  • --slug flag only on update, not create - the API requires the authSAML feature for slugs, which a brand-new org won't have
  • delete blocks server-side if the org has associated projects - the command surfaces that API error clearly
  • Members management commands (organizations members list/invite) are deferred pending internal discussion

FIXES AIGRO-4968


Note

Medium Risk
Delete and update call destructive or org-wide APIs with only CLI-side guards; mistakes or --force could remove organizations, though patterns match existing CLI topics.

Overview
Adds a new organizations CLI topic (minor @sanity/cli changeset) with list, get, create, update, and delete commands wired through extended organizations service helpers (getOrganization, updateOrganization, deleteOrganization, optional defaultRoleName on create).

Create supports --name / --default-role or interactive naming; update patches name, slug, or default role with client-side slug rules via new validateOrganizationSlug; delete fetches the org and requires typing its name unless --force. Commands register hiddenAliases plus topicAliases / oclif.config.js entries so org, organization, etc. resolve to the same actions.

promptForOrganizationName now works without a user argument and trims input; ProjectOrganization.slug is typed as nullable. Broad command and validator tests cover success paths, validation, 404s, and delete confirmation behavior.

Reviewed by Cursor Bugbot for commit fe06012. Bugbot is set up for automated code reviews on this repo. Configure here.

@claude

claude Bot commented Mar 23, 2026

Copy link
Copy Markdown

Claude finished @mttdnt's task in 1m 50s —— View job


Review

  • Gather context
  • Read changed files
  • Identify issues
  • Post review

1. validateOrganizationSlug accepts invalid characters

src/actions/organizations/validateOrganizationSlug.ts:1-12 — The validator only checks for empty, uppercase, and spaces. It allows characters that are invalid in URL slugs (e.g. acme!, org_name, org.name). Slugs should be constrained to [a-z0-9-].

if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(input) && !/^[a-z0-9]$/.test(input)) {
  return 'Organization slug may only contain lowercase letters, numbers, and hyphens'
}

Fix this →


2. Misleading "User cancelled" error in delete.ts

src/commands/organizations/delete.ts:103-107 — The catch block around input() labels any thrown error as "User cancelled", but input() could throw for other reasons. The error message should pass through the actual error text rather than hardcoding "User cancelled".

} catch (error) {
  deleteOrgDebug(`User cancelled or input error`, error)
  this.error(getErrorMessage(error), {exit: 1})
}

3. Falsy check in createOrganization inconsistent with !== undefined pattern

src/services/organizations.ts:83defaultRoleName ? {defaultRoleName} : {} uses truthy check, so an empty string would be omitted. The rest of the codebase (command layer) uses !== undefined. Should be:

body: {name, ...(defaultRoleName !== undefined ? {defaultRoleName} : {})},

4. getOrganization passes string 'false' for boolean query params

src/services/organizations.ts:115-116{includeFeatures: 'false', includeMembers: 'false'} uses strings instead of booleans. If the API is type-sensitive this could return unexpected data. Use false (boolean) instead.

@github-actions

github-actions Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/commands/organizations/create.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/delete.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/get.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/list.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/update.ts 100.0% (new)
packages/@sanity/cli/src/services/organizations.ts 100.0% (±0%)
packages/@sanity/cli/src/topicAliases.ts 100.0% (±0%)
packages/@sanity/cli/src/util/apiError.ts 100.0% (new)

Comparing 8 changed files against main @ 133be82f2a246f5cc1573c8e2733d50a2b2ea746

Overall Coverage

Metric Coverage
Statements 83.2% (+ 0.2%)
Branches 72.8% (+ 0.1%)
Functions 83.3% (+ 0.1%)
Lines 83.7% (+ 0.2%)

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @sanity/cli

Compared against main (8985b42f)

@sanity/cli

Metric Value vs main (8985b42)
Internal (raw) 2.7 KB +539 B, +24.7%
Internal (gzip) 1.0 KB +257 B, +32.2%
Bundled (raw) 11.15 MB +19.2 KB, +0.2%
Bundled (gzip) 2.10 MB +5.2 KB, +0.2%
Import time 874ms -1ms, -0.1%

bin:sanity

Metric Value vs main (8985b42)
Internal (raw) 782 B -
Internal (gzip) 423 B -
Bundled (raw) 9.87 MB -
Bundled (gzip) 1.77 MB -
Import time 1.98s +1.05s, +112.8% ⚠️

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @sanity/cli-core

Compared against main (8985b42f)

Metric Value vs main (8985b42)
Internal (raw) 106.7 KB +8.5 KB, +8.7%
Internal (gzip) 26.7 KB +3.4 KB, +14.5%
Bundled (raw) 21.71 MB +7.8 KB, +0.0%
Bundled (gzip) 3.46 MB +3.3 KB, +0.1%
Import time 771ms +1ms, +0.2%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — create-sanity

Compared against main (8985b42f)

Metric Value vs main (8985b42)
Internal (raw) 908 B -
Internal (gzip) 483 B -
Bundled (raw) 931 B -
Bundled (gzip) 491 B -
Import time ❌ ChildProcess denied: node -
Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/organizations/validateOrganizationSlug.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/create.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/delete.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/get.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/list.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/update.ts 100.0% (new)
packages/@sanity/cli/src/prompts/promptForOrganizationName.ts 100.0% (±0%)
packages/@sanity/cli/src/services/organizations.ts 100.0% (±0%)
packages/@sanity/cli/src/topicAliases.ts 100.0% (±0%)
packages/@sanity/cli/src/util/organizationAliases.ts 100.0% (new)

Comparing 10 changed files against main @ b02ed4421c46d7d08853e5e89c8452bba6ddced9

Overall Coverage

Metric Coverage
Statements 88.0% (+ 0.2%)
Branches 77.8% (+ 0.2%)
Functions 87.4% (+ 0.1%)
Lines 88.4% (+ 0.2%)

@mttdnt mttdnt marked this pull request as ready for review April 2, 2026 14:47
@mttdnt mttdnt requested a review from a team as a code owner April 2, 2026 14:47
@mttdnt mttdnt requested review from gu-stav and removed request for a team April 2, 2026 14:47
Comment thread packages/@sanity/cli/src/commands/organizations/list.ts
@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Coverage Delta

File Statements
packages/@sanity/cli/src/actions/organizations/validateOrganizationSlug.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/create.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/delete.ts 94.7% (new)
packages/@sanity/cli/src/commands/organizations/get.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/list.ts 100.0% (new)
packages/@sanity/cli/src/commands/organizations/update.ts 100.0% (new)
packages/@sanity/cli/src/prompts/promptForOrganizationName.ts 100.0% (±0%)
packages/@sanity/cli/src/services/organizations.ts 100.0% (±0%)
packages/@sanity/cli/src/topicAliases.ts 100.0% (±0%)
packages/@sanity/cli/src/util/apiError.ts 100.0% (new)
packages/@sanity/cli/src/util/organizationAliases.ts 100.0% (new)

Comparing 11 changed files against main @ ebc4e56198cd2071556e8687ec5f416ee2557873

Overall Coverage

Metric Coverage
Statements 83.3% (+ 0.2%)
Branches 73.0% (+ 0.1%)
Functions 83.4% (+ 0.1%)
Lines 83.7% (+ 0.2%)

@mttdnt mttdnt requested a review from binoy14 April 2, 2026 15:00
squiggler-app Bot added a commit that referenced this pull request May 14, 2026
@squiggler-app squiggler-app Bot requested a review from a team as a code owner May 14, 2026 16:59
Comment thread packages/@sanity/cli/src/util/apiError.ts Outdated

@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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 24915ab. Configure here.

Comment thread packages/@sanity/cli/src/commands/organizations/delete.ts
squiggler-app Bot added a commit that referenced this pull request Jun 12, 2026
rexxars and others added 10 commits June 12, 2026 13:55
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…pe guard

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds `organizations update` command with --name, --slug, and --default-role flags. Includes tests for success, validation (no flags), missing arg, and API error cases.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Remove export from Organization and OrganizationDeleteResponse interfaces
as they are only used internally within the service module.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
rexxars and others added 7 commits June 12, 2026 13:55
Wire up topic aliases (organization, organisations, organisation, org, orgs)
in topicAliases.ts and oclif.config.js. Fix linter-reported import and
property ordering in org command files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Trim organization names on create, update, prompt, and delete confirmation
  compare, so padded names round-trip consistently
- Validate slug charset (lowercase letters, numbers, dashes; no leading or
  trailing dash) before hitting the API
- Reject empty --default-role on both create and update
- Narrow hasStatusCode guard to what it actually verifies, drop the 'as' cast
- Assert request payloads in create/update tests; fix vacuous dash assertion
  in list test; cover trimmed delete confirmation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rexxars rexxars changed the title feat(organizations): add CRUD commands for organizations feat(organizations): add commands for listing, creating, updating, and deleting organizations Jun 12, 2026
@rexxars rexxars force-pushed the feat/org-commands branch from 4bda978 to e155564 Compare June 12, 2026 20:57
@joneidejohnsen joneidejohnsen requested a review from runeb June 18, 2026 16:03

@runeb runeb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lets make sure create returns an actionable error if ran without the required params in non-interactive mode. Currently I think it will just halt with a prompt.

Instead of introducing hasStatusCode can we leverage the existing isHttpError from @sanity/client already in use in the project?

It would also be great if list/get supports --json like other commands do, but that is a nicety. I dont think all commands support this generally yet.

@rexxars rexxars requested a review from runeb June 22, 2026 21:41
@rexxars

rexxars commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

Addressed your PR feedback. Yes, --json would be nice but we need a unified strategy before we start integrating into each command, IMHO. Eg is there a root shape, or does every command decide their own format? So will leave that for a future iteration.

Also heading on vacation for a bit, so handing this off to someone else until I'm back 👋

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.

3 participants