@share-crm/sharecrm-cli 1.1.0
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 +293 -0
- package/dist/cli/parser.js +79 -0
- package/dist/cli/root.js +63 -0
- package/dist/cli/router.js +32 -0
- package/dist/commands/auth/login.js +34 -0
- package/dist/commands/auth/logout.js +12 -0
- package/dist/commands/auth/status.js +41 -0
- package/dist/commands/auth/token.js +70 -0
- package/dist/commands/config/init.js +28 -0
- package/dist/commands/help/help.js +174 -0
- package/dist/commands/remote/execute.js +131 -0
- package/dist/core/auth/authTypes.js +2 -0
- package/dist/core/auth/deviceFlow.js +77 -0
- package/dist/core/auth/tokenManager.js +45 -0
- package/dist/core/cache/cacheTypes.js +2 -0
- package/dist/core/cache/commandCache.js +24 -0
- package/dist/core/config/authBaseUrl.js +11 -0
- package/dist/core/config/envPersistence.js +59 -0
- package/dist/core/config/interactive.js +60 -0
- package/dist/core/config/locale.js +9 -0
- package/dist/core/debug/debugOutput.js +18 -0
- package/dist/core/debug/runtimeDebug.js +19 -0
- package/dist/core/http/apiClient.js +320 -0
- package/dist/core/http/requestTypes.js +2 -0
- package/dist/core/output/errors.js +44 -0
- package/dist/core/output/stderr.js +6 -0
- package/dist/core/output/stdout.js +6 -0
- package/dist/core/state/authSessionStore.js +129 -0
- package/dist/core/state/authSessionTypes.js +2 -0
- package/dist/core/state/configStore.js +65 -0
- package/dist/core/state/fileLock.js +66 -0
- package/dist/core/state/legacySessionMigration.js +109 -0
- package/dist/core/state/paths.js +40 -0
- package/dist/core/state/secretStore/commonFileCrypto.js +61 -0
- package/dist/core/state/secretStore/index.js +28 -0
- package/dist/core/state/secretStore/secretStore.darwin.js +139 -0
- package/dist/core/state/secretStore/secretStore.linux.js +90 -0
- package/dist/core/state/secretStore/secretStore.unsupported.js +17 -0
- package/dist/core/state/secretStore/secretStore.win32.js +162 -0
- package/dist/core/state/secretStore/types.js +2 -0
- package/dist/core/state/sessionMetaStore.js +24 -0
- package/dist/core/state/sessionStore.js +23 -0
- package/dist/index.js +49 -0
- package/dist/shared/constants.js +13 -0
- package/dist/shared/env.js +69 -0
- package/dist/shared/generatedConfig.js +9 -0
- package/dist/shared/utils.js +14 -0
- package/dist/types/command.js +2 -0
- package/package.json +40 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runHelpCommand = runHelpCommand;
|
|
4
|
+
exports.renderGlobalHelp = renderGlobalHelp;
|
|
5
|
+
exports.renderManifestHelp = renderManifestHelp;
|
|
6
|
+
exports.renderCommandHelp = renderCommandHelp;
|
|
7
|
+
exports.renderExecuteHelp = renderExecuteHelp;
|
|
8
|
+
const constants_1 = require("../../shared/constants");
|
|
9
|
+
const apiClient_1 = require("../../core/http/apiClient");
|
|
10
|
+
const errors_1 = require("../../core/output/errors");
|
|
11
|
+
const stdout_1 = require("../../core/output/stdout");
|
|
12
|
+
const sessionStore_1 = require("../../core/state/sessionStore");
|
|
13
|
+
async function runHelpCommand(command, helpTarget = []) {
|
|
14
|
+
if (helpTarget.length === 0) {
|
|
15
|
+
command.outputHelp();
|
|
16
|
+
const session = await loadSessionForHelp();
|
|
17
|
+
if (!session?.accessToken) {
|
|
18
|
+
(0, stdout_1.writeStdout)('Login to view available business commands.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const manifest = await new apiClient_1.ApiClient().fetchCommandManifest(session);
|
|
22
|
+
(0, stdout_1.writeStdout)(renderGlobalHelp(manifest.commands));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const session = await loadSessionForHelp();
|
|
26
|
+
if (!session?.accessToken) {
|
|
27
|
+
(0, stdout_1.writeStdout)('Please login first to view remote command help.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const commandKey = helpTarget.join(' ');
|
|
31
|
+
try {
|
|
32
|
+
const manifest = await new apiClient_1.ApiClient().fetchCommandManifest(session, commandKey);
|
|
33
|
+
(0, stdout_1.writeStdout)(renderManifestHelp(helpTarget, manifest.commands));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
const remoteError = extractRemoteError(error);
|
|
37
|
+
if (remoteError?.help) {
|
|
38
|
+
(0, stdout_1.writeStdout)(renderExecuteHelp(remoteError.help, remoteError.commandKey ?? commandKey));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function loadSessionForHelp() {
|
|
45
|
+
try {
|
|
46
|
+
return await new sessionStore_1.SessionStore().load();
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (error instanceof errors_1.CliError && error.message === 'Failed to read session file.') {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function renderGlobalHelp(commands) {
|
|
56
|
+
if (commands.length === 0) {
|
|
57
|
+
return 'No business commands available.';
|
|
58
|
+
}
|
|
59
|
+
return [
|
|
60
|
+
renderUsageBlock('sharecrm <command>'),
|
|
61
|
+
renderCommandList(commands),
|
|
62
|
+
].join('\n\n');
|
|
63
|
+
}
|
|
64
|
+
function renderManifestHelp(helpTarget, commands) {
|
|
65
|
+
if (commands.length === 0) {
|
|
66
|
+
return 'Command not found.';
|
|
67
|
+
}
|
|
68
|
+
if (shouldRenderCommandList(helpTarget, commands)) {
|
|
69
|
+
return [
|
|
70
|
+
renderUsageBlock(`sharecrm ${helpTarget.join(' ')} <command>`),
|
|
71
|
+
renderCommandList(commands, helpTarget.join(' ')),
|
|
72
|
+
].join('\n\n');
|
|
73
|
+
}
|
|
74
|
+
return renderCommandHelp(commands);
|
|
75
|
+
}
|
|
76
|
+
function renderCommandHelp(commands) {
|
|
77
|
+
if (commands.length === 0) {
|
|
78
|
+
return 'Command not found.';
|
|
79
|
+
}
|
|
80
|
+
const [current, ...subCommands] = commands;
|
|
81
|
+
const lines = [
|
|
82
|
+
current.description ?? current.summary ?? current.commandKey,
|
|
83
|
+
'',
|
|
84
|
+
renderUsageBlock(resolveUsage(current.commandKey, current.usage)),
|
|
85
|
+
];
|
|
86
|
+
if (subCommands.length > 0) {
|
|
87
|
+
lines.push('', renderCommandList(subCommands, current.commandKey));
|
|
88
|
+
}
|
|
89
|
+
const properties = current.argumentsSchema && 'properties' in current.argumentsSchema
|
|
90
|
+
? current.argumentsSchema.properties
|
|
91
|
+
: undefined;
|
|
92
|
+
if (properties && Object.keys(properties).length > 0) {
|
|
93
|
+
lines.push('', 'Flags:');
|
|
94
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
95
|
+
lines.push(formatFlagRow(value.title ?? key, value.type ?? 'unknown', value.description ?? ''));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
function renderExecuteHelp(content, commandKey) {
|
|
101
|
+
const normalizedCommandKey = commandKey.trim();
|
|
102
|
+
const schemaCommandKey = normalizedCommandKey || content.apiName || content.command || 'unknown';
|
|
103
|
+
const helpCommandKey = content.command
|
|
104
|
+
? normalizedCommandKey
|
|
105
|
+
: schemaCommandKey;
|
|
106
|
+
return renderCommandHelp([
|
|
107
|
+
{
|
|
108
|
+
commandKey: helpCommandKey,
|
|
109
|
+
description: content.description,
|
|
110
|
+
usage: content.usage,
|
|
111
|
+
argumentsSchema: content.input ?? null,
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
114
|
+
}
|
|
115
|
+
function extractRemoteError(error) {
|
|
116
|
+
if (!(error instanceof errors_1.CliError)) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
const remoteError = error.details?.remoteError;
|
|
120
|
+
if (!remoteError || typeof remoteError !== 'object') {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return remoteError;
|
|
124
|
+
}
|
|
125
|
+
function shouldRenderCommandList(helpTarget, commands) {
|
|
126
|
+
if (commands.length <= 1) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const target = helpTarget.join(' ').trim();
|
|
130
|
+
if (!target) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return commands.every((command) => command.commandKey.length > target.length && command.commandKey.startsWith(`${target} `));
|
|
134
|
+
}
|
|
135
|
+
function renderUsageBlock(usage) {
|
|
136
|
+
return ['Usage:', ` ${usage}`].join('\n');
|
|
137
|
+
}
|
|
138
|
+
function renderCommandList(commands, parentCommandKey) {
|
|
139
|
+
const lines = ['Available Commands:'];
|
|
140
|
+
for (const command of commands) {
|
|
141
|
+
const relativeCommand = parentCommandKey && command.commandKey.startsWith(`${parentCommandKey} `)
|
|
142
|
+
? command.commandKey.slice(parentCommandKey.length).trim()
|
|
143
|
+
: command.commandKey;
|
|
144
|
+
lines.push(formatCommandRow(relativeCommand, command.description ?? command.summary ?? ''));
|
|
145
|
+
}
|
|
146
|
+
return lines.join('\n');
|
|
147
|
+
}
|
|
148
|
+
function resolveUsage(commandKey, usage) {
|
|
149
|
+
const normalizedUsage = normalizeUsage(usage ?? commandKey);
|
|
150
|
+
if (!usage) {
|
|
151
|
+
return normalizedUsage;
|
|
152
|
+
}
|
|
153
|
+
const withoutCliPrefix = normalizedUsage.replace(/^cli\s+/, '');
|
|
154
|
+
const commandKeyParts = commandKey.trim().split(/\s+/).filter(Boolean);
|
|
155
|
+
const usageParts = withoutCliPrefix.split(/\s+/).filter(Boolean);
|
|
156
|
+
if (usageParts.length === 0) {
|
|
157
|
+
return `${constants_1.CLI_NAME} ${commandKey}`;
|
|
158
|
+
}
|
|
159
|
+
const remainder = usageParts.slice(1).join(' ');
|
|
160
|
+
if (usageParts[0] === commandKeyParts[commandKeyParts.length - 1] && commandKeyParts.length > 1) {
|
|
161
|
+
return `${constants_1.CLI_NAME} ${commandKey}${remainder ? ` ${remainder}` : ''}`;
|
|
162
|
+
}
|
|
163
|
+
return normalizedUsage;
|
|
164
|
+
}
|
|
165
|
+
function normalizeUsage(usage) {
|
|
166
|
+
return usage.replace(/^share-cli\b/, `${constants_1.CLI_NAME}`);
|
|
167
|
+
}
|
|
168
|
+
function formatCommandRow(command, description) {
|
|
169
|
+
return ` ${command.padEnd(18)} ${description}`.trimEnd();
|
|
170
|
+
}
|
|
171
|
+
function formatFlagRow(name, type, description) {
|
|
172
|
+
const header = `${name} ${type}`.trim();
|
|
173
|
+
return ` ${header.padEnd(24)} ${description}`.trimEnd();
|
|
174
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runRemoteCommand = runRemoteCommand;
|
|
4
|
+
const apiClient_1 = require("../../core/http/apiClient");
|
|
5
|
+
const errors_1 = require("../../core/output/errors");
|
|
6
|
+
const stdout_1 = require("../../core/output/stdout");
|
|
7
|
+
const sessionStore_1 = require("../../core/state/sessionStore");
|
|
8
|
+
const help_1 = require("../help/help");
|
|
9
|
+
const constants_1 = require("../../shared/constants");
|
|
10
|
+
async function runRemoteCommand(commandKey, arg, mode) {
|
|
11
|
+
const session = await new sessionStore_1.SessionStore().load();
|
|
12
|
+
if (!session?.accessToken) {
|
|
13
|
+
throw new errors_1.AuthenticationRequiredCliError();
|
|
14
|
+
}
|
|
15
|
+
const isHelpFlag = mode === 'help';
|
|
16
|
+
//console.log('arg:' + arg + ',commandKey:' + commandKey + ' ,isHelpFlag:' + mode);
|
|
17
|
+
const argumentsPayload = parseArguments(arg);
|
|
18
|
+
let result;
|
|
19
|
+
try {
|
|
20
|
+
result = await new apiClient_1.ApiClient().executeRemoteCommand(session, commandKey, argumentsPayload);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const remoteError = extractRemoteError(error);
|
|
24
|
+
if (remoteError) {
|
|
25
|
+
(0, stdout_1.writeStdout)(formatRemoteErrorSummary(remoteError, isHelpFlag));
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
if (result.helpInfoReturned) {
|
|
31
|
+
(0, stdout_1.writeStdout)((0, help_1.renderExecuteHelp)(result.content, result.commandKey));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (result.errorMessage) {
|
|
35
|
+
throw new errors_1.CliError(result.errorMessage, 1, { commandKey: result.commandKey });
|
|
36
|
+
}
|
|
37
|
+
(0, stdout_1.writeStdout)(formatExecuteResult(result));
|
|
38
|
+
}
|
|
39
|
+
function extractRemoteError(error) {
|
|
40
|
+
if (!(error instanceof errors_1.CliError)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const remoteError = error.details?.remoteError;
|
|
44
|
+
if (!remoteError || typeof remoteError !== 'object') {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return remoteError;
|
|
48
|
+
}
|
|
49
|
+
function formatRemoteErrorSummary(remoteError, isHelpFlag = false) {
|
|
50
|
+
const lines = [];
|
|
51
|
+
if (!isHelpFlag) {
|
|
52
|
+
if (remoteError.commandKey) {
|
|
53
|
+
lines.push(`\ncommand:${constants_1.CLI_NAME} ${remoteError.commandKey}`);
|
|
54
|
+
}
|
|
55
|
+
if (remoteError.errorCode) {
|
|
56
|
+
lines.push(`errorCode:${remoteError.errorCode}`);
|
|
57
|
+
}
|
|
58
|
+
if (remoteError.errorMessage) {
|
|
59
|
+
lines.push(`errorMsg:${remoteError.errorMessage}`);
|
|
60
|
+
}
|
|
61
|
+
if (remoteError.hint) {
|
|
62
|
+
lines.push(`Tips:${remoteError.hint.replace(/^share-cli\b/, constants_1.CLI_NAME)} view the command help info`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const summary = lines.join('\n');
|
|
66
|
+
const helpText = remoteError.help ? formatRemoteErrorHelp(remoteError.help, remoteError.commandKey) : '';
|
|
67
|
+
if (!helpText) {
|
|
68
|
+
return summary;
|
|
69
|
+
}
|
|
70
|
+
if (!summary) {
|
|
71
|
+
return helpText;
|
|
72
|
+
}
|
|
73
|
+
return `${summary}\n\n${helpText}`;
|
|
74
|
+
}
|
|
75
|
+
function formatRemoteErrorHelp(help, commandKey) {
|
|
76
|
+
if (help.hasSubCommands === false && help.input) {
|
|
77
|
+
return (0, help_1.renderExecuteHelp)(help, commandKey ?? help.command ?? help.apiName ?? '');
|
|
78
|
+
}
|
|
79
|
+
const lines = [];
|
|
80
|
+
if (help.description) {
|
|
81
|
+
lines.push(help.description.replace(/^Share CLI\b/g, constants_1.CLI_NAME));
|
|
82
|
+
}
|
|
83
|
+
if (help.usage) {
|
|
84
|
+
lines.push('', 'Usage:', ` ${help.usage.replaceAll(/\bshare-cli\b/g, constants_1.CLI_NAME).replace('--userInput', '--data').replace('--query', '--data')}`);
|
|
85
|
+
}
|
|
86
|
+
if (help.subCommands?.length) {
|
|
87
|
+
lines.push('', 'Commands:');
|
|
88
|
+
for (const subCommand of help.subCommands) {
|
|
89
|
+
const command = subCommand.command ?? '';
|
|
90
|
+
const description = subCommand.description ?? '';
|
|
91
|
+
lines.push(` ${command.padEnd(21, ' ')}${description}`.trimEnd());
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n');
|
|
95
|
+
}
|
|
96
|
+
function parseArguments(arg) {
|
|
97
|
+
if (!arg) {
|
|
98
|
+
throw new errors_1.CliError('The -d option requires a JSON object payload.', 1);
|
|
99
|
+
}
|
|
100
|
+
let parsed;
|
|
101
|
+
try {
|
|
102
|
+
parsed = JSON.parse(arg);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
throw new errors_1.CliError('The -d option must be valid JSON.', 1);
|
|
106
|
+
}
|
|
107
|
+
if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
|
|
108
|
+
throw new errors_1.CliError('The -d option must be a JSON object.', 1);
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
function formatExecuteResult(result) {
|
|
113
|
+
switch (result.outputType) {
|
|
114
|
+
case 'text':
|
|
115
|
+
case 'markdown':
|
|
116
|
+
return typeof result.content === 'string' ? result.content : stringifyValue(result.content);
|
|
117
|
+
case 'json':
|
|
118
|
+
return JSON.stringify(result.content ?? null, null, 2);
|
|
119
|
+
default:
|
|
120
|
+
return stringifyValue(result.content);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function stringifyValue(content) {
|
|
124
|
+
if (content === null || content === undefined) {
|
|
125
|
+
return 'null';
|
|
126
|
+
}
|
|
127
|
+
if (typeof content === 'string') {
|
|
128
|
+
return content;
|
|
129
|
+
}
|
|
130
|
+
return JSON.stringify(content, null, 2);
|
|
131
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toScope = toScope;
|
|
4
|
+
exports.requestDeviceCode = requestDeviceCode;
|
|
5
|
+
exports.pollDeviceAuthorization = pollDeviceAuthorization;
|
|
6
|
+
exports.refreshAccessToken = refreshAccessToken;
|
|
7
|
+
const errors_1 = require("../output/errors");
|
|
8
|
+
const apiClient_1 = require("../http/apiClient");
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
function toScope(scope) {
|
|
13
|
+
return scope?.split(/\s+/).map((value) => value.trim()).filter(Boolean) ?? [];
|
|
14
|
+
}
|
|
15
|
+
function toSessionRecord(response, existing) {
|
|
16
|
+
return {
|
|
17
|
+
accessToken: response.accessToken,
|
|
18
|
+
refreshToken: response.refreshToken ?? existing?.refreshToken,
|
|
19
|
+
tokenExpireAt: Date.now() + response.expiresIn * 1000,
|
|
20
|
+
scope: toScope(response.scope),
|
|
21
|
+
userId: response.userId ?? existing?.userId,
|
|
22
|
+
appId: response.appId ?? existing?.appId,
|
|
23
|
+
apiUrl: response.apiUrl ?? existing?.apiUrl,
|
|
24
|
+
grantedAt: existing?.grantedAt,
|
|
25
|
+
identity: existing?.identity,
|
|
26
|
+
userName: response.userName ?? existing?.userName,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
async function requestDeviceCode(clientId, apiClient = new apiClient_1.ApiClient()) {
|
|
30
|
+
void clientId;
|
|
31
|
+
const response = await apiClient.requestDeviceCode();
|
|
32
|
+
return {
|
|
33
|
+
deviceCode: response.deviceCode,
|
|
34
|
+
userCode: response.userCode,
|
|
35
|
+
verificationUrl: response.verificationUrl,
|
|
36
|
+
interval: response.interval,
|
|
37
|
+
expiresIn: response.expireIn,
|
|
38
|
+
traceId: response.traceId,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
async function pollDeviceAuthorization(deviceCode, intervalSeconds = 5, apiClient = new apiClient_1.ApiClient(), { maxAttempts = 600 } = {}) {
|
|
42
|
+
let intervalMs = intervalSeconds * 1000;
|
|
43
|
+
let attempts = 0;
|
|
44
|
+
while (attempts < maxAttempts) {
|
|
45
|
+
attempts++;
|
|
46
|
+
try {
|
|
47
|
+
const response = await apiClient.requestDeviceToken(deviceCode);
|
|
48
|
+
return toSessionRecord(response);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (!(error instanceof errors_1.ApiResponseCliError)) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
if (error.errorCode === 40001) {
|
|
55
|
+
await sleep(intervalMs);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (error.errorCode === 40002) {
|
|
59
|
+
intervalMs += 5_000;
|
|
60
|
+
await sleep(intervalMs);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (error.errorCode === 40003) {
|
|
64
|
+
throw new errors_1.CliError('Device code expired. Please run auth login again.', 1, error.details);
|
|
65
|
+
}
|
|
66
|
+
if (error.errorCode === 40004) {
|
|
67
|
+
throw new errors_1.CliError('Authorization was denied. Please try again after granting access.', 1, error.details);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new errors_1.CliError('设备授权超时,请重新尝试登录。', 1);
|
|
73
|
+
}
|
|
74
|
+
async function refreshAccessToken(refreshToken, appId, apiClient = new apiClient_1.ApiClient(), existing) {
|
|
75
|
+
const response = await apiClient.refreshToken(refreshToken, appId);
|
|
76
|
+
return toSessionRecord(response, existing);
|
|
77
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TokenManager = exports.SessionRefreshPersistenceError = void 0;
|
|
4
|
+
const deviceFlow_1 = require("./deviceFlow");
|
|
5
|
+
const sessionStore_1 = require("../state/sessionStore");
|
|
6
|
+
class SessionRefreshPersistenceError extends Error {
|
|
7
|
+
session;
|
|
8
|
+
constructor(session, options) {
|
|
9
|
+
super('Failed to persist refreshed session.');
|
|
10
|
+
this.session = session;
|
|
11
|
+
this.name = 'SessionRefreshPersistenceError';
|
|
12
|
+
if (options?.cause !== undefined) {
|
|
13
|
+
this.cause = options.cause;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
exports.SessionRefreshPersistenceError = SessionRefreshPersistenceError;
|
|
18
|
+
class TokenManager {
|
|
19
|
+
isExpired(session) {
|
|
20
|
+
if (!session?.tokenExpireAt) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return session.tokenExpireAt <= Date.now();
|
|
24
|
+
}
|
|
25
|
+
shouldRefresh(session, skewMs = 60_000) {
|
|
26
|
+
if (!session?.tokenExpireAt || !session.refreshToken) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return session.tokenExpireAt <= Date.now() + skewMs;
|
|
30
|
+
}
|
|
31
|
+
async refreshSession(session) {
|
|
32
|
+
if (!session.refreshToken || !session.appId) {
|
|
33
|
+
return session;
|
|
34
|
+
}
|
|
35
|
+
const refreshed = await (0, deviceFlow_1.refreshAccessToken)(session.refreshToken, session.appId, undefined, session);
|
|
36
|
+
try {
|
|
37
|
+
await new sessionStore_1.SessionStore().save(refreshed);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
throw new SessionRefreshPersistenceError(refreshed, { cause: error });
|
|
41
|
+
}
|
|
42
|
+
return refreshed;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.TokenManager = TokenManager;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommandCache = void 0;
|
|
4
|
+
const constants_1 = require("../../shared/constants");
|
|
5
|
+
const utils_1 = require("../../shared/utils");
|
|
6
|
+
class CommandCache {
|
|
7
|
+
record = null;
|
|
8
|
+
get() {
|
|
9
|
+
return this.record;
|
|
10
|
+
}
|
|
11
|
+
set(commands, ttlSeconds = constants_1.DEFAULT_COMMAND_CACHE_TTL_SECONDS) {
|
|
12
|
+
const expireAt = Date.now() + ttlSeconds * 1000;
|
|
13
|
+
this.record = {
|
|
14
|
+
fetchedAt: (0, utils_1.toIsoTimestamp)(),
|
|
15
|
+
expireAt,
|
|
16
|
+
commands: [...commands],
|
|
17
|
+
};
|
|
18
|
+
return this.record;
|
|
19
|
+
}
|
|
20
|
+
has(commandKey) {
|
|
21
|
+
return Boolean(this.record?.commands.includes(commandKey));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.CommandCache = CommandCache;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeAuthBaseUrl = normalizeAuthBaseUrl;
|
|
4
|
+
const errors_1 = require("../output/errors");
|
|
5
|
+
function normalizeAuthBaseUrl(value) {
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
if (!trimmed.startsWith('https://')) {
|
|
8
|
+
throw new errors_1.CliError('FS_CLI_AUTH_BASE_URL 必须使用 HTTPS 协议。', 1);
|
|
9
|
+
}
|
|
10
|
+
return trimmed.endsWith('/') ? trimmed : `${trimmed}/`;
|
|
11
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.persistAuthBaseUrl = persistAuthBaseUrl;
|
|
7
|
+
exports.setWindowsUserEnvironmentVariable = setWindowsUserEnvironmentVariable;
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_util_1 = require("node:util");
|
|
10
|
+
const promises_1 = require("node:fs/promises");
|
|
11
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const errors_1 = require("../output/errors");
|
|
14
|
+
function resolveShellProfilePath(homedirFn, platform, env) {
|
|
15
|
+
if (platform === 'win32') {
|
|
16
|
+
throw new errors_1.CliError('Use setWindowsUserEnvironmentVariable for Windows.', 1);
|
|
17
|
+
}
|
|
18
|
+
const shell = env.SHELL ?? '/bin/sh';
|
|
19
|
+
const home = homedirFn();
|
|
20
|
+
if (shell.includes('zsh')) {
|
|
21
|
+
return node_path_1.default.join(home, '.zshrc');
|
|
22
|
+
}
|
|
23
|
+
if (shell.includes('bash')) {
|
|
24
|
+
return node_path_1.default.join(home, '.bashrc');
|
|
25
|
+
}
|
|
26
|
+
return node_path_1.default.join(home, '.bashrc');
|
|
27
|
+
}
|
|
28
|
+
function upsertShellExport(content, envName, newLine) {
|
|
29
|
+
const lines = content.split('\n');
|
|
30
|
+
const existingIndex = lines.findIndex((line) => line.startsWith(`export ${envName}=`));
|
|
31
|
+
if (existingIndex >= 0) {
|
|
32
|
+
lines[existingIndex] = newLine;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
lines.push(newLine);
|
|
36
|
+
}
|
|
37
|
+
return lines.join('\n');
|
|
38
|
+
}
|
|
39
|
+
async function persistAuthBaseUrl(value, opts = {}) {
|
|
40
|
+
const platform = opts.platform ?? process.platform;
|
|
41
|
+
const env = opts.env ?? process.env;
|
|
42
|
+
const homedirFn = opts.homedirFn ?? (() => node_os_1.default.homedir());
|
|
43
|
+
if (platform === 'win32') {
|
|
44
|
+
await setWindowsUserEnvironmentVariable('FS_CLI_AUTH_BASE_URL', value, opts.execFile ?? (0, node_util_1.promisify)(node_child_process_1.execFile));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const profilePath = resolveShellProfilePath(homedirFn, platform, env);
|
|
48
|
+
const line = `export FS_CLI_AUTH_BASE_URL="${value}"`;
|
|
49
|
+
const content = await (0, promises_1.readFile)(profilePath, 'utf8').catch(() => '');
|
|
50
|
+
const next = upsertShellExport(content, 'FS_CLI_AUTH_BASE_URL', line);
|
|
51
|
+
await (0, promises_1.writeFile)(profilePath, next, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
async function setWindowsUserEnvironmentVariable(name, value, execFile) {
|
|
54
|
+
const quotedValue = `"${value}"`;
|
|
55
|
+
const { stderr } = await execFile('setx', [name, quotedValue]);
|
|
56
|
+
if (stderr) {
|
|
57
|
+
throw new errors_1.CliError(`setx stderr: ${stderr}`, 1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.promptText = promptText;
|
|
7
|
+
exports.readStdinText = readStdinText;
|
|
8
|
+
exports.promptSelect = promptSelect;
|
|
9
|
+
const promises_1 = __importDefault(require("node:readline/promises"));
|
|
10
|
+
const node_events_1 = require("node:events");
|
|
11
|
+
const node_process_1 = require("node:process");
|
|
12
|
+
const errors_1 = require("../output/errors");
|
|
13
|
+
async function promptText(message) {
|
|
14
|
+
const rl = promises_1.default.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout });
|
|
15
|
+
try {
|
|
16
|
+
return (await rl.question(message)).trim();
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
rl.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function readStdinText(message) {
|
|
23
|
+
node_process_1.stdout.write(message);
|
|
24
|
+
if (node_process_1.stdin.readableEnded) {
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
if (node_process_1.stdin.isTTY) {
|
|
28
|
+
const rl = promises_1.default.createInterface({ input: node_process_1.stdin, output: node_process_1.stdout, terminal: false });
|
|
29
|
+
try {
|
|
30
|
+
const line = await rl.question('');
|
|
31
|
+
return line.trim();
|
|
32
|
+
}
|
|
33
|
+
finally {
|
|
34
|
+
rl.close();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
node_process_1.stdin.setEncoding('utf8');
|
|
38
|
+
let buffer = '';
|
|
39
|
+
node_process_1.stdin.resume();
|
|
40
|
+
node_process_1.stdin.on('data', onData);
|
|
41
|
+
try {
|
|
42
|
+
await (0, node_events_1.once)(node_process_1.stdin, 'end');
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
node_process_1.stdin.off('data', onData);
|
|
46
|
+
}
|
|
47
|
+
return buffer.trim();
|
|
48
|
+
function onData(chunk) {
|
|
49
|
+
buffer += chunk.toString();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function promptSelect(message, options) {
|
|
53
|
+
const rendered = options.map((option, index) => `${index + 1}. ${option.label}`).join('\n');
|
|
54
|
+
const answer = await promptText(`${message}\n${rendered}\n请输入序号: `);
|
|
55
|
+
const picked = options[Number(answer) - 1];
|
|
56
|
+
if (!picked) {
|
|
57
|
+
throw new errors_1.CliError('无效的选项,请重新执行 config init。', 1);
|
|
58
|
+
}
|
|
59
|
+
return picked.value;
|
|
60
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_LOCALE = exports.SUPPORTED_LOCALES = void 0;
|
|
4
|
+
exports.isCliLocale = isCliLocale;
|
|
5
|
+
exports.SUPPORTED_LOCALES = ['zh-CN', 'en', 'zh-TW'];
|
|
6
|
+
exports.DEFAULT_LOCALE = 'zh-CN';
|
|
7
|
+
function isCliLocale(value) {
|
|
8
|
+
return exports.SUPPORTED_LOCALES.includes(value);
|
|
9
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeDebugRequest = writeDebugRequest;
|
|
4
|
+
exports.writeDebugResponse = writeDebugResponse;
|
|
5
|
+
exports.writeDebugException = writeDebugException;
|
|
6
|
+
const stderr_1 = require("../output/stderr");
|
|
7
|
+
function writeDebugBlock(label, payload) {
|
|
8
|
+
(0, stderr_1.writeStderr)(`[debug] ${label} ${JSON.stringify(payload, null, 2)}`);
|
|
9
|
+
}
|
|
10
|
+
function writeDebugRequest(payload) {
|
|
11
|
+
writeDebugBlock('request', payload);
|
|
12
|
+
}
|
|
13
|
+
function writeDebugResponse(payload) {
|
|
14
|
+
writeDebugBlock('response', payload);
|
|
15
|
+
}
|
|
16
|
+
function writeDebugException(payload) {
|
|
17
|
+
writeDebugBlock('exception', payload);
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Process-local debug state primitive.
|
|
4
|
+
* Used to control global debug logging across the CLI.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.setDebugEnabled = setDebugEnabled;
|
|
8
|
+
exports.isDebugEnabled = isDebugEnabled;
|
|
9
|
+
exports.resetDebugState = resetDebugState;
|
|
10
|
+
let debugEnabled = false;
|
|
11
|
+
function setDebugEnabled(value) {
|
|
12
|
+
debugEnabled = value;
|
|
13
|
+
}
|
|
14
|
+
function isDebugEnabled() {
|
|
15
|
+
return debugEnabled;
|
|
16
|
+
}
|
|
17
|
+
function resetDebugState() {
|
|
18
|
+
debugEnabled = false;
|
|
19
|
+
}
|