@nocobase/app-cli-client 0.0.0-stage → 0.1.0-beta.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 +24 -2
- package/dist/brand.d.ts +52 -0
- package/dist/brand.d.ts.map +1 -0
- package/dist/brand.js +61 -0
- package/dist/brand.js.map +1 -0
- package/dist/commands.d.ts +9 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +30 -0
- package/dist/commands.js.map +1 -0
- package/dist/completion.d.ts +13 -0
- package/dist/completion.d.ts.map +1 -0
- package/dist/completion.js +157 -0
- package/dist/completion.js.map +1 -0
- package/dist/config.d.ts +100 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/docs.d.ts +9 -0
- package/dist/docs.d.ts.map +1 -0
- package/dist/docs.js +146 -0
- package/dist/docs.js.map +1 -0
- package/dist/dynamic/execute.d.ts +24 -0
- package/dist/dynamic/execute.d.ts.map +1 -0
- package/dist/dynamic/execute.js +125 -0
- package/dist/dynamic/execute.js.map +1 -0
- package/dist/dynamic/files.d.ts +21 -0
- package/dist/dynamic/files.d.ts.map +1 -0
- package/dist/dynamic/files.js +79 -0
- package/dist/dynamic/files.js.map +1 -0
- package/dist/dynamic/help.d.ts +6 -0
- package/dist/dynamic/help.d.ts.map +1 -0
- package/dist/dynamic/help.js +42 -0
- package/dist/dynamic/help.js.map +1 -0
- package/dist/dynamic/index.d.ts +34 -0
- package/dist/dynamic/index.d.ts.map +1 -0
- package/dist/dynamic/index.js +197 -0
- package/dist/dynamic/index.js.map +1 -0
- package/dist/dynamic/manifest.d.ts +21 -0
- package/dist/dynamic/manifest.d.ts.map +1 -0
- package/dist/dynamic/manifest.js +175 -0
- package/dist/dynamic/manifest.js.map +1 -0
- package/dist/dynamic/output.d.ts +14 -0
- package/dist/dynamic/output.d.ts.map +1 -0
- package/dist/dynamic/output.js +103 -0
- package/dist/dynamic/output.js.map +1 -0
- package/dist/dynamic/parse.d.ts +3 -0
- package/dist/dynamic/parse.d.ts.map +1 -0
- package/dist/dynamic/parse.js +13 -0
- package/dist/dynamic/parse.js.map +1 -0
- package/dist/dynamic/rest.d.ts +29 -0
- package/dist/dynamic/rest.d.ts.map +1 -0
- package/dist/dynamic/rest.js +212 -0
- package/dist/dynamic/rest.js.map +1 -0
- package/dist/dynamic/session.d.ts +20 -0
- package/dist/dynamic/session.d.ts.map +1 -0
- package/dist/dynamic/session.js +70 -0
- package/dist/dynamic/session.js.map +1 -0
- package/dist/dynamic/ticket.d.ts +13 -0
- package/dist/dynamic/ticket.d.ts.map +1 -0
- package/dist/dynamic/ticket.js +40 -0
- package/dist/dynamic/ticket.js.map +1 -0
- package/dist/hooks/command-not-found.d.ts +4 -0
- package/dist/hooks/command-not-found.d.ts.map +1 -0
- package/dist/hooks/command-not-found.js +6 -0
- package/dist/hooks/command-not-found.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +72 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +148 -0
- package/dist/install.js.map +1 -0
- package/dist/lib/command.d.ts +35 -0
- package/dist/lib/command.d.ts.map +1 -0
- package/dist/lib/command.js +97 -0
- package/dist/lib/command.js.map +1 -0
- package/dist/lib/credentials.d.ts +110 -0
- package/dist/lib/credentials.d.ts.map +1 -0
- package/dist/lib/credentials.js +330 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/device.d.ts +48 -0
- package/dist/lib/device.d.ts.map +1 -0
- package/dist/lib/device.js +189 -0
- package/dist/lib/device.js.map +1 -0
- package/dist/lib/envelope.d.ts +21 -0
- package/dist/lib/envelope.d.ts.map +1 -0
- package/dist/lib/envelope.js +87 -0
- package/dist/lib/envelope.js.map +1 -0
- package/dist/lib/files.d.ts +4 -0
- package/dist/lib/files.d.ts.map +1 -0
- package/dist/lib/files.js +23 -0
- package/dist/lib/files.js.map +1 -0
- package/dist/lib/globals.d.ts +25 -0
- package/dist/lib/globals.d.ts.map +1 -0
- package/dist/lib/globals.js +77 -0
- package/dist/lib/globals.js.map +1 -0
- package/dist/lib/http.d.ts +45 -0
- package/dist/lib/http.d.ts.map +1 -0
- package/dist/lib/http.js +141 -0
- package/dist/lib/http.js.map +1 -0
- package/dist/lib/mime.d.ts +3 -0
- package/dist/lib/mime.d.ts.map +1 -0
- package/dist/lib/mime.js +38 -0
- package/dist/lib/mime.js.map +1 -0
- package/dist/lib/secrets/index.d.ts +18 -0
- package/dist/lib/secrets/index.d.ts.map +1 -0
- package/dist/lib/secrets/index.js +55 -0
- package/dist/lib/secrets/index.js.map +1 -0
- package/dist/lib/secrets/macos.d.ts +13 -0
- package/dist/lib/secrets/macos.d.ts.map +1 -0
- package/dist/lib/secrets/macos.js +61 -0
- package/dist/lib/secrets/macos.js.map +1 -0
- package/dist/lib/secrets/secret-service.d.ts +17 -0
- package/dist/lib/secrets/secret-service.d.ts.map +1 -0
- package/dist/lib/secrets/secret-service.js +81 -0
- package/dist/lib/secrets/secret-service.js.map +1 -0
- package/dist/lib/secrets/store.d.ts +28 -0
- package/dist/lib/secrets/store.d.ts.map +1 -0
- package/dist/lib/secrets/store.js +41 -0
- package/dist/lib/secrets/store.js.map +1 -0
- package/dist/lib/secrets/windows.d.ts +20 -0
- package/dist/lib/secrets/windows.d.ts.map +1 -0
- package/dist/lib/secrets/windows.js +155 -0
- package/dist/lib/secrets/windows.js.map +1 -0
- package/dist/lib/update-hint.d.ts +5 -0
- package/dist/lib/update-hint.d.ts.map +1 -0
- package/dist/lib/update-hint.js +26 -0
- package/dist/lib/update-hint.js.map +1 -0
- package/dist/login.d.ts +15 -0
- package/dist/login.d.ts.map +1 -0
- package/dist/login.js +232 -0
- package/dist/login.js.map +1 -0
- package/dist/logout.d.ts +13 -0
- package/dist/logout.d.ts.map +1 -0
- package/dist/logout.js +56 -0
- package/dist/logout.js.map +1 -0
- package/dist/parse/arguments.d.ts +84 -0
- package/dist/parse/arguments.d.ts.map +1 -0
- package/dist/parse/arguments.js +460 -0
- package/dist/parse/arguments.js.map +1 -0
- package/dist/parse/exit.d.ts +7 -0
- package/dist/parse/exit.d.ts.map +1 -0
- package/dist/parse/exit.js +24 -0
- package/dist/parse/exit.js.map +1 -0
- package/dist/parse/help.d.ts +22 -0
- package/dist/parse/help.d.ts.map +1 -0
- package/dist/parse/help.js +151 -0
- package/dist/parse/help.js.map +1 -0
- package/dist/parse/index.d.ts +5 -0
- package/dist/parse/index.d.ts.map +1 -0
- package/dist/parse/index.js +7 -0
- package/dist/parse/index.js.map +1 -0
- package/dist/parse/manifest.d.ts +97 -0
- package/dist/parse/manifest.d.ts.map +1 -0
- package/dist/parse/manifest.js +4 -0
- package/dist/parse/manifest.js.map +1 -0
- package/dist/profile.d.ts +4 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +80 -0
- package/dist/profile.js.map +1 -0
- package/dist/request.d.ts +25 -0
- package/dist/request.d.ts.map +1 -0
- package/dist/request.js +35 -0
- package/dist/request.js.map +1 -0
- package/dist/run.d.ts +17 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +88 -0
- package/dist/run.js.map +1 -0
- package/dist/update.d.ts +45 -0
- package/dist/update.d.ts.map +1 -0
- package/dist/update.js +177 -0
- package/dist/update.js.map +1 -0
- package/dist/whoami.d.ts +31 -0
- package/dist/whoami.d.ts.map +1 -0
- package/dist/whoami.js +118 -0
- package/dist/whoami.js.map +1 -0
- package/package.json +67 -3
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The macOS Keychain through `/usr/bin/security`, called by its absolute path so nothing on PATH stands in for it.
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { firstValue, runTool, SecretStoreError, toolError, } from "./store.js";
|
|
4
|
+
export const SECURITY = '/usr/bin/security';
|
|
5
|
+
/** `errSecItemNotFound`: how `find-` and `delete-generic-password` exit when there is no item. */
|
|
6
|
+
const NOT_FOUND = 44;
|
|
7
|
+
/** `security -i` splits its input on whitespace and reads double-quoted words; refuse what would need escaping. */
|
|
8
|
+
function quote(value) {
|
|
9
|
+
if (/["\\\n\r]/u.test(value))
|
|
10
|
+
throw new SecretStoreError('The value contains characters the macOS Keychain command cannot take.');
|
|
11
|
+
return `"${value}"`;
|
|
12
|
+
}
|
|
13
|
+
export class MacKeychain {
|
|
14
|
+
kind = 'keychain';
|
|
15
|
+
run;
|
|
16
|
+
platform;
|
|
17
|
+
constructor(run = runTool, platform = process.platform) {
|
|
18
|
+
this.run = run;
|
|
19
|
+
this.platform = platform;
|
|
20
|
+
}
|
|
21
|
+
async available() {
|
|
22
|
+
if (this.platform !== 'darwin' || !existsSync(SECURITY))
|
|
23
|
+
return false;
|
|
24
|
+
return (await this.run(SECURITY, ['default-keychain'])).code === 0;
|
|
25
|
+
}
|
|
26
|
+
async get(service, account) {
|
|
27
|
+
const result = await this.run(SECURITY, [
|
|
28
|
+
'find-generic-password',
|
|
29
|
+
'-s',
|
|
30
|
+
service,
|
|
31
|
+
'-a',
|
|
32
|
+
account,
|
|
33
|
+
'-w',
|
|
34
|
+
]);
|
|
35
|
+
if (result.code === NOT_FOUND)
|
|
36
|
+
return undefined;
|
|
37
|
+
if (result.code !== 0)
|
|
38
|
+
throw new SecretStoreError(`Cannot read the macOS Keychain: ${toolError(result)}`);
|
|
39
|
+
return firstValue(result.stdout);
|
|
40
|
+
}
|
|
41
|
+
async set(service, account, secret, label) {
|
|
42
|
+
// `security -i` reads the command from standard input, so the secret never appears in the process list. It exits 0
|
|
43
|
+
// even when the command fails, hence the read-back.
|
|
44
|
+
const line = `add-generic-password -U -s ${quote(service)} -a ${quote(account)} -l ${quote(label)} -w ${quote(secret)}\n`;
|
|
45
|
+
const result = await this.run(SECURITY, ['-i'], line);
|
|
46
|
+
if (result.code !== 0 || (await this.get(service, account)) !== secret)
|
|
47
|
+
throw new SecretStoreError(`Cannot write to the macOS Keychain: ${result.stderr.trim() || 'the item did not stick'}`);
|
|
48
|
+
}
|
|
49
|
+
async delete(service, account) {
|
|
50
|
+
const result = await this.run(SECURITY, [
|
|
51
|
+
'delete-generic-password',
|
|
52
|
+
'-s',
|
|
53
|
+
service,
|
|
54
|
+
'-a',
|
|
55
|
+
account,
|
|
56
|
+
]);
|
|
57
|
+
if (result.code !== 0 && result.code !== NOT_FOUND)
|
|
58
|
+
throw new SecretStoreError(`Cannot delete from the macOS Keychain: ${toolError(result)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=macos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"macos.js","sourceRoot":"","sources":["../../../src/lib/secrets/macos.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RunTool, type SecretStore } from './store.ts';
|
|
2
|
+
/** `secret-tool` on `searchPath`, or undefined. */
|
|
3
|
+
export declare function findSecretTool(searchPath?: string): string | undefined;
|
|
4
|
+
export declare class SecretService implements SecretStore {
|
|
5
|
+
readonly kind: "secret-service";
|
|
6
|
+
private readonly run;
|
|
7
|
+
private readonly tool;
|
|
8
|
+
private readonly env;
|
|
9
|
+
private readonly platform;
|
|
10
|
+
constructor(run?: RunTool, tool?: string | undefined, env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform);
|
|
11
|
+
available(): Promise<boolean>;
|
|
12
|
+
get(service: string, account: string): Promise<string | undefined>;
|
|
13
|
+
set(service: string, account: string, secret: string, label: string): Promise<void>;
|
|
14
|
+
delete(service: string, account: string): Promise<void>;
|
|
15
|
+
private requireTool;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=secret-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-service.d.ts","sourceRoot":"","sources":["../../../src/lib/secrets/secret-service.ts"],"names":[],"mappings":"AAKA,OAAO,EAKL,KAAK,OAAO,EACZ,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAEpB,mDAAmD;AACnD,wBAAgB,cAAc,CAC5B,UAAU,GAAE,MAA+B,GAC1C,MAAM,GAAG,SAAS,CAOpB;AAED,qBAAa,aAAc,YAAW,WAAW;IAC/C,QAAQ,CAAC,IAAI,EAAG,gBAAgB,CAAU;IAE1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAqB;IAC1C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAoB;IACxC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAGzC,GAAG,GAAE,OAAiB,EACtB,IAAI,GAAE,MAAM,GAAG,SAA4B,EAC3C,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B;IAQxC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAoB7B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAgBlE,GAAG,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAaV,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc7D,OAAO,CAAC,WAAW;CAKpB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// The Secret Service (GNOME Keyring, KWallet) through libsecret's `secret-tool`. It needs a desktop session's D-Bus: on
|
|
2
|
+
// a headless server there is usually none, and the CLI falls back to a file.
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { firstValue, runTool, SecretStoreError, toolError, } from "./store.js";
|
|
6
|
+
/** `secret-tool` on `searchPath`, or undefined. */
|
|
7
|
+
export function findSecretTool(searchPath = process.env.PATH ?? '') {
|
|
8
|
+
for (const dir of searchPath.split(path.delimiter)) {
|
|
9
|
+
if (dir === '')
|
|
10
|
+
continue;
|
|
11
|
+
const candidate = path.join(dir, 'secret-tool');
|
|
12
|
+
if (existsSync(candidate))
|
|
13
|
+
return candidate;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export class SecretService {
|
|
18
|
+
kind = 'secret-service';
|
|
19
|
+
run;
|
|
20
|
+
tool;
|
|
21
|
+
env;
|
|
22
|
+
platform;
|
|
23
|
+
constructor(run = runTool, tool = findSecretTool(), env = process.env, platform = process.platform) {
|
|
24
|
+
this.run = run;
|
|
25
|
+
this.tool = tool;
|
|
26
|
+
this.env = env;
|
|
27
|
+
this.platform = platform;
|
|
28
|
+
}
|
|
29
|
+
async available() {
|
|
30
|
+
if (this.platform !== 'linux' ||
|
|
31
|
+
this.tool === undefined ||
|
|
32
|
+
(this.env.DBUS_SESSION_BUS_ADDRESS ?? '') === '')
|
|
33
|
+
return false;
|
|
34
|
+
// `lookup` exits 1 with nothing on stderr when there is simply no item; a missing service or bus says why.
|
|
35
|
+
const result = await this.run(this.tool, [
|
|
36
|
+
'lookup',
|
|
37
|
+
'service',
|
|
38
|
+
'nocobase-cli-probe',
|
|
39
|
+
'account',
|
|
40
|
+
'probe',
|
|
41
|
+
]);
|
|
42
|
+
return (result.code === 0 || (result.code === 1 && result.stderr.trim() === ''));
|
|
43
|
+
}
|
|
44
|
+
async get(service, account) {
|
|
45
|
+
const result = await this.run(this.requireTool(), [
|
|
46
|
+
'lookup',
|
|
47
|
+
'service',
|
|
48
|
+
service,
|
|
49
|
+
'account',
|
|
50
|
+
account,
|
|
51
|
+
]);
|
|
52
|
+
if (result.code === 1 && result.stderr.trim() === '')
|
|
53
|
+
return undefined;
|
|
54
|
+
if (result.code !== 0)
|
|
55
|
+
throw new SecretStoreError(`Cannot read the Secret Service: ${toolError(result)}`);
|
|
56
|
+
return firstValue(result.stdout);
|
|
57
|
+
}
|
|
58
|
+
async set(service, account, secret, label) {
|
|
59
|
+
// `store` reads the secret from standard input when it is not a terminal.
|
|
60
|
+
const result = await this.run(this.requireTool(), ['store', `--label=${label}`, 'service', service, 'account', account], secret);
|
|
61
|
+
if (result.code !== 0 || (await this.get(service, account)) !== secret)
|
|
62
|
+
throw new SecretStoreError(`Cannot write to the Secret Service: ${result.stderr.trim() || 'the item did not stick'}`);
|
|
63
|
+
}
|
|
64
|
+
async delete(service, account) {
|
|
65
|
+
const result = await this.run(this.requireTool(), [
|
|
66
|
+
'clear',
|
|
67
|
+
'service',
|
|
68
|
+
service,
|
|
69
|
+
'account',
|
|
70
|
+
account,
|
|
71
|
+
]);
|
|
72
|
+
if (result.code !== 0 && result.stderr.trim() !== '')
|
|
73
|
+
throw new SecretStoreError(`Cannot delete from the Secret Service: ${toolError(result)}`);
|
|
74
|
+
}
|
|
75
|
+
requireTool() {
|
|
76
|
+
if (this.tool === undefined)
|
|
77
|
+
throw new SecretStoreError('secret-tool is not installed.');
|
|
78
|
+
return this.tool;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=secret-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-service.js","sourceRoot":"","sources":["../../../src/lib/secrets/secret-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export type SecretStoreKind = 'keychain' | 'secret-service' | 'credential-manager';
|
|
2
|
+
export interface SecretStore {
|
|
3
|
+
readonly kind: SecretStoreKind;
|
|
4
|
+
/** False when there is no usable keychain here (no tool, no session bus, no default keychain). */
|
|
5
|
+
available(): Promise<boolean>;
|
|
6
|
+
/** The stored secret, or undefined when there is no item. Throws `SecretStoreError` when the keychain cannot be read. */
|
|
7
|
+
get(service: string, account: string): Promise<string | undefined>;
|
|
8
|
+
/** Stores or replaces the secret and reads it back; throws `SecretStoreError` when it did not stick. */
|
|
9
|
+
set(service: string, account: string, secret: string, label: string): Promise<void>;
|
|
10
|
+
/** Removes the item; a missing item is not an error. */
|
|
11
|
+
delete(service: string, account: string): Promise<void>;
|
|
12
|
+
}
|
|
13
|
+
export declare class SecretStoreError extends Error {
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ToolResult {
|
|
17
|
+
readonly code: number | null;
|
|
18
|
+
readonly stdout: string;
|
|
19
|
+
readonly stderr: string;
|
|
20
|
+
}
|
|
21
|
+
/** Runs a keychain tool; tests replace it. */
|
|
22
|
+
export type RunTool = (command: string, args: readonly string[], input?: string) => Promise<ToolResult>;
|
|
23
|
+
export declare const runTool: RunTool;
|
|
24
|
+
/** What a failed tool said, for an error message. */
|
|
25
|
+
export declare function toolError(result: ToolResult): string;
|
|
26
|
+
/** The tool's answer without the line break it ends with. */
|
|
27
|
+
export declare function firstValue(stdout: string): string | undefined;
|
|
28
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/lib/secrets/store.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,eAAe,GACzB,UAAU,GAAG,gBAAgB,GAAG,oBAAoB,CAAC;AAEvD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,kGAAkG;IAClG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9B,yHAAyH;IACzH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACnE,wGAAwG;IACxG,GAAG,CACD,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,wDAAwD;IACxD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IAChC,IAAI,SAAsB;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,8CAA8C;AAC9C,MAAM,MAAM,OAAO,GAAG,CACpB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,KAAK,CAAC,EAAE,MAAM,KACX,OAAO,CAAC,UAAU,CAAC,CAAC;AAKzB,eAAO,MAAM,OAAO,EAAE,OAqBlB,CAAC;AAEL,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG7D"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The system keychain behind one small interface: the macOS Keychain through `/usr/bin/security`, the Secret Service
|
|
2
|
+
// (GNOME Keyring, KWallet) through `secret-tool`, and the Windows Credential Manager through PowerShell. Calling the
|
|
3
|
+
// operating system's own tools keeps the CLI free of native modules, so its standalone tarball stays the same on every
|
|
4
|
+
// platform. A secret always travels on standard input, never in a process argument another user could read in `ps`.
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
export class SecretStoreError extends Error {
|
|
7
|
+
name = 'SecretStoreError';
|
|
8
|
+
}
|
|
9
|
+
/** A locked keychain may wait on an unlock dialog; give up after a minute rather than hang. */
|
|
10
|
+
const TOOL_TIMEOUT_MS = 60_000;
|
|
11
|
+
export const runTool = (command, args, input) => new Promise((resolve) => {
|
|
12
|
+
let stdout = '';
|
|
13
|
+
let stderr = '';
|
|
14
|
+
const child = spawn(command, [...args], {
|
|
15
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
});
|
|
18
|
+
const timer = setTimeout(() => child.kill('SIGKILL'), TOOL_TIMEOUT_MS);
|
|
19
|
+
child.stdout.on('data', (chunk) => (stdout += chunk.toString()));
|
|
20
|
+
child.stderr.on('data', (chunk) => (stderr += chunk.toString()));
|
|
21
|
+
child.on('error', (error) => {
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
resolve({ code: null, stdout, stderr: stderr || error.message });
|
|
24
|
+
});
|
|
25
|
+
child.on('close', (code) => {
|
|
26
|
+
clearTimeout(timer);
|
|
27
|
+
resolve({ code, stdout, stderr });
|
|
28
|
+
});
|
|
29
|
+
child.stdin.on('error', () => undefined);
|
|
30
|
+
child.stdin.end(input ?? '');
|
|
31
|
+
});
|
|
32
|
+
/** What a failed tool said, for an error message. */
|
|
33
|
+
export function toolError(result) {
|
|
34
|
+
return result.stderr.trim() || `exit ${String(result.code)}`;
|
|
35
|
+
}
|
|
36
|
+
/** The tool's answer without the line break it ends with. */
|
|
37
|
+
export function firstValue(stdout) {
|
|
38
|
+
const value = stdout.replace(/\r?\n$/u, '');
|
|
39
|
+
return value === '' ? undefined : value;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/lib/secrets/store.ts"],"names":[],"mappings":"AAAA,qHAAqH;AACrH,qHAAqH;AACrH,uHAAuH;AACvH,oHAAoH;AACpH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAsB3C,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,GAAG,kBAAkB,CAAC;CACpC;AAeD,+FAA+F;AAC/F,MAAM,eAAe,GAAG,MAAM,CAAC;AAE/B,MAAM,CAAC,MAAM,OAAO,GAAY,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CACvD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;IACtB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE;QACtC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;QAC/B,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,eAAe,CAAC,CAAC;IACvE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACzE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;QAC1B,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;QACzB,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IACzC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;AAC/B,CAAC,CAAC,CAAC;AAEL,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,MAAkB;IAC1C,OAAO,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,QAAQ,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RunTool, type SecretStore } from './store.ts';
|
|
2
|
+
/** Windows PowerShell by its absolute path, so nothing on PATH stands in for it. */
|
|
3
|
+
export declare function windowsPowerShell(env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/** The arguments PowerShell runs the script with. */
|
|
5
|
+
export declare const POWERSHELL_ARGS: readonly string[];
|
|
6
|
+
/** The credential's target name: one generic credential per service and account. */
|
|
7
|
+
export declare function credentialTarget(service: string, account: string): string;
|
|
8
|
+
export declare class CredentialManager implements SecretStore {
|
|
9
|
+
readonly kind: "credential-manager";
|
|
10
|
+
private readonly run;
|
|
11
|
+
private readonly powershell;
|
|
12
|
+
private readonly platform;
|
|
13
|
+
constructor(run?: RunTool, powershell?: string, platform?: NodeJS.Platform);
|
|
14
|
+
available(): Promise<boolean>;
|
|
15
|
+
get(service: string, account: string): Promise<string | undefined>;
|
|
16
|
+
set(service: string, account: string, secret: string, label: string): Promise<void>;
|
|
17
|
+
delete(service: string, account: string): Promise<void>;
|
|
18
|
+
private call;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=windows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.d.ts","sourceRoot":"","sources":["../../../src/lib/secrets/windows.ts"],"names":[],"mappings":"AAMA,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,WAAW,EACjB,MAAM,YAAY,CAAC;AAoFpB,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC/B,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAQR;AAED,qDAAqD;AACrD,eAAO,MAAM,eAAe,EAAE,SAAS,MAAM,EAO5C,CAAC;AAEF,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEzE;AAUD,qBAAa,iBAAkB,YAAW,WAAW;IACnD,QAAQ,CAAC,IAAI,EAAG,oBAAoB,CAAU;IAE9C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAU;IAC9B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;gBAGzC,GAAG,GAAE,OAAiB,EACtB,UAAU,GAAE,MAA4B,EACxC,QAAQ,GAAE,MAAM,CAAC,QAA2B;IAOxC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAM7B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAclE,GAAG,CACP,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC;IAcV,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7D,OAAO,CAAC,IAAI;CAGb"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// The Windows Credential Manager through Windows PowerShell, which compiles a few lines of C# calling `CredReadW`,
|
|
2
|
+
// `CredWriteW` and `CredDeleteW` (advapi32) when it runs: no native module to ship. The script goes in
|
|
3
|
+
// `-EncodedCommand`; the request, the secret among it, goes on standard input as JSON, base64 where it is binary.
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { runTool, SecretStoreError, toolError, } from "./store.js";
|
|
7
|
+
/** How the script exits when there is no such credential. */
|
|
8
|
+
const NOT_FOUND = 44;
|
|
9
|
+
const SCRIPT = String.raw `$ErrorActionPreference = 'Stop'
|
|
10
|
+
if (-not ('AppCli.Credentials' -as [type])) {
|
|
11
|
+
Add-Type -TypeDefinition @'
|
|
12
|
+
using System;
|
|
13
|
+
using System.ComponentModel;
|
|
14
|
+
using System.Runtime.InteropServices;
|
|
15
|
+
namespace AppCli {
|
|
16
|
+
public static class Credentials {
|
|
17
|
+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
|
18
|
+
struct CREDENTIAL {
|
|
19
|
+
public uint Flags; public uint Type; public string TargetName; public string Comment;
|
|
20
|
+
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
|
|
21
|
+
public uint CredentialBlobSize; public IntPtr CredentialBlob; public uint Persist;
|
|
22
|
+
public uint AttributeCount; public IntPtr Attributes; public string TargetAlias; public string UserName;
|
|
23
|
+
}
|
|
24
|
+
[DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
25
|
+
static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);
|
|
26
|
+
[DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
27
|
+
static extern bool CredWrite(ref CREDENTIAL credential, uint flags);
|
|
28
|
+
[DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
29
|
+
static extern bool CredDelete(string target, uint type, uint flags);
|
|
30
|
+
[DllImport("advapi32.dll")]
|
|
31
|
+
static extern void CredFree(IntPtr buffer);
|
|
32
|
+
const int ERROR_NOT_FOUND = 1168;
|
|
33
|
+
const uint CRED_TYPE_GENERIC = 1;
|
|
34
|
+
const uint CRED_PERSIST_LOCAL_MACHINE = 2;
|
|
35
|
+
public static byte[] Read(string target) {
|
|
36
|
+
IntPtr pointer;
|
|
37
|
+
if (!CredRead(target, CRED_TYPE_GENERIC, 0, out pointer)) {
|
|
38
|
+
int error = Marshal.GetLastWin32Error();
|
|
39
|
+
if (error == ERROR_NOT_FOUND) return null;
|
|
40
|
+
throw new Win32Exception(error);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
CREDENTIAL credential = (CREDENTIAL)Marshal.PtrToStructure(pointer, typeof(CREDENTIAL));
|
|
44
|
+
byte[] bytes = new byte[credential.CredentialBlobSize];
|
|
45
|
+
if (bytes.Length > 0) Marshal.Copy(credential.CredentialBlob, bytes, 0, bytes.Length);
|
|
46
|
+
return bytes;
|
|
47
|
+
} finally { CredFree(pointer); }
|
|
48
|
+
}
|
|
49
|
+
public static void Write(string target, string user, string comment, byte[] secret) {
|
|
50
|
+
CREDENTIAL credential = new CREDENTIAL();
|
|
51
|
+
credential.Type = CRED_TYPE_GENERIC; credential.TargetName = target; credential.UserName = user;
|
|
52
|
+
credential.Comment = comment; credential.Persist = CRED_PERSIST_LOCAL_MACHINE;
|
|
53
|
+
credential.CredentialBlobSize = (uint)secret.Length;
|
|
54
|
+
credential.CredentialBlob = Marshal.AllocHGlobal(secret.Length);
|
|
55
|
+
try {
|
|
56
|
+
Marshal.Copy(secret, 0, credential.CredentialBlob, secret.Length);
|
|
57
|
+
if (!CredWrite(ref credential, 0)) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
58
|
+
} finally { Marshal.FreeHGlobal(credential.CredentialBlob); }
|
|
59
|
+
}
|
|
60
|
+
public static void Delete(string target) {
|
|
61
|
+
if (!CredDelete(target, CRED_TYPE_GENERIC, 0)) {
|
|
62
|
+
int error = Marshal.GetLastWin32Error();
|
|
63
|
+
if (error != ERROR_NOT_FOUND) throw new Win32Exception(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
'@
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
72
|
+
switch ($request.op) {
|
|
73
|
+
'get' {
|
|
74
|
+
$bytes = [AppCli.Credentials]::Read($request.target)
|
|
75
|
+
if ($null -eq $bytes) { exit ${NOT_FOUND} }
|
|
76
|
+
[Console]::Out.Write([Convert]::ToBase64String($bytes))
|
|
77
|
+
}
|
|
78
|
+
'set' { [AppCli.Credentials]::Write($request.target, $request.account, $request.label, [Convert]::FromBase64String($request.secret)) }
|
|
79
|
+
'delete' { [AppCli.Credentials]::Delete($request.target) }
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
[Console]::Error.WriteLine($_.Exception.Message)
|
|
83
|
+
exit 1
|
|
84
|
+
}
|
|
85
|
+
exit 0
|
|
86
|
+
`;
|
|
87
|
+
/** Windows PowerShell by its absolute path, so nothing on PATH stands in for it. */
|
|
88
|
+
export function windowsPowerShell(env = process.env) {
|
|
89
|
+
return path.join(env.SystemRoot ?? 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
90
|
+
}
|
|
91
|
+
/** The arguments PowerShell runs the script with. */
|
|
92
|
+
export const POWERSHELL_ARGS = [
|
|
93
|
+
'-NoProfile',
|
|
94
|
+
'-NonInteractive',
|
|
95
|
+
'-ExecutionPolicy',
|
|
96
|
+
'Bypass',
|
|
97
|
+
'-EncodedCommand',
|
|
98
|
+
Buffer.from(SCRIPT, 'utf16le').toString('base64'),
|
|
99
|
+
];
|
|
100
|
+
/** The credential's target name: one generic credential per service and account. */
|
|
101
|
+
export function credentialTarget(service, account) {
|
|
102
|
+
return `${service}:${account}`;
|
|
103
|
+
}
|
|
104
|
+
export class CredentialManager {
|
|
105
|
+
kind = 'credential-manager';
|
|
106
|
+
run;
|
|
107
|
+
powershell;
|
|
108
|
+
platform;
|
|
109
|
+
constructor(run = runTool, powershell = windowsPowerShell(), platform = process.platform) {
|
|
110
|
+
this.run = run;
|
|
111
|
+
this.powershell = powershell;
|
|
112
|
+
this.platform = platform;
|
|
113
|
+
}
|
|
114
|
+
async available() {
|
|
115
|
+
if (this.platform !== 'win32' || !existsSync(this.powershell))
|
|
116
|
+
return false;
|
|
117
|
+
// Compiling the helper is what fails where scripts or Add-Type are locked down.
|
|
118
|
+
return (await this.call({ op: 'probe', target: '' })).code === 0;
|
|
119
|
+
}
|
|
120
|
+
async get(service, account) {
|
|
121
|
+
const result = await this.call({
|
|
122
|
+
op: 'get',
|
|
123
|
+
target: credentialTarget(service, account),
|
|
124
|
+
});
|
|
125
|
+
if (result.code === NOT_FOUND)
|
|
126
|
+
return undefined;
|
|
127
|
+
if (result.code !== 0)
|
|
128
|
+
throw new SecretStoreError(`Cannot read the Windows Credential Manager: ${toolError(result)}`);
|
|
129
|
+
const value = Buffer.from(result.stdout.trim(), 'base64').toString('utf8');
|
|
130
|
+
return value === '' ? undefined : value;
|
|
131
|
+
}
|
|
132
|
+
async set(service, account, secret, label) {
|
|
133
|
+
const result = await this.call({
|
|
134
|
+
op: 'set',
|
|
135
|
+
target: credentialTarget(service, account),
|
|
136
|
+
account,
|
|
137
|
+
label,
|
|
138
|
+
secret: Buffer.from(secret, 'utf8').toString('base64'),
|
|
139
|
+
});
|
|
140
|
+
if (result.code !== 0 || (await this.get(service, account)) !== secret)
|
|
141
|
+
throw new SecretStoreError(`Cannot write to the Windows Credential Manager: ${result.stderr.trim() || 'the credential did not stick'}`);
|
|
142
|
+
}
|
|
143
|
+
async delete(service, account) {
|
|
144
|
+
const result = await this.call({
|
|
145
|
+
op: 'delete',
|
|
146
|
+
target: credentialTarget(service, account),
|
|
147
|
+
});
|
|
148
|
+
if (result.code !== 0)
|
|
149
|
+
throw new SecretStoreError(`Cannot delete from the Windows Credential Manager: ${toolError(result)}`);
|
|
150
|
+
}
|
|
151
|
+
call(request) {
|
|
152
|
+
return this.run(this.powershell, POWERSHELL_ARGS, JSON.stringify(request));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=windows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"windows.js","sourceRoot":"","sources":["../../../src/lib/secrets/windows.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AppCliSession } from '../config.ts';
|
|
2
|
+
/** Starts asking; the answer is printed by `printUpdateHint`. */
|
|
3
|
+
export declare function askUpdateHint(session: AppCliSession): Promise<string | undefined>;
|
|
4
|
+
export declare function printUpdateHint(pending: Promise<string | undefined>, write?: (line: string) => void): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=update-hint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-hint.d.ts","sourceRoot":"","sources":["../../src/lib/update-hint.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAKhE,iEAAiE;AACjE,wBAAgB,aAAa,CAC3B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAQ7B;AAED,wBAAsB,eAAe,CACnC,OAAO,EAAE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,EACpC,KAAK,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAkD,GAC1E,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The application's update hint (`AppCliConfig.updateHint`, or the built-in one of `selfUpdate`): asked as the
|
|
2
|
+
// signed-in person, printed on stderr at most once per process, so `--json` keeps stdout to its one document. A hint
|
|
3
|
+
// that fails says nothing.
|
|
4
|
+
import { appCliConfig } from "../config.js";
|
|
5
|
+
import { updateEnvironment, updateHint } from "../update.js";
|
|
6
|
+
let printed = false;
|
|
7
|
+
/** Starts asking; the answer is printed by `printUpdateHint`. */
|
|
8
|
+
export function askUpdateHint(session) {
|
|
9
|
+
if (printed)
|
|
10
|
+
return Promise.resolve(undefined);
|
|
11
|
+
const config = appCliConfig();
|
|
12
|
+
if (config.updateHint !== undefined)
|
|
13
|
+
return config.updateHint(session).catch(() => undefined);
|
|
14
|
+
const environment = updateEnvironment(config);
|
|
15
|
+
if (environment === undefined)
|
|
16
|
+
return Promise.resolve(undefined);
|
|
17
|
+
return updateHint(session, environment).catch(() => undefined);
|
|
18
|
+
}
|
|
19
|
+
export async function printUpdateHint(pending, write = (line) => process.stderr.write(`${line}\n`)) {
|
|
20
|
+
const line = await pending;
|
|
21
|
+
if (line === undefined || line === '' || printed)
|
|
22
|
+
return;
|
|
23
|
+
printed = true;
|
|
24
|
+
write(line);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=update-hint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-hint.js","sourceRoot":"","sources":["../../src/lib/update-hint.ts"],"names":[],"mappings":"AAAA,+GAA+G;AAC/G,qHAAqH;AACrH,2BAA2B;AAC3B,OAAO,EAAE,YAAY,EAAsB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE7D,IAAI,OAAO,GAAG,KAAK,CAAC;AAEpB,iEAAiE;AACjE,MAAM,UAAU,aAAa,CAC3B,OAAsB;IAEtB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS;QACjC,OAAO,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3D,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACjE,OAAO,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AACjE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAoC,EACpC,QAAgC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC;IAE3E,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC;IAC3B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE,IAAI,OAAO;QAAE,OAAO;IACzD,OAAO,GAAG,IAAI,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
|
package/dist/login.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type Command } from '@oclif/core';
|
|
2
|
+
import { type AppCliConfig } from './config.ts';
|
|
3
|
+
import { type KeyStorage } from './lib/secrets/index.ts';
|
|
4
|
+
/** What `login` answers. */
|
|
5
|
+
export interface LoginResult {
|
|
6
|
+
readonly server: string;
|
|
7
|
+
readonly profile: string;
|
|
8
|
+
readonly storage: KeyStorage;
|
|
9
|
+
readonly method: 'browser' | 'api-key';
|
|
10
|
+
readonly userId?: string;
|
|
11
|
+
readonly displayName?: string;
|
|
12
|
+
}
|
|
13
|
+
/** `<bin> login`: signs in through the browser, or stores an API key given on standard input. */
|
|
14
|
+
export declare function loginCommand(app: AppCliConfig): Command.Class;
|
|
15
|
+
//# sourceMappingURL=login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"AAEA,OAAO,EAAS,KAAK,OAAO,EAAE,MAAM,aAAa,CAAC;AAIlD,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAyB7E,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC;AA4B1E,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,iGAAiG;AACjG,wBAAgB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,KAAK,CAiP7D"}
|