@sdods/cli 0.2.0 → 0.2.1

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.
@@ -169,41 +169,11 @@ function printReport(r) {
169
169
  out(`${icon} ${pc.bold(c.title)} — ${c.detail}${c.fix ? pc.dim(`\n → ${c.fix}`) : ''}`);
170
170
  }
171
171
  }
172
- /**
173
- * Why each module exists. Modules are inferred by fusing several signals - the file that defines
174
- * a route, the monorepo workspace, a domain directory, an OpenAPI tag - so the winning signal and
175
- * its confidence are the part a reviewer actually needs to judge the proposal.
176
- */
177
- function printModules(p) {
178
- const mods = p.detectedModules ?? [];
179
- if (!mods.length)
180
- return;
181
- out(pc.dim('--- modules ---'));
182
- const w = Math.max(6, ...mods.map((m) => m.name.length));
183
- for (const m of mods) {
184
- const targets = [
185
- m.routes ? `${m.routes} route${m.routes === 1 ? '' : 's'}` : '',
186
- m.endpoints ? `${m.endpoints} endpoint${m.endpoints === 1 ? '' : 's'}` : '',
187
- ]
188
- .filter(Boolean)
189
- .join(', ');
190
- const from = m.evidence[0]?.file;
191
- out(` ${m.name.padEnd(w)} ${pc.dim(m.confidence.toFixed(2))} ${targets.padEnd(22)}` +
192
- `${pc.dim(m.signals.join('+'))}${from ? pc.dim(` ← ${from}`) : ''}`);
193
- }
194
- const ignored = p.ignoredTargets ?? [];
195
- if (ignored.length) {
196
- out(pc.dim(`--- skipped (${ignored.length}) ---`));
197
- for (const i of ignored)
198
- out(pc.dim(` ${i.target.padEnd(20)} ${i.reason}`));
199
- }
200
- }
201
172
  function printProposal(p) {
202
173
  heading(`Proposed project: ${p.slug}`);
203
174
  out(pc.dim(`envs: ${Object.keys(p.envYamls).join(', ')} · starter features: ${Object.keys(p.starterFeatures).length} · coverage targets: ${p.coverageMap.length}`));
204
175
  for (const n of p.notes)
205
176
  warn(n);
206
- printModules(p);
207
177
  out(pc.dim('--- sdods.project.yaml ---'));
208
178
  const lines = p.projectYaml.split('\n');
209
179
  out(lines.slice(0, 60).join('\n'));
@@ -76,7 +76,7 @@ export function register(program) {
76
76
  });
77
77
  browsers
78
78
  .command('list')
79
- .description('Show which browsers are installed and where')
79
+ .description('Show which browsers are installed and where; exits 1 if any is missing')
80
80
  .option('-p, --project <slug>', 'limit to the browsers declared by a project')
81
81
  .action(async (opts, cmd) => {
82
82
  const ctx = createContext(cmd);
@@ -4,7 +4,7 @@ import { heading, json, out, table } from '../ui.js';
4
4
  export function register(program) {
5
5
  program
6
6
  .command('coverage')
7
- .description('Route, endpoint and role coverage by scenarios, split by suite tag')
7
+ .description('Route, endpoint and role coverage by scenarios, split by suite tag; exits 1 if any module has no scenarios')
8
8
  .requiredOption('-p, --project <slug>', 'project slug')
9
9
  .option('-e, --env <name>', 'environment (for the OpenAPI spec and API base URL)')
10
10
  .option('--openapi [file]', 'include endpoints from the OpenAPI spec (env api.openapi or a file)')
@@ -86,21 +86,28 @@ export function register(program) {
86
86
  tokens
87
87
  .command('revoke <id>')
88
88
  .description('Revoke a token by id')
89
- .action(async (id, cmd) => {
89
+ .action(async (id, _opts, cmd) => {
90
90
  const ctx = createContext(cmd);
91
91
  const db = await import('@sdods/db');
92
92
  const adb = await db.openDb();
93
93
  try {
94
- await db.revokeApiToken(adb.db, id);
95
- await db.audit(adb.db, {
96
- actorType: 'cli',
97
- action: 'token.revoke',
98
- targetType: 'api_token',
99
- targetId: id,
100
- });
94
+ // Reporting success for an id that does not exist reads as "the token is gone" when
95
+ // nothing was checked at all — an operator revoking a leaked token needs the difference.
96
+ const revoked = await db.revokeApiToken(adb.db, id);
97
+ if (revoked) {
98
+ await db.audit(adb.db, {
99
+ actorType: 'cli',
100
+ action: 'token.revoke',
101
+ targetType: 'api_token',
102
+ targetId: id,
103
+ });
104
+ }
101
105
  if (ctx.opts.json)
102
- return json({ id, revoked: true });
103
- ok(`Revoked ${id}`);
106
+ return json({ id, revoked });
107
+ if (revoked)
108
+ ok(`Revoked ${id}`);
109
+ else
110
+ warn(`No live token with id ${id}; nothing to revoke.`);
104
111
  }
105
112
  finally {
106
113
  await adb.close();
@@ -88,7 +88,7 @@ export function register(program) {
88
88
  users
89
89
  .command('set-role <username> <role>')
90
90
  .description('Change a platform role (viewer | editor | admin)')
91
- .action(async (username, role, cmd) => {
91
+ .action(async (username, role, _opts, cmd) => {
92
92
  const ctx = createContext(cmd);
93
93
  const adb = await openDb();
94
94
  try {
@@ -112,7 +112,7 @@ export function register(program) {
112
112
  users
113
113
  .command('deactivate <username>')
114
114
  .description('Deactivate a user (sessions and tokens stop working)')
115
- .action(async (username, cmd) => {
115
+ .action(async (username, _opts, cmd) => {
116
116
  const ctx = createContext(cmd);
117
117
  const adb = await openDb();
118
118
  try {
package/dist/context.js CHANGED
@@ -1,5 +1,12 @@
1
1
  import { ProjectRegistry, setLogJson, setLogLevel } from '@sdods/core';
2
2
  export function globalOptions(cmd) {
3
+ // Commander calls an action with (...positionalArgs, options, command). A callback that forgets
4
+ // the options parameter receives the options object here instead of the Command, and the failure
5
+ // surfaced as an unhelpful "root.opts is not a function".
6
+ if (typeof cmd?.opts !== 'function' || !('parent' in cmd)) {
7
+ throw new Error('createContext expected the Commander Command. An action callback takes ' +
8
+ '(...args, options, command) — the options parameter is probably missing.');
9
+ }
3
10
  let root = cmd;
4
11
  while (root.parent)
5
12
  root = root.parent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdods/cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "SDODS command line: run, record, analyze, agents, MCP server, scheduler and the web server.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "SDODS <admin@sdods.com>",
@@ -23,8 +23,8 @@
23
23
  "node": ">=22"
24
24
  },
25
25
  "dependencies": {
26
- "@sdods/contracts": "0.2.0",
27
- "@sdods/core": "0.2.0",
26
+ "@sdods/contracts": "0.2.1",
27
+ "@sdods/core": "0.2.1",
28
28
  "commander": "^15.0.0",
29
29
  "execa": "^10.0.1",
30
30
  "picocolors": "^1.1.1",
@@ -33,12 +33,12 @@
33
33
  "zod": "^4.5.4",
34
34
  "@playwright/test": "^1.62.1",
35
35
  "playwright-core": "^1.62.1",
36
- "@sdods/integrations": "0.2.0",
37
- "@sdods/mcp": "0.2.0",
38
- "@sdods/agents": "0.2.0",
39
- "@sdods/db": "0.2.0",
36
+ "@sdods/integrations": "0.2.1",
37
+ "@sdods/mcp": "0.2.1",
38
+ "@sdods/agents": "0.2.1",
39
+ "@sdods/db": "0.2.1",
40
40
  "csv-parse": "^7.0.2",
41
- "@sdods/server": "0.2.0"
41
+ "@sdods/server": "0.2.1"
42
42
  },
43
43
  "homepage": "https://sdods.com",
44
44
  "bugs": {