docugate 0.2.8 → 0.2.9
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/cli.js +75 -62
- package/dist/init.js +9 -4
- package/dist/tour-install.js +58 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13,57 +13,57 @@ import { startTourServer } from './tour-server.js';
|
|
|
13
13
|
import { login, clearCredentials, loadCredentials } from './auth.js';
|
|
14
14
|
import { BASE_URL, getSession } from './api.js';
|
|
15
15
|
const SITE = 'https://www.trydocugate.site';
|
|
16
|
-
const HELP = `docugate: set up and check the docs DocuGate reads from your repository
|
|
17
|
-
|
|
18
|
-
Usage
|
|
19
|
-
docugate <command> [options]
|
|
20
|
-
|
|
21
|
-
Commands
|
|
22
|
-
init Set up this repository for DocuGate (interactive)
|
|
23
|
-
login Sign in to DocuGate with GitHub
|
|
24
|
-
logout Sign out and delete saved credentials
|
|
25
|
-
whoami Print the currently signed-in user
|
|
26
|
-
check Check the docs the way DocuGate will read them
|
|
27
|
-
openapi Prepare an OpenAPI spec for DocuGate's API reference
|
|
28
|
-
tour init Write the first tour of this app with IBM Bob
|
|
29
|
-
tour install Add the pill loader to your app
|
|
30
|
-
tour serve Start a local server that serves tour data for the pill
|
|
31
|
-
tour export Write every tour screen to a single JSON file
|
|
32
|
-
|
|
33
|
-
init
|
|
34
|
-
--dir <folder> Docs folder (default: docs)
|
|
35
|
-
--title <text> Space title (default: this folder's name)
|
|
36
|
-
--role <role> Repository role: frontend, backend, or both
|
|
37
|
-
--yes Accept every default without asking
|
|
38
|
-
|
|
39
|
-
check
|
|
40
|
-
--dir <folder> Check this folder instead of docugate.json's docsDir
|
|
41
|
-
--strict Treat warnings as errors
|
|
42
|
-
|
|
43
|
-
openapi
|
|
44
|
-
--from <file|url> The spec to read (or api.generate.from in docugate.json)
|
|
45
|
-
--out <file> Where to write it (default: openapi.json)
|
|
46
|
-
--include <glob> Keep only paths that match; repeatable
|
|
47
|
-
--exclude <glob> Drop paths that match; repeatable
|
|
48
|
-
--redact <name> Remove a property from every schema and example; repeatable
|
|
49
|
-
--check Fail if the file is out of date, instead of writing it
|
|
50
|
-
|
|
51
|
-
tour init
|
|
52
|
-
--max-cost <n> Most Bobcoins the run may spend (default: 3)
|
|
53
|
-
--team-id <id> Bob team to run under, for API keys that need one
|
|
54
|
-
|
|
55
|
-
tour install
|
|
56
|
-
--port <n> Port the pill server listens on (default: 4178)
|
|
57
|
-
--remove Remove the pill snippet instead of adding it
|
|
58
|
-
|
|
59
|
-
tour serve
|
|
60
|
-
--port <n> Port to listen on (default: 4178)
|
|
61
|
-
|
|
62
|
-
tour export <file>
|
|
63
|
-
Write all tour screens to <file> as JSON.
|
|
64
|
-
|
|
65
|
-
Run it from the root of your repository.
|
|
66
|
-
${SITE}
|
|
16
|
+
const HELP = `docugate: set up and check the docs DocuGate reads from your repository
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
docugate <command> [options]
|
|
20
|
+
|
|
21
|
+
Commands
|
|
22
|
+
init Set up this repository for DocuGate (interactive)
|
|
23
|
+
login Sign in to DocuGate with GitHub
|
|
24
|
+
logout Sign out and delete saved credentials
|
|
25
|
+
whoami Print the currently signed-in user
|
|
26
|
+
check Check the docs the way DocuGate will read them
|
|
27
|
+
openapi Prepare an OpenAPI spec for DocuGate's API reference
|
|
28
|
+
tour init Write the first tour of this app with IBM Bob
|
|
29
|
+
tour install Add the pill loader to your app
|
|
30
|
+
tour serve Start a local server that serves tour data for the pill
|
|
31
|
+
tour export Write every tour screen to a single JSON file
|
|
32
|
+
|
|
33
|
+
init
|
|
34
|
+
--dir <folder> Docs folder (default: docs)
|
|
35
|
+
--title <text> Space title (default: this folder's name)
|
|
36
|
+
--role <role> Repository role: frontend, backend, or both
|
|
37
|
+
--yes Accept every default without asking
|
|
38
|
+
|
|
39
|
+
check
|
|
40
|
+
--dir <folder> Check this folder instead of docugate.json's docsDir
|
|
41
|
+
--strict Treat warnings as errors
|
|
42
|
+
|
|
43
|
+
openapi
|
|
44
|
+
--from <file|url> The spec to read (or api.generate.from in docugate.json)
|
|
45
|
+
--out <file> Where to write it (default: openapi.json)
|
|
46
|
+
--include <glob> Keep only paths that match; repeatable
|
|
47
|
+
--exclude <glob> Drop paths that match; repeatable
|
|
48
|
+
--redact <name> Remove a property from every schema and example; repeatable
|
|
49
|
+
--check Fail if the file is out of date, instead of writing it
|
|
50
|
+
|
|
51
|
+
tour init
|
|
52
|
+
--max-cost <n> Most Bobcoins the run may spend (default: 3)
|
|
53
|
+
--team-id <id> Bob team to run under, for API keys that need one
|
|
54
|
+
|
|
55
|
+
tour install
|
|
56
|
+
--port <n> Port the pill server listens on (default: 4178)
|
|
57
|
+
--remove Remove the pill snippet instead of adding it
|
|
58
|
+
|
|
59
|
+
tour serve
|
|
60
|
+
--port <n> Port to listen on (default: 4178)
|
|
61
|
+
|
|
62
|
+
tour export <file>
|
|
63
|
+
Write all tour screens to <file> as JSON.
|
|
64
|
+
|
|
65
|
+
Run it from the root of your repository.
|
|
66
|
+
${SITE}
|
|
67
67
|
`;
|
|
68
68
|
const color = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
69
69
|
const paint = (code, text) => (color ? `\x1b[${code}m${text}\x1b[0m` : text);
|
|
@@ -287,6 +287,10 @@ async function main() {
|
|
|
287
287
|
// already / no-op
|
|
288
288
|
console.log(` ${dim('unchanged')} ${result.target}`);
|
|
289
289
|
}
|
|
290
|
+
if (result.hookAction === 'inserted')
|
|
291
|
+
console.log(` ${green('installed')} package.json ${dim(`(${result.hook}: the tour server starts with your app)`)}`);
|
|
292
|
+
if (result.hookAction === 'removed')
|
|
293
|
+
console.log(` ${green('removed')} package.json ${dim(`(${result.hook})`)}`);
|
|
290
294
|
return;
|
|
291
295
|
}
|
|
292
296
|
case 'init': {
|
|
@@ -297,28 +301,28 @@ async function main() {
|
|
|
297
301
|
}
|
|
298
302
|
catch (error) {
|
|
299
303
|
if (error instanceof BobKeyMissingError) {
|
|
300
|
-
console.error(`${red('error')} Bob Shell needs an API key to run on its own (your IDE sign-in is not used here).
|
|
304
|
+
console.error(`${red('error')} Bob Shell needs an API key to run on its own (your IDE sign-in is not used here).
|
|
301
305
|
` +
|
|
302
|
-
`1. At https://bob.ibm.com, open your instance, then API keys, and create an Inference key.
|
|
306
|
+
`1. At https://bob.ibm.com, open your instance, then API keys, and create an Inference key.
|
|
303
307
|
` +
|
|
304
|
-
`2. Save it as the BOB_API_KEY environment variable, then open a new terminal:
|
|
308
|
+
`2. Save it as the BOB_API_KEY environment variable, then open a new terminal:
|
|
305
309
|
` +
|
|
306
|
-
` PowerShell [Environment]::SetEnvironmentVariable("BOB_API_KEY", "<key>", "User")
|
|
310
|
+
` PowerShell [Environment]::SetEnvironmentVariable("BOB_API_KEY", "<key>", "User")
|
|
307
311
|
` +
|
|
308
|
-
` macOS/Linux export BOB_API_KEY=<key> (add it to your shell profile)
|
|
312
|
+
` macOS/Linux export BOB_API_KEY=<key> (add it to your shell profile)
|
|
309
313
|
` +
|
|
310
314
|
dim('Keep the key out of your repository. https://bob.ibm.com/docs/ide/account/api-keys'));
|
|
311
315
|
process.exit(2);
|
|
312
316
|
}
|
|
313
317
|
if (!(error instanceof BobMissingError))
|
|
314
318
|
throw error;
|
|
315
|
-
console.error(`${red('error')} Bob Shell is not installed, and tours are written by IBM Bob.
|
|
319
|
+
console.error(`${red('error')} Bob Shell is not installed, and tours are written by IBM Bob.
|
|
316
320
|
` +
|
|
317
|
-
`Install it (Node 24 or later), sign in, then run this again:
|
|
321
|
+
`Install it (Node 24 or later), sign in, then run this again:
|
|
318
322
|
` +
|
|
319
|
-
` Windows powershell -c "irm -Uri https://bob.ibm.com/download/bobshell.ps1 | iex"
|
|
323
|
+
` Windows powershell -c "irm -Uri https://bob.ibm.com/download/bobshell.ps1 | iex"
|
|
320
324
|
` +
|
|
321
|
-
` macOS/Linux curl -fsSL https://bob.ibm.com/download/bobshell.sh | bash
|
|
325
|
+
` macOS/Linux curl -fsSL https://bob.ibm.com/download/bobshell.sh | bash
|
|
322
326
|
` +
|
|
323
327
|
dim('https://bob.ibm.com/docs/shell/getting-started/install-and-setup'));
|
|
324
328
|
process.exit(2);
|
|
@@ -337,13 +341,22 @@ async function main() {
|
|
|
337
341
|
stats?.durationMs !== undefined ? `${Math.round(stats.durationMs / 1000)}s` : '',
|
|
338
342
|
stats?.cost !== undefined ? `${stats.cost} Bobcoins` : '',
|
|
339
343
|
].filter(Boolean);
|
|
340
|
-
console.log(`
|
|
344
|
+
console.log(`
|
|
341
345
|
${dim(`Bob: ${parts.join(', ')}`)}`);
|
|
342
346
|
console.log('Next: read the new files, fix anything Bob got wrong, and commit them. They are yours now.');
|
|
343
347
|
return;
|
|
344
348
|
}
|
|
345
349
|
case 'serve': {
|
|
346
|
-
|
|
350
|
+
const port = parseInt(one('port') ?? '4178', 10);
|
|
351
|
+
if (args.bools.has('background')) {
|
|
352
|
+
// Run from the app's predev script: start (or reuse) the server,
|
|
353
|
+
// say so in one line, and hand the terminal back to the app.
|
|
354
|
+
mkdirSync(join(root, '.docugate', 'tour'), { recursive: true });
|
|
355
|
+
const up = await startServerInBackground(root, port);
|
|
356
|
+
console.log(up ? `${green('DocuGate')} tour ready on port ${port}` : `${yellow('DocuGate')} could not start the tour server. Run docugate tour serve to see why.`);
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
await serveTour(root, port);
|
|
347
360
|
return;
|
|
348
361
|
}
|
|
349
362
|
case 'export': {
|
package/dist/init.js
CHANGED
|
@@ -188,7 +188,9 @@ async function askForBobKey(io, openUrl) {
|
|
|
188
188
|
return false;
|
|
189
189
|
}
|
|
190
190
|
useBobKeyForThisRun(key);
|
|
191
|
-
|
|
191
|
+
// Say it arrived, without showing it: the last four characters and the length.
|
|
192
|
+
io.print(` ${green('✓')} Key received (…${key.slice(-4)}, ${key.length} characters). Using it for this run only.`);
|
|
193
|
+
io.print(' To keep it for next time, set it in your environment:');
|
|
192
194
|
io.print(process.platform === 'win32'
|
|
193
195
|
? ' [Environment]::SetEnvironmentVariable("BOB_API_KEY", "<key>", "User") (then open a new terminal)'
|
|
194
196
|
: ' export BOB_API_KEY=<key> (add it to your shell profile)');
|
|
@@ -549,14 +551,14 @@ export async function initFlow(root, options = {}) {
|
|
|
549
551
|
try {
|
|
550
552
|
tourResult = tourInit(root, { runBob: options.runBob });
|
|
551
553
|
stop();
|
|
552
|
-
io.print(` IBM Bob wrote the tour.`);
|
|
554
|
+
io.print(` ${green('✓')} IBM Bob wrote the tour (${tourResult.added.length} screen${tourResult.added.length === 1 ? '' : 's'}).`);
|
|
553
555
|
}
|
|
554
556
|
catch (error) {
|
|
555
557
|
stop();
|
|
556
558
|
// Bob missing or failed — describe what went wrong, then continue.
|
|
557
559
|
if (error instanceof BobKeyMissingError) {
|
|
558
560
|
tourError =
|
|
559
|
-
|
|
561
|
+
`✗ IBM Bob did not accept this key. Check it is an Inference key from bob.ibm.com, then run docugate tour init.`;
|
|
560
562
|
}
|
|
561
563
|
else if (error instanceof BobMissingError) {
|
|
562
564
|
tourError =
|
|
@@ -623,6 +625,8 @@ export async function initFlow(root, options = {}) {
|
|
|
623
625
|
if (role !== 'backend') {
|
|
624
626
|
rows.push(['Tour', tourResult ? `IBM Bob wrote ${tourResult.added.length} screen${tourResult.added.length === 1 ? '' : 's'}` : 'add steps in the browser']);
|
|
625
627
|
rows.push(['Tour button', hasPill && installResult?.target ? installResult.target : 'not added']);
|
|
628
|
+
if (installResult?.hook)
|
|
629
|
+
rows.push(['Starts with', `npm run ${installResult.hook.replace(/^pre/, '')}`]);
|
|
626
630
|
}
|
|
627
631
|
io.print(``);
|
|
628
632
|
io.print(` ${dimmed('╭')} ${bold('DocuGate setup')}`);
|
|
@@ -636,7 +640,8 @@ export async function initFlow(root, options = {}) {
|
|
|
636
640
|
}
|
|
637
641
|
else if (hasPill) {
|
|
638
642
|
io.print(` ${green('✓')} ${bold('DocuGate is set up.')}`);
|
|
639
|
-
|
|
643
|
+
const run = installResult?.hook ? `npm run ${installResult.hook.replace(/^pre/, '')}` : 'npm run dev';
|
|
644
|
+
io.print(` Start your project as usual (${gold(run)}) and open it:`);
|
|
640
645
|
io.print(` the ${gold('Tour')} button is in the bottom-right corner.`);
|
|
641
646
|
}
|
|
642
647
|
else {
|
package/dist/tour-install.js
CHANGED
|
@@ -24,6 +24,59 @@ export const CANDIDATES = [
|
|
|
24
24
|
'app/index.html',
|
|
25
25
|
'public/index.html',
|
|
26
26
|
];
|
|
27
|
+
/** What the app's own start script runs first, so the tour server comes up with it. */
|
|
28
|
+
export const SERVE_HOOK = 'docugate tour serve --background';
|
|
29
|
+
/**
|
|
30
|
+
* Adds `docugate tour serve --background` as the `predev` script (or
|
|
31
|
+
* `prestart`, for apps started with npm start), so running the project the
|
|
32
|
+
* usual way also starts the tour server. npm runs pre-scripts by itself, on
|
|
33
|
+
* every platform. An existing pre-script keeps running first. Only this
|
|
34
|
+
* repository's package.json, and only its scripts, are touched.
|
|
35
|
+
*/
|
|
36
|
+
export function installStartHook(root, options = {}) {
|
|
37
|
+
const path = join(root, 'package.json');
|
|
38
|
+
if (!existsSync(path))
|
|
39
|
+
return { action: 'none' };
|
|
40
|
+
const text = readFileSync(path, 'utf8');
|
|
41
|
+
let pkg;
|
|
42
|
+
try {
|
|
43
|
+
pkg = JSON.parse(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return { action: 'none' };
|
|
47
|
+
}
|
|
48
|
+
const scripts = pkg.scripts ?? {};
|
|
49
|
+
const base = scripts.dev ? 'dev' : scripts.start ? 'start' : null;
|
|
50
|
+
if (!base)
|
|
51
|
+
return { action: 'none' };
|
|
52
|
+
const hook = `pre${base}`;
|
|
53
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
54
|
+
const command = port === DEFAULT_PORT ? SERVE_HOOK : `${SERVE_HOOK} --port ${port}`;
|
|
55
|
+
const existing = scripts[hook];
|
|
56
|
+
if (options.remove) {
|
|
57
|
+
if (!existing?.includes(SERVE_HOOK))
|
|
58
|
+
return { hook, action: 'already' };
|
|
59
|
+
const rest = existing
|
|
60
|
+
.split('&&')
|
|
61
|
+
.map((part) => part.trim())
|
|
62
|
+
.filter((part) => !part.startsWith(SERVE_HOOK))
|
|
63
|
+
.join(' && ');
|
|
64
|
+
if (rest)
|
|
65
|
+
scripts[hook] = rest;
|
|
66
|
+
else
|
|
67
|
+
delete scripts[hook];
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (existing?.includes(SERVE_HOOK))
|
|
71
|
+
return { hook, action: 'already' };
|
|
72
|
+
scripts[hook] = existing ? `${existing} && ${command}` : command;
|
|
73
|
+
}
|
|
74
|
+
pkg.scripts = scripts;
|
|
75
|
+
const indent = text.match(/^[ \t]+(?=")/m)?.[0] ?? ' ';
|
|
76
|
+
const eol = text.includes('\r\n') ? '\r\n' : '\n';
|
|
77
|
+
writeFileSync(path, JSON.stringify(pkg, null, indent).replace(/\n/g, eol) + eol);
|
|
78
|
+
return { hook, action: options.remove ? 'removed' : 'inserted' };
|
|
79
|
+
}
|
|
27
80
|
/** Returns true if any candidate file already contains the pill snippet. */
|
|
28
81
|
export function hasPillSnippet(root) {
|
|
29
82
|
for (const rel of CANDIDATES) {
|
|
@@ -100,6 +153,11 @@ function removeSnippet(text, open, close) {
|
|
|
100
153
|
* already present is a no-op.
|
|
101
154
|
*/
|
|
102
155
|
export function tourInstall(root, options = {}) {
|
|
156
|
+
const result = installSnippet(root, options);
|
|
157
|
+
const started = installStartHook(root, options);
|
|
158
|
+
return { ...result, hook: started.hook, hookAction: started.action };
|
|
159
|
+
}
|
|
160
|
+
function installSnippet(root, options) {
|
|
103
161
|
const port = options.port ?? DEFAULT_PORT;
|
|
104
162
|
const remove = options.remove ?? false;
|
|
105
163
|
for (const rel of CANDIDATES) {
|
package/package.json
CHANGED