@formigio/fazemos-cli 0.10.16 → 0.10.17

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/dist/index.js CHANGED
@@ -5539,14 +5539,57 @@ executions
5539
5539
  // ── My Work ─────────────────────────────────────────────────
5540
5540
  program
5541
5541
  .command('my-work')
5542
- .description('Show pending work in the active project (or all projects with --all-projects)')
5542
+ .description('Show pending work in the active project (or all projects with --all-projects, or across every org with --all-orgs)')
5543
5543
  .option('--project <slug>', 'Override active project for this call')
5544
5544
  .option('--all-projects', 'Show pending work across every project in the active org', false)
5545
+ .option('-a, --all-orgs', 'Show pending work across every org you are a member of (calls /api/my-work/unified)', false)
5546
+ .option('--json', 'Emit machine-readable JSON', false)
5545
5547
  .action(async (opts) => {
5548
+ // ── F19 — cross-Org unified path ───────────────────────────
5549
+ // When --all-orgs is set, we drop X-Org-Id / X-Fazemos-Project-Id
5550
+ // headers and call the unified endpoint. The endpoint is mounted
5551
+ // BEFORE requireOrgMembership and runs across every Org the caller is
5552
+ // an active member of. See rul_cross_org_auth_no_org_middleware.
5553
+ if (opts.allOrgs) {
5554
+ try {
5555
+ const data = await api('GET', '/api/my-work/unified', undefined, { noProjectHeader: true });
5556
+ if (opts.json) {
5557
+ console.log(JSON.stringify(data, null, 2));
5558
+ return;
5559
+ }
5560
+ // Sage edge case #24: single-Org user with --all-orgs gets a soft
5561
+ // notice + their normal output (no flag-stripping, no error).
5562
+ if (data.meta.orgCount === 1) {
5563
+ console.log(chalk.yellow("--all-orgs has no effect — you're a member of 1 org. Showing your current scope.\n"));
5564
+ }
5565
+ renderUnifiedMyWork(data);
5566
+ return;
5567
+ }
5568
+ catch (err) {
5569
+ // Defensive: 403 NO_ACTIVE_MEMBERSHIP gets a friendly nudge.
5570
+ if (err instanceof ApiError && err.code === 'NO_ACTIVE_MEMBERSHIP') {
5571
+ console.error(chalk.red('No active org memberships.'));
5572
+ console.error(chalk.gray('Ask an admin to add you, or accept a pending invite.'));
5573
+ process.exit(1);
5574
+ }
5575
+ if (err instanceof Error) {
5576
+ console.error(chalk.red(err.message));
5577
+ }
5578
+ else {
5579
+ console.error(chalk.red(String(err)));
5580
+ }
5581
+ process.exit(1);
5582
+ }
5583
+ }
5584
+ // ── Default project-scoped path (unchanged) ─────────────────
5546
5585
  try {
5547
5586
  const data = await api('GET', '/api/my-work', undefined, projectOpts(opts));
5548
5587
  const c = data.commitments;
5549
5588
  const total = c.overdue.length + c.due_today.length + c.due_this_week.length + c.upcoming.length;
5589
+ if (opts.json) {
5590
+ console.log(JSON.stringify(data, null, 2));
5591
+ return;
5592
+ }
5550
5593
  console.log(chalk.cyan(`Commitments: ${total}`));
5551
5594
  if (c.overdue.length)
5552
5595
  console.log(chalk.red(` Overdue: ${c.overdue.length}`));
@@ -5564,6 +5607,81 @@ program
5564
5607
  handleScopedError(err);
5565
5608
  }
5566
5609
  });
5610
+ function renderUnifiedMyWork(data) {
5611
+ if (data.items.length === 0) {
5612
+ console.log(chalk.green("Looking good — across every project, in every org, you're caught up."));
5613
+ return;
5614
+ }
5615
+ const today = new Date().toISOString().slice(0, 10);
5616
+ const truncate = (s, n) => s.length > n ? s.slice(0, n - 1) + '…' : s;
5617
+ // Header
5618
+ const header = [
5619
+ pad('STATUS', 10),
5620
+ pad('TYPE', 9),
5621
+ pad('TITLE', 36),
5622
+ pad('ORG', 5),
5623
+ pad('PROJ', 5),
5624
+ 'DUE',
5625
+ ].join(' ');
5626
+ console.log(chalk.gray(header));
5627
+ console.log(chalk.gray('-'.repeat(80)));
5628
+ for (const item of data.items) {
5629
+ // Status color rules per manifest.cli.changes.
5630
+ let statusLabel;
5631
+ if (item.dueDate && item.dueDate < today) {
5632
+ statusLabel = chalk.red('overdue');
5633
+ }
5634
+ else if (item.dueDate === today) {
5635
+ statusLabel = chalk.yellow('today');
5636
+ }
5637
+ else {
5638
+ statusLabel = item.status;
5639
+ }
5640
+ const row = [
5641
+ pad(statusLabel, 10),
5642
+ pad(item.type, 9),
5643
+ pad(truncate(item.title, 36), 36),
5644
+ pad(`[${computeAbbreviation(item.org.name)}]`, 5),
5645
+ pad(`[${computeAbbreviation(item.project.name)}]`, 5),
5646
+ item.dueDate ?? '—',
5647
+ ].join(' ');
5648
+ console.log(row);
5649
+ }
5650
+ // Footer
5651
+ const shown = data.items.length;
5652
+ const total = data.meta.typeCounts.commitment +
5653
+ data.meta.typeCounts.action +
5654
+ data.meta.typeCounts.worksheet +
5655
+ data.meta.typeCounts.check_in +
5656
+ data.meta.typeCounts.pipeline_step;
5657
+ console.log('');
5658
+ console.log(chalk.gray(`Showing ${shown} of ${total}.` +
5659
+ (data.nextCursor ? ' More available — re-run with --cursor=<next>.' : '')));
5660
+ console.log(chalk.gray(`Tip: fazemos my-work (no flag) shows just the active project.`));
5661
+ }
5662
+ /**
5663
+ * 2-letter abbreviation: first letter of first word + first letter of next
5664
+ * word, or first 2 letters of the only word if no second word exists.
5665
+ * Duplicates the helper used in fazemos-web (ProjectBadge.tsx) per
5666
+ * manifest.cli.shared_helpers — "don't block on packaging."
5667
+ */
5668
+ function computeAbbreviation(name) {
5669
+ const cleaned = name.trim();
5670
+ if (!cleaned)
5671
+ return '??';
5672
+ const words = cleaned.split(/\s+/);
5673
+ if (words.length === 1) {
5674
+ return words[0].slice(0, 2).toUpperCase();
5675
+ }
5676
+ return (words[0][0] + words[1][0]).toUpperCase();
5677
+ }
5678
+ function pad(s, width) {
5679
+ // Strip ANSI escape codes for length measurement so colored cells line up.
5680
+ const visible = s.replace(/\u001b\[[0-9;]*m/g, '');
5681
+ if (visible.length >= width)
5682
+ return s;
5683
+ return s + ' '.repeat(width - visible.length);
5684
+ }
5567
5685
  // ── Test ────────────────────────────────────────────────────
5568
5686
  program
5569
5687
  .command('test')