Skip to content

feat(project): add project share to invite a collaborator#373

Merged
scottlovegrove merged 4 commits into
mainfrom
scottl/project-share
May 29, 2026
Merged

feat(project): add project share to invite a collaborator#373
scottlovegrove merged 4 commits into
mainfrom
scottl/project-share

Conversation

@scottlovegrove

Copy link
Copy Markdown
Collaborator

What

Adds td project share <project-ref> <email> to invite a collaborator to a project — closes the gap alongside the existing project join and project collaborators commands.

Closes #372.

td project share <project-ref> <email> [--role guest|member|admin] [--message <msg>] [--auto-invite] [--json] [--dry-run]

Behaviour

  • Personal / shared projects — shares via the share_project sync command. --role / --auto-invite don't apply and are ignored with a warning.
  • Workspace projects--role guest|member|admin (default member) maps to the workspace role. If the email isn't already a workspace member, the command errors with a hint, unless --auto-invite is passed — which invites them to the workspace first (via inviteWorkspaceUsers) and then shares.
  • --message attaches an optional invitation message.
  • --dry-run previews (including whether an auto-invite would fire) without mutating; --json outputs the result.

Notes

  • Inviting is only available through the Sync API share_project command (there's no REST endpoint), so this adds a shareProject sync wrapper in lib/api/projects-sync.ts.
  • Role vocabulary is the real workspace roles (guest/member/admin), not the viewer/editor in the original issue text.
  • Skill content + SKILL.md updated and in sync.
  • Share tests live in a new project.share.test.ts, and the shared createProjectProgram test helper was extracted for reuse.

Verification

  • npm run type-check
  • npm run check (lint + format) ✅
  • npm run check:skill-sync
  • npm test — full suite passes (1672)

Adds `td project share <project-ref> <email>` to invite a collaborator
to a project, closing the gap alongside `project join` and
`project collaborators`.

- Personal/shared projects: shares via the `share_project` sync command;
  `--role`/`--auto-invite` are ignored with a warning (no roles apply).
- Workspace projects: maps `--role guest|member|admin` (default member)
  to the workspace role. A non-member email errors unless `--auto-invite`
  is passed, which invites them to the workspace first.
- Supports `--message`, `--json`, and `--dry-run`.

Also extracts a shared `createProjectProgram` test helper and splits the
share tests into their own `project.share.test.ts`.
@scottlovegrove scottlovegrove self-assigned this May 29, 2026
@doistbot doistbot requested a review from nats12 May 29, 2026 15:54

@doistbot doistbot 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.

Thanks scottlovegrove for your contribution 🤗. The new project share command is a great addition that cleanly handles the nuances between sharing personal and workspace projects.

Few things worth tightening:

  • Support the standard --project <ref> alias for consistency, and move the workspace member validation above the --dry-run check so dry runs accurately reflect failure states.
  • Expand the SKILL_CONTENT update to cover the newly added --message, --json, and --dry-run flags.
  • Add a test with a mocked hasMore: true response to exercise the workspace member pagination loop.

I also included a few optional follow-up notes in the details below.

Optional follow-up notes (5)
  • [P3] src/commands/project/share.ts:32: Rather than inferring this type using Awaited<ReturnType<typeof getApi>>, you can explicitly type it by importing type { TodoistApi } from @doist/todoist-sdk.
  • [P3] src/commands/project/index.ts:264: --role is introduced here as a free-form string even though this file already uses withCaseInsensitiveChoices(...) for other enum-like options. That forces share.ts to hardcode and revalidate the same role list separately, so help text, shell completions, and runtime validation can drift. Build this option from the shared workspace role list and Commander choice helper instead of validating it ad hoc later.
  • [P3] src/commands/project/share.ts:31: This adds another copy of the getWorkspaceUsers cursor loop that already exists in src/commands/workspace/helpers.ts (findUser / resolveWorkspaceUserRef). Pulling the exact-email membership check into that shared helper would keep workspace-user lookup behavior in one place if the paging contract or matching rules ever change.
  • [P3] src/commands/project/share.ts:40: Per AGENTS.md, API paginated responses should always be destructured. Consider destructuring { workspaceUsers, hasMore, nextCursor } directly from the response object.
  • [P3] src/commands/project/share.ts:79: AGENTS.md requires mutating commands to output JSON using formatJson from src/lib/output.ts. Even though there isn't a strict EntityType for a share result, you can use formatJson({ ... }) instead of JSON.stringify(...) to maintain consistency with the core CLI formatter. (This also applies to the JSON output on line 136).

Share FeedbackReview Logs

Comment thread src/commands/project/share.ts Outdated
Comment thread src/commands/project/index.ts Outdated
Comment thread src/commands/project/project.share.test.ts
Comment thread src/lib/skills/content.ts
- Accept `--project <ref>` as an alias for the positional ref, erroring
  if both are supplied (matches the project-context flag contract).
- Move the workspace-membership check above the `--dry-run` early return
  so a dry run reflects the real success/failure, and simplify the
  auto-invite preview.
- Reuse the shared `findUser` paginator from workspace/helpers instead of
  a local cursor loop.
- Emit JSON via `formatJson` rather than `JSON.stringify`.
- Expand SKILL_CONTENT to cover --project/--message/--json/--dry-run.
- Add tests for paginated member lookup, the --project alias, and the
  positional/flag conflict.
@scottlovegrove

Copy link
Copy Markdown
Collaborator Author

@doistbot /review

@doistbot doistbot 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.

Thanks scottlovegrove for your contribution 😇. The new project share flow looks sound overall, and the test coverage effectively exercises the new workspace role logic and pagination loops.

Few things worth tightening:

  • Restructure the argument parsing so that using the --project flag doesn't cause the first positional argument to be misidentified as the collaborator email.
  • Add a test for --dry-run on workspace projects when the email is not already a member and --auto-invite is omitted to lock in the early validation logic.

I also included a few optional follow-up notes in the details below.

Optional follow-up notes (3)
  • [P3] src/commands/project/test-helpers.ts:1: This file is test-only, but because it lives under src/commands/ and is not a *.test.ts file, tsconfig.build.json will emit it into dist/ and pull @doist/cli-core/testing into the production build. Move it under src/test-support/ or exclude/rename it so test helpers stay out of release artifacts.
  • [P3] src/commands/project/index.ts:266: Consider using withCaseInsensitiveChoices and Option (both already imported in this file) for the --role option. This enables shell autocompletion for the valid choices and delegates validation to Commander, which allows you to remove the manual INVALID_ROLE check in share.ts. typescript .addOption( withCaseInsensitiveChoices( new Option('--role <role>', 'Workspace role: guest, member, or admin (workspace projects only; default: member)'), ['guest', 'member', 'admin'] ) )
  • [P3] src/commands/project/project.share.test.ts:31: Per the test conventions in CODEBASE.md, mock entities should come from src/test-support/fixtures.ts rather than being hand-built inline. This new suite starts constructing project/workspace-user objects directly here and repeats that pattern throughout the file, which makes it easier for test shapes to drift from the shared fixtures. Please switch these mocks to fixture-based objects, adding missing fixture helpers if needed.

Share FeedbackReview Logs

Comment thread src/commands/project/index.ts Outdated
Comment thread src/commands/project/project.share.test.ts
Addresses second review round:

- Rework share arg parsing into two clear modes: `--project` supplies the
  project and the lone positional is the email; the two-positional form is
  project-then-email. Throw CONFLICTING_OPTIONS if the project is given
  both ways, and MISSING_EMAIL when no email is supplied.
- Define `--role` with `withCaseInsensitiveChoices(['guest','member','admin'])`
  so Commander validates and shells can complete it; drop the ad-hoc
  INVALID_ROLE check in share.ts.
- Move the project test-program helper to src/test-support/ so it is not
  emitted into dist/ (and no longer drags @doist/cli-core/testing into the
  production build).
- Add tests for dry-run-not-member validation and case-insensitive --role.
The backend rejects shares that exceed plan limits or permissions via the
Sync API's per-command status; the SDK throws but drops the machine
error_tag, leaving only the HTTP status and human message. Map those
messages in share.ts to CliErrors with accurate codes and hints
(collaborator limit, workspace guest/member limits, already a
member/collaborator, unverified account, frozen project, external-invite
disabled). Unrecognized errors pass through unchanged.

Adds the sharing error codes to the ErrorCode union and tests for the
collaborator-limit, workspace-guest-limit, and pass-through cases.
@scottlovegrove scottlovegrove added the 👀 Show PR PR must be reviewed before or after merging label May 29, 2026
@scottlovegrove scottlovegrove merged commit 514d0fd into main May 29, 2026
5 checks passed
@scottlovegrove scottlovegrove deleted the scottl/project-share branch May 29, 2026 17:18
doist-release-bot Bot added a commit that referenced this pull request May 29, 2026
## [1.70.0](v1.69.3...v1.70.0) (2026-05-29)

### Features

* **project:** add `project share` to invite a collaborator ([#373](#373)) ([514d0fd](514d0fd))
@doist-release-bot

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 1.70.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

released 👀 Show PR PR must be reviewed before or after merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: td project share — add command to invite a collaborator to a project

2 participants