@gpdoc/cli 1.2.0 → 1.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.
- package/README.md +1 -1
- package/bin/gpdoc.js +60 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Conversions write to standard output or an explicit output path. Existing files
|
|
|
18
18
|
|
|
19
19
|
## Account and remote providers
|
|
20
20
|
|
|
21
|
-
Use `gpdoc login` to begin GPDoc's device authorization flow
|
|
21
|
+
Use `gpdoc login` to begin GPDoc's device authorization flow. It attempts to open your browser, waits for authorization, and writes the local CLI session before returning. If it cannot launch a browser, use the printed URL and code manually. Use `gpdoc login --no-browser` in a headless session. If you interrupt the command after completing browser authorization, run `gpdoc login complete` before the displayed expiry to finish saving the local session. `gpdoc whoami --json` reports the CLI session without including credentials. `gpdoc logout` clears only the local CLI credential file.
|
|
22
22
|
|
|
23
23
|
For CI, set `GPDOC_ACCESS_TOKEN` instead of signing in interactively. The CLI does not persist that environment value. Interactive credentials are stored outside the current workspace with user-only permissions. Provider tokens are not shown in command output.
|
|
24
24
|
|
package/bin/gpdoc.js
CHANGED
|
@@ -27,7 +27,7 @@ class CliError extends Error {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function usage() {
|
|
30
|
-
return `Usage:\n gpdoc new OUTPUT [--title TITLE] [--json]\n gpdoc inspect INPUT [--json]\n gpdoc validate INPUT [--json]\n gpdoc convert INPUT --to gpdoc|markdown|html|text|json|docx|pdf [--output PATH | --in-place] [--json]\n gpdoc edit INPUT [--json]\n gpdoc login [complete] [--json]\n gpdoc logout [--json]\n gpdoc whoami [--json]\n gpdoc google upload INPUT [--title TITLE] [--json]\n gpdoc google update INPUT --drive-id ID --item-id ID --revision REVISION [--json]\n gpdoc microsoft upload INPUT [--filename NAME] [--json]\n gpdoc microsoft update INPUT --drive-id ID --item-id ID [--json]\n gpdoc microsoft share --drive-id ID --item-id ID --role view|edit --scope anonymous|organization [--json]\n gpdoc gist create INPUT [--private] [--description TEXT] [--json]\n gpdoc gist update GIST_ID INPUT [--description TEXT] [--json]\n gpdoc repo put OWNER/REPOSITORY INPUT --path REMOTE_PATH [--branch BRANCH] [--message TEXT] [--json]\n`;
|
|
30
|
+
return `Usage:\n gpdoc new OUTPUT [--title TITLE] [--json]\n gpdoc inspect INPUT [--json]\n gpdoc validate INPUT [--json]\n gpdoc convert INPUT --to gpdoc|markdown|html|text|json|docx|pdf [--output PATH | --in-place] [--json]\n gpdoc edit INPUT [--json]\n gpdoc login [complete] [--no-browser] [--json]\n gpdoc logout [--json]\n gpdoc whoami [--json]\n gpdoc google upload INPUT [--title TITLE] [--json]\n gpdoc google update INPUT --drive-id ID --item-id ID --revision REVISION [--json]\n gpdoc microsoft upload INPUT [--filename NAME] [--json]\n gpdoc microsoft update INPUT --drive-id ID --item-id ID [--json]\n gpdoc microsoft share --drive-id ID --item-id ID --role view|edit --scope anonymous|organization [--json]\n gpdoc gist create INPUT [--private] [--description TEXT] [--json]\n gpdoc gist update GIST_ID INPUT [--description TEXT] [--json]\n gpdoc repo put OWNER/REPOSITORY INPUT --path REMOTE_PATH [--branch BRANCH] [--message TEXT] [--json]\n`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function parseArguments(argv) {
|
|
@@ -42,7 +42,7 @@ function parseArguments(argv) {
|
|
|
42
42
|
continue;
|
|
43
43
|
}
|
|
44
44
|
const key = value.slice(2);
|
|
45
|
-
if (['json', 'in-place', 'private'].includes(key)) {
|
|
45
|
+
if (['json', 'in-place', 'private', 'no-browser'].includes(key)) {
|
|
46
46
|
options[key] = true;
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
@@ -119,6 +119,47 @@ function launchEditor(editor, target) {
|
|
|
119
119
|
});
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
function delay(milliseconds) {
|
|
123
|
+
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// The device flow does not send a callback to the local terminal. The foreground
|
|
127
|
+
// CLI process opens the provider page, then polls the token endpoint itself.
|
|
128
|
+
export async function openBrowser(url, { platform = process.platform, spawnImpl = spawn } = {}) {
|
|
129
|
+
const command = platform === 'darwin'
|
|
130
|
+
? ['open', [url]]
|
|
131
|
+
: platform === 'win32'
|
|
132
|
+
? ['cmd', ['/c', 'start', '', url]]
|
|
133
|
+
: ['xdg-open', [url]];
|
|
134
|
+
try {
|
|
135
|
+
return await new Promise((resolve) => {
|
|
136
|
+
let settled = false;
|
|
137
|
+
const complete = (opened) => {
|
|
138
|
+
if (settled) return;
|
|
139
|
+
settled = true;
|
|
140
|
+
resolve(opened);
|
|
141
|
+
};
|
|
142
|
+
const child = spawnImpl(command[0], command[1], { detached: true, stdio: 'ignore', windowsHide: true });
|
|
143
|
+
child.once('error', () => complete(false));
|
|
144
|
+
child.once('spawn', () => {
|
|
145
|
+
child.unref?.();
|
|
146
|
+
complete(true);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function waitForDeviceAuthorization(auth, sleep = delay) {
|
|
155
|
+
for (;;) {
|
|
156
|
+
const result = await auth.complete();
|
|
157
|
+
if (result.status !== 'pending') return result;
|
|
158
|
+
const retryAfterSeconds = Math.max(1, Number(result.retryAfterSeconds || 1));
|
|
159
|
+
await sleep(retryAfterSeconds * 1000);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
122
163
|
function requireOption(options, key, message) {
|
|
123
164
|
if (!options[key]) throw new CliError('USAGE', message || `--${key} is required.`);
|
|
124
165
|
return options[key];
|
|
@@ -139,7 +180,7 @@ function renderRemote(result, json) {
|
|
|
139
180
|
return printResult(result, false);
|
|
140
181
|
}
|
|
141
182
|
|
|
142
|
-
// @spec CLI-001, CLI-002, CLI-003, CLI-004, CLI-005, CLI-006, CLI-007, CLI-008, CLI-009, CLI-014, CLI-015, CLI-016, CLI-017, CLI-018, CLI-019, CLI-020, CLI-021, CLI-022, CLI-023, CLI-024, CLI-025
|
|
183
|
+
// @spec CLI-001, CLI-002, CLI-003, CLI-004, CLI-005, CLI-006, CLI-007, CLI-008, CLI-009, CLI-014, CLI-015, CLI-016, CLI-017, CLI-018, CLI-019, CLI-020, CLI-021, CLI-022, CLI-023, CLI-024, CLI-025, CLI-028
|
|
143
184
|
export async function run(argv, runtime = {}) {
|
|
144
185
|
const parsed = parseArguments(argv);
|
|
145
186
|
if (parsed.command === 'help') {
|
|
@@ -154,8 +195,22 @@ export async function run(argv, runtime = {}) {
|
|
|
154
195
|
const remote = runtime.remote || createRemoteClient({ auth, env: runtime.env || process.env, fetchImpl: runtime.fetchImpl || globalThis.fetch });
|
|
155
196
|
|
|
156
197
|
if (parsed.command === 'login') {
|
|
157
|
-
|
|
158
|
-
|
|
198
|
+
if (parsed.positionals[0] === 'complete') {
|
|
199
|
+
printResult(await auth.complete(), parsed.options.json);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const started = await auth.start();
|
|
203
|
+
const verificationUrl = started.verificationUriComplete || started.verificationUri;
|
|
204
|
+
const browserOpened = parsed.options['no-browser']
|
|
205
|
+
? false
|
|
206
|
+
: await (runtime.openBrowser || openBrowser)(verificationUrl);
|
|
207
|
+
if (!parsed.options.json) {
|
|
208
|
+
printResult(started, false);
|
|
209
|
+
if (browserOpened) process.stdout.write('Opened your browser. Complete sign-in there; GPDoc will finish this command automatically.\n');
|
|
210
|
+
else process.stdout.write(`Open ${verificationUrl} in a browser, then return here. GPDoc is waiting for authorization.\n`);
|
|
211
|
+
}
|
|
212
|
+
const completed = await waitForDeviceAuthorization(auth, runtime.sleep || delay);
|
|
213
|
+
printResult({ ...started, browserOpened, ...completed }, parsed.options.json);
|
|
159
214
|
return;
|
|
160
215
|
}
|
|
161
216
|
if (parsed.command === 'logout') {
|