@myapihq/cli 2.25.0 → 2.25.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.
|
@@ -786,6 +786,7 @@ Subcommands:
|
|
|
786
786
|
domain <id> <domain> Bind a custom domain (--remove to unbind)
|
|
787
787
|
env <id> Change environment variables (merge; rolls a new revision)
|
|
788
788
|
get <id> Inspect a container
|
|
789
|
+
health-check <id> Set or clear the startup probe (--clear); rolls a new revision
|
|
789
790
|
list List containers in your org
|
|
790
791
|
logs <id> Show recent runtime logs (--tail <n>, --scope all) — see build-logs for build failures
|
|
791
792
|
promote <id> <rev> Move all traffic to a revision (seconds, no rebuild)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// A verb you cannot find is a verb you do not have.
|
|
2
|
+
//
|
|
3
|
+
// `container health-check` shipped dispatched, documented in SUBCOMMAND_USAGE,
|
|
4
|
+
// covered by tests and named in the skill — and absent from the list that
|
|
5
|
+
// `myapi container --help` prints. The only way to discover it was to already
|
|
6
|
+
// know it existed. It was found by running the published binary after release,
|
|
7
|
+
// which is not a mechanism.
|
|
8
|
+
//
|
|
9
|
+
// The existing help linter checks that listings are ALPHABETICAL. Nothing
|
|
10
|
+
// checked that they are COMPLETE, so a new verb could be sorted correctly into
|
|
11
|
+
// a list it was not in.
|
|
12
|
+
//
|
|
13
|
+
// This drives each command's own help output — the exact string the user sees,
|
|
14
|
+
// not a listing block matched by a regex, because two earlier attempts at this
|
|
15
|
+
// measured the wrong thing: one required a `Subcommands:` header that `task`
|
|
16
|
+
// does not use, and one accepted an entry in SUBCOMMAND_USAGE, which is what
|
|
17
|
+
// health-check already had while remaining invisible.
|
|
18
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
19
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
22
|
+
const COMMANDS_DIR = fileURLToPath(new URL('.', import.meta.url));
|
|
23
|
+
// Verbs deliberately absent from the printed list, each with a reason.
|
|
24
|
+
// A hidden verb is a decision; an accidentally hidden one is the bug above.
|
|
25
|
+
const HIDDEN_OK = {
|
|
26
|
+
'database.ts': {
|
|
27
|
+
keys: 'undocumented alias of `entries`, kept so existing scripts keep working; ' +
|
|
28
|
+
'the code says it goes no earlier than the next minor',
|
|
29
|
+
},
|
|
30
|
+
'org.ts': {
|
|
31
|
+
import: 'deprecated alias of `sync-brand`; still dispatches and prints a ' +
|
|
32
|
+
'deprecation notice, deliberately absent from the listing',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
function moduleFiles() {
|
|
36
|
+
const out = [];
|
|
37
|
+
for (const entry of readdirSync(COMMANDS_DIR).sort()) {
|
|
38
|
+
const p = path.join(COMMANDS_DIR, entry);
|
|
39
|
+
if (statSync(p).isDirectory()) {
|
|
40
|
+
const idx = path.join(p, 'index.ts');
|
|
41
|
+
try {
|
|
42
|
+
statSync(idx);
|
|
43
|
+
out.push(`${entry}/index.ts`);
|
|
44
|
+
}
|
|
45
|
+
catch { /* no dispatcher */ }
|
|
46
|
+
}
|
|
47
|
+
else if (entry.endsWith('.ts') && !entry.includes('.test.')) {
|
|
48
|
+
out.push(entry);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
// Only the TOP-LEVEL dispatcher's cases. A nested switch — `account registrant
|
|
54
|
+
// set`, `domain records create`, `llm cache list` — belongs to a sub-verb that
|
|
55
|
+
// the parent line documents inline, and counting those made the check report
|
|
56
|
+
// three commands that are fine.
|
|
57
|
+
function dispatchedVerbs(src) {
|
|
58
|
+
const start = src.indexOf('export async function run(');
|
|
59
|
+
if (start === -1)
|
|
60
|
+
return [];
|
|
61
|
+
let i = src.indexOf('{', start);
|
|
62
|
+
if (i === -1)
|
|
63
|
+
return [];
|
|
64
|
+
let depth = 0, end = src.length;
|
|
65
|
+
for (let j = i; j < src.length; j++) {
|
|
66
|
+
if (src[j] === '{')
|
|
67
|
+
depth++;
|
|
68
|
+
else if (src[j] === '}') {
|
|
69
|
+
depth--;
|
|
70
|
+
if (depth === 0) {
|
|
71
|
+
end = j;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const body = src.slice(i, end);
|
|
77
|
+
return [...new Set([...body.matchAll(/case\s+'([a-z][a-z0-9-]*)'\s*:/g)].map(m => m[1]))];
|
|
78
|
+
}
|
|
79
|
+
let printed;
|
|
80
|
+
beforeEach(async () => {
|
|
81
|
+
printed = [];
|
|
82
|
+
const output = await import('../output.js');
|
|
83
|
+
const capture = ((m) => { printed.push(String(m ?? '')); });
|
|
84
|
+
vi.spyOn(output, 'info').mockImplementation(capture);
|
|
85
|
+
vi.spyOn(output, 'success').mockImplementation(capture);
|
|
86
|
+
vi.spyOn(output, 'printJson').mockImplementation(capture);
|
|
87
|
+
vi.spyOn(output, 'error').mockImplementation(((m) => {
|
|
88
|
+
printed.push(String(m));
|
|
89
|
+
throw new Error('__EXIT__');
|
|
90
|
+
}));
|
|
91
|
+
});
|
|
92
|
+
afterEach(() => vi.restoreAllMocks());
|
|
93
|
+
async function helpTextOf(file) {
|
|
94
|
+
const mod = await import(`./${file}`);
|
|
95
|
+
if (typeof mod.run !== 'function')
|
|
96
|
+
return '';
|
|
97
|
+
try {
|
|
98
|
+
await mod.run(undefined, [], {});
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
if (e?.message !== '__EXIT__')
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
return printed.join('\n');
|
|
105
|
+
}
|
|
106
|
+
describe('every dispatched verb appears in its command help', () => {
|
|
107
|
+
const files = moduleFiles();
|
|
108
|
+
it('no waiver claims to hide a verb that is listed anyway', async () => {
|
|
109
|
+
const stale = [];
|
|
110
|
+
for (const [file, waived] of Object.entries(HIDDEN_OK)) {
|
|
111
|
+
const help = await helpTextOf(file);
|
|
112
|
+
for (const verb of Object.keys(waived)) {
|
|
113
|
+
if (new RegExp(`^\\s+\`?${verb}\\b`, 'm').test(help))
|
|
114
|
+
stale.push(`${file}: ${verb}`);
|
|
115
|
+
}
|
|
116
|
+
printed = [];
|
|
117
|
+
}
|
|
118
|
+
expect(stale, 'these are in the help now, so the waiver claims a gap that is ' +
|
|
119
|
+
'closed — delete the entry.').toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
it('found command modules to check (or this test proves nothing)', () => {
|
|
122
|
+
expect(files.length).toBeGreaterThan(10);
|
|
123
|
+
});
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const src = readFileSync(path.join(COMMANDS_DIR, file), 'utf8');
|
|
126
|
+
const verbs = dispatchedVerbs(src);
|
|
127
|
+
if (!verbs.length)
|
|
128
|
+
continue;
|
|
129
|
+
it(`${file} lists all ${verbs.length} of its verbs`, async () => {
|
|
130
|
+
const help = await helpTextOf(file);
|
|
131
|
+
// A module whose bare `run()` does not print help has nothing to check
|
|
132
|
+
// here; its verbs are reachable from the parent listing.
|
|
133
|
+
if (!help.trim())
|
|
134
|
+
return;
|
|
135
|
+
const waived = HIDDEN_OK[file] ?? {};
|
|
136
|
+
const missing = verbs
|
|
137
|
+
.filter(v => !(v in waived))
|
|
138
|
+
// Listed means: named at the start of an indented line, the shape
|
|
139
|
+
// every command uses for its entries.
|
|
140
|
+
.filter(v => !new RegExp(`^\\s+\`?${v}\\b`, 'm').test(help));
|
|
141
|
+
expect(missing, `${file}: ${missing.join(', ')} dispatch but are not in the ` +
|
|
142
|
+
'help this command prints. A verb nobody can find is a verb nobody has — ' +
|
|
143
|
+
'add it to the listing, or to HIDDEN_OK with a reason.').toEqual([]);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myapihq/cli",
|
|
3
3
|
"license": "Apache-2.0",
|
|
4
|
-
"version": "2.25.
|
|
4
|
+
"version": "2.25.1",
|
|
5
5
|
"description": "MyAPI command-line interface",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"lint:skills:strict": "node scripts/copy-skills.js && node scripts/lint-skills.js --strict"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@myapihq/sdk": "^2.25.
|
|
49
|
+
"@myapihq/sdk": "^2.25.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0",
|