@revoengine/cli 1.0.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 +211 -0
- package/dist/bin/revo.d.ts +2 -0
- package/dist/bin/revo.js +19 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +213 -0
- package/dist/src/client.d.ts +72 -0
- package/dist/src/client.js +315 -0
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/auth.js +131 -0
- package/dist/src/commands/component.d.ts +2 -0
- package/dist/src/commands/component.js +905 -0
- package/dist/src/commands/endpoints.d.ts +2 -0
- package/dist/src/commands/endpoints.js +4 -0
- package/dist/src/commands/index.d.ts +7 -0
- package/dist/src/commands/index.js +7 -0
- package/dist/src/commands/info.d.ts +2 -0
- package/dist/src/commands/info.js +6 -0
- package/dist/src/commands/project.d.ts +2 -0
- package/dist/src/commands/project.js +80 -0
- package/dist/src/commands/request.d.ts +2 -0
- package/dist/src/commands/request.js +59 -0
- package/dist/src/commands/search.d.ts +2 -0
- package/dist/src/commands/search.js +22 -0
- package/dist/src/config.d.ts +54 -0
- package/dist/src/config.js +356 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +4 -0
- package/dist/src/legacy.d.ts +8 -0
- package/dist/src/legacy.js +88 -0
- package/dist/src/project.d.ts +102 -0
- package/dist/src/project.js +475 -0
- package/dist/src/prompt.d.ts +4 -0
- package/dist/src/prompt.js +64 -0
- package/dist/src/runtime-view.d.ts +17 -0
- package/dist/src/runtime-view.js +80 -0
- package/dist/src/spinner.d.ts +14 -0
- package/dist/src/spinner.js +46 -0
- package/dist/src/types.d.ts +36 -0
- package/dist/src/types.js +1 -0
- package/dist/src/ui.d.ts +26 -0
- package/dist/src/ui.js +182 -0
- package/dist/src/utils.d.ts +10 -0
- package/dist/src/utils.js +86 -0
- package/package.json +32 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
export function isInteractiveTerminal() {
|
|
3
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
4
|
+
}
|
|
5
|
+
export async function promptText(message, defaultValue = '') {
|
|
6
|
+
if (!isInteractiveTerminal()) {
|
|
7
|
+
return defaultValue;
|
|
8
|
+
}
|
|
9
|
+
const rl = readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
14
|
+
try {
|
|
15
|
+
const answer = await rl.question(`${message}${suffix}: `);
|
|
16
|
+
return answer.trim() || defaultValue;
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
rl.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export async function promptSecret(message) {
|
|
23
|
+
if (!isInteractiveTerminal()) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const rl = readline.createInterface({
|
|
27
|
+
input: process.stdin,
|
|
28
|
+
output: process.stdout,
|
|
29
|
+
terminal: true,
|
|
30
|
+
});
|
|
31
|
+
let muted = true;
|
|
32
|
+
const originalWriteToOutput = rl._writeToOutput.bind(rl);
|
|
33
|
+
rl._writeToOutput = (value) => {
|
|
34
|
+
if (!muted) {
|
|
35
|
+
originalWriteToOutput(value);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (value.startsWith(`${message}: `)) {
|
|
39
|
+
originalWriteToOutput(value);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Preserve terminal editing/paste behavior while masking visible content.
|
|
43
|
+
originalWriteToOutput('*');
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const answer = await rl.question(`${message}: `);
|
|
47
|
+
return answer;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
muted = false;
|
|
51
|
+
rl.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export async function promptConfirm(message, defaultValue = false) {
|
|
55
|
+
if (!isInteractiveTerminal()) {
|
|
56
|
+
return defaultValue;
|
|
57
|
+
}
|
|
58
|
+
const hint = defaultValue ? 'Y/n' : 'y/N';
|
|
59
|
+
const answer = (await promptText(`${message} (${hint})`)).trim().toLowerCase();
|
|
60
|
+
if (!answer) {
|
|
61
|
+
return defaultValue;
|
|
62
|
+
}
|
|
63
|
+
return answer === 'y' || answer === 'yes';
|
|
64
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolveRuntimeConfig } from './config.ts';
|
|
2
|
+
import type { CommandContext } from './types.ts';
|
|
3
|
+
export type AuthViewState = {
|
|
4
|
+
status: 'authenticated' | 'not_authenticated' | 'unable_to_validate';
|
|
5
|
+
validation: string | null;
|
|
6
|
+
email: string | null;
|
|
7
|
+
};
|
|
8
|
+
export type RuntimeViewModel = {
|
|
9
|
+
packageVersion: string;
|
|
10
|
+
nodeVersion: string;
|
|
11
|
+
configDir: string;
|
|
12
|
+
runtime: ReturnType<typeof resolveRuntimeConfig>;
|
|
13
|
+
auth: AuthViewState;
|
|
14
|
+
};
|
|
15
|
+
export declare function buildRuntimeViewModel(context: CommandContext, options?: {
|
|
16
|
+
forceValidation?: boolean;
|
|
17
|
+
}): Promise<RuntimeViewModel>;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { getConfigDir, resolveRuntimeConfig } from "./config.js";
|
|
2
|
+
import { AuthenticationError } from "./client.js";
|
|
3
|
+
function resolveArgsRuntime(context) {
|
|
4
|
+
return resolveRuntimeConfig({
|
|
5
|
+
baseUrl: typeof context.args.url === 'string'
|
|
6
|
+
? context.args.url
|
|
7
|
+
: typeof context.args.baseUrl === 'string'
|
|
8
|
+
? context.args.baseUrl
|
|
9
|
+
: undefined,
|
|
10
|
+
instance: typeof context.args.instance === 'string'
|
|
11
|
+
? context.args.instance
|
|
12
|
+
: typeof context.args.i === 'string'
|
|
13
|
+
? context.args.i
|
|
14
|
+
: undefined,
|
|
15
|
+
token: typeof context.args.token === 'string'
|
|
16
|
+
? context.args.token
|
|
17
|
+
: typeof context.args.t === 'string'
|
|
18
|
+
? context.args.t
|
|
19
|
+
: undefined,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function extractEmail(profile) {
|
|
23
|
+
if (!profile || typeof profile !== 'object') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const candidate = profile;
|
|
27
|
+
const direct = ['email', 'mail', 'userEmail', 'login'];
|
|
28
|
+
for (const key of direct) {
|
|
29
|
+
if (typeof candidate[key] === 'string' && candidate[key]) {
|
|
30
|
+
return candidate[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const nestedKeys = ['user', 'data', 'profile'];
|
|
34
|
+
for (const key of nestedKeys) {
|
|
35
|
+
const nested = candidate[key];
|
|
36
|
+
if (nested && typeof nested === 'object') {
|
|
37
|
+
const nestedEmail = extractEmail(nested);
|
|
38
|
+
if (nestedEmail) {
|
|
39
|
+
return nestedEmail;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
export async function buildRuntimeViewModel(context, options = {}) {
|
|
46
|
+
const runtime = resolveArgsRuntime(context);
|
|
47
|
+
const auth = {
|
|
48
|
+
status: 'not_authenticated',
|
|
49
|
+
validation: null,
|
|
50
|
+
email: null,
|
|
51
|
+
};
|
|
52
|
+
if (runtime.token && runtime.instance) {
|
|
53
|
+
context.client.baseUrl = runtime.baseUrl;
|
|
54
|
+
context.client.instance = runtime.instance;
|
|
55
|
+
context.client.token = runtime.token;
|
|
56
|
+
try {
|
|
57
|
+
const profile = await context.client.me({ force: options.forceValidation });
|
|
58
|
+
auth.status = 'authenticated';
|
|
59
|
+
auth.validation = null;
|
|
60
|
+
auth.email = extractEmail(profile);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (error instanceof AuthenticationError) {
|
|
64
|
+
auth.status = 'not_authenticated';
|
|
65
|
+
auth.validation = error.message;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
auth.status = 'unable_to_validate';
|
|
69
|
+
auth.validation = error instanceof Error ? error.message : String(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
packageVersion: context.packageVersion,
|
|
75
|
+
nodeVersion: process.version,
|
|
76
|
+
configDir: getConfigDir(),
|
|
77
|
+
runtime,
|
|
78
|
+
auth,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
type SpinnerStream = {
|
|
2
|
+
isTTY?: boolean;
|
|
3
|
+
write: (chunk: string) => boolean;
|
|
4
|
+
};
|
|
5
|
+
type SpinnerOptions = {
|
|
6
|
+
text: string;
|
|
7
|
+
stream?: SpinnerStream;
|
|
8
|
+
intervalMs?: number;
|
|
9
|
+
};
|
|
10
|
+
export declare function createSpinner(options: SpinnerOptions): {
|
|
11
|
+
start(): void;
|
|
12
|
+
stop(): void;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const FRAMES = ['|', '/', '-', '\\'];
|
|
2
|
+
const ANSI = {
|
|
3
|
+
reset: '\u001b[0m',
|
|
4
|
+
cyan: '\u001b[36m',
|
|
5
|
+
dim: '\u001b[2m',
|
|
6
|
+
};
|
|
7
|
+
function supportsSpinner(stream) {
|
|
8
|
+
return Boolean(stream.isTTY || process.env.FORCE_COLOR);
|
|
9
|
+
}
|
|
10
|
+
function paint(text, code, stream) {
|
|
11
|
+
return supportsSpinner(stream) ? `${code}${text}${ANSI.reset}` : text;
|
|
12
|
+
}
|
|
13
|
+
export function createSpinner(options) {
|
|
14
|
+
const stream = options.stream || process.stderr;
|
|
15
|
+
if (!supportsSpinner(stream)) {
|
|
16
|
+
return {
|
|
17
|
+
start() { },
|
|
18
|
+
stop() { },
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
let index = 0;
|
|
22
|
+
let timer = null;
|
|
23
|
+
const render = () => {
|
|
24
|
+
const frame = paint(FRAMES[index % FRAMES.length], ANSI.cyan, stream);
|
|
25
|
+
const text = paint(options.text, ANSI.dim, stream);
|
|
26
|
+
stream.write(`\r${frame} ${text}`);
|
|
27
|
+
index += 1;
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
start() {
|
|
31
|
+
if (timer) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
render();
|
|
35
|
+
timer = setInterval(render, options.intervalMs || 90);
|
|
36
|
+
timer.unref?.();
|
|
37
|
+
},
|
|
38
|
+
stop() {
|
|
39
|
+
if (timer) {
|
|
40
|
+
clearInterval(timer);
|
|
41
|
+
timer = null;
|
|
42
|
+
}
|
|
43
|
+
stream.write('\r\u001b[2K');
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RevoClient } from './client.ts';
|
|
2
|
+
export type ParsedArgs = {
|
|
3
|
+
_: string[];
|
|
4
|
+
[key: string]: string | boolean | string[] | undefined;
|
|
5
|
+
};
|
|
6
|
+
export type CommandContext = {
|
|
7
|
+
args: ParsedArgs;
|
|
8
|
+
client: RevoClient;
|
|
9
|
+
cwd: string;
|
|
10
|
+
packageVersion: string;
|
|
11
|
+
print: (value: unknown) => void;
|
|
12
|
+
println: (message: string) => void;
|
|
13
|
+
error: (message: string) => void;
|
|
14
|
+
};
|
|
15
|
+
export type ComponentElementRecord = {
|
|
16
|
+
key: string;
|
|
17
|
+
desc?: string;
|
|
18
|
+
details?: string;
|
|
19
|
+
hidden?: boolean;
|
|
20
|
+
order: number;
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
};
|
|
23
|
+
export type ComponentRecord = {
|
|
24
|
+
componentId?: string;
|
|
25
|
+
id?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
category?: string | null;
|
|
28
|
+
desc?: string;
|
|
29
|
+
type?: string;
|
|
30
|
+
compiler?: string;
|
|
31
|
+
active?: boolean;
|
|
32
|
+
async?: boolean;
|
|
33
|
+
version?: number;
|
|
34
|
+
elements?: ComponentElementRecord[];
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/ui.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { RuntimeConfig } from './config.ts';
|
|
2
|
+
import type { AuthViewState } from './runtime-view.ts';
|
|
3
|
+
export declare const CLI_NAME = "revo";
|
|
4
|
+
export declare const CLI_ALIAS = "revoengine";
|
|
5
|
+
export declare const CLI_PACKAGE = "@revoengine/cli";
|
|
6
|
+
export declare function renderBanner(version: string): string;
|
|
7
|
+
export declare function renderHelp(version: string): string;
|
|
8
|
+
export declare function renderRuntimeInfo(input: {
|
|
9
|
+
packageVersion: string;
|
|
10
|
+
nodeVersion: string;
|
|
11
|
+
configDir: string;
|
|
12
|
+
runtime: RuntimeConfig;
|
|
13
|
+
auth: AuthViewState;
|
|
14
|
+
}): string;
|
|
15
|
+
export declare function renderAuthStatus(input: {
|
|
16
|
+
configDir: string;
|
|
17
|
+
runtime: RuntimeConfig;
|
|
18
|
+
auth: AuthViewState;
|
|
19
|
+
}): string;
|
|
20
|
+
export declare function renderOverview(input: {
|
|
21
|
+
packageVersion: string;
|
|
22
|
+
nodeVersion: string;
|
|
23
|
+
configDir: string;
|
|
24
|
+
runtime: RuntimeConfig;
|
|
25
|
+
auth: AuthViewState;
|
|
26
|
+
}): string;
|
package/dist/src/ui.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
export const CLI_NAME = 'revo';
|
|
2
|
+
export const CLI_ALIAS = 'revoengine';
|
|
3
|
+
export const CLI_PACKAGE = '@revoengine/cli';
|
|
4
|
+
const ANSI = {
|
|
5
|
+
reset: '\u001b[0m',
|
|
6
|
+
bold: '\u001b[1m',
|
|
7
|
+
dim: '\u001b[2m',
|
|
8
|
+
red: '\u001b[31m',
|
|
9
|
+
green: '\u001b[32m',
|
|
10
|
+
blue: '\u001b[34m',
|
|
11
|
+
cyan: '\u001b[36m',
|
|
12
|
+
magenta: '\u001b[35m',
|
|
13
|
+
yellow: '\u001b[33m',
|
|
14
|
+
};
|
|
15
|
+
function supportsColor() {
|
|
16
|
+
return Boolean(process.stdout.isTTY || process.env.FORCE_COLOR);
|
|
17
|
+
}
|
|
18
|
+
function paint(text, code) {
|
|
19
|
+
return supportsColor() ? `${code}${text}${ANSI.reset}` : text;
|
|
20
|
+
}
|
|
21
|
+
function paintHeader(text) {
|
|
22
|
+
return paint(text, ANSI.bold + ANSI.magenta);
|
|
23
|
+
}
|
|
24
|
+
function paintMuted(text) {
|
|
25
|
+
return paint(text, ANSI.dim + ANSI.yellow);
|
|
26
|
+
}
|
|
27
|
+
function paintWarning(text) {
|
|
28
|
+
return paint(text, ANSI.bold + ANSI.red);
|
|
29
|
+
}
|
|
30
|
+
function paintStatus(status) {
|
|
31
|
+
if (status === 'authenticated') {
|
|
32
|
+
return paint('Authenticated', ANSI.bold + ANSI.green);
|
|
33
|
+
}
|
|
34
|
+
if (status === 'not_authenticated') {
|
|
35
|
+
return paint('Not authenticated', ANSI.bold + ANSI.red);
|
|
36
|
+
}
|
|
37
|
+
return paint('Unable to validate', ANSI.bold + ANSI.yellow);
|
|
38
|
+
}
|
|
39
|
+
function abbreviateEmail(email) {
|
|
40
|
+
const match = email.match(/^([^@]+)@([0-9a-f]{4})[0-9a-f-]*([0-9a-f]{3})(\.sa\.revoengine\.com)$/i);
|
|
41
|
+
if (!match) {
|
|
42
|
+
return email;
|
|
43
|
+
}
|
|
44
|
+
return `${match[1]}@${match[2]}...${match[3]}${match[4]}`;
|
|
45
|
+
}
|
|
46
|
+
function formatStatusValue(auth) {
|
|
47
|
+
if (auth.status === 'authenticated' && auth.email) {
|
|
48
|
+
return `${paintStatus(auth.status)}. ${paintMuted(`(${abbreviateEmail(auth.email)})`)}`;
|
|
49
|
+
}
|
|
50
|
+
if (auth.status !== 'authenticated' && auth.validation && auth.validation !== 'skipped') {
|
|
51
|
+
return `${paintStatus(auth.status)}. ${paintMuted(`(${auth.validation})`)}`;
|
|
52
|
+
}
|
|
53
|
+
return paintStatus(auth.status);
|
|
54
|
+
}
|
|
55
|
+
function shouldRenderValidationRow(auth) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
function row(label, value) {
|
|
59
|
+
return ` ${paint(label.padEnd(14), ANSI.bold + ANSI.blue)} ${value}`;
|
|
60
|
+
}
|
|
61
|
+
function formatCommand(command) {
|
|
62
|
+
return command
|
|
63
|
+
.split(/(\s+)/)
|
|
64
|
+
.map((part) => {
|
|
65
|
+
if (!part.trim()) {
|
|
66
|
+
return part;
|
|
67
|
+
}
|
|
68
|
+
if (part === CLI_NAME || part === CLI_ALIAS) {
|
|
69
|
+
return paint(part, ANSI.bold + ANSI.cyan);
|
|
70
|
+
}
|
|
71
|
+
if (part.startsWith('<') && part.endsWith('>')) {
|
|
72
|
+
return paint(part, ANSI.yellow);
|
|
73
|
+
}
|
|
74
|
+
if (part.startsWith('--') || /^-\w/.test(part)) {
|
|
75
|
+
return paint(part, ANSI.magenta);
|
|
76
|
+
}
|
|
77
|
+
if (part.startsWith('[') && part.endsWith(']')) {
|
|
78
|
+
return paint(part, ANSI.dim + ANSI.yellow);
|
|
79
|
+
}
|
|
80
|
+
return paint(part, ANSI.blue);
|
|
81
|
+
})
|
|
82
|
+
.join('');
|
|
83
|
+
}
|
|
84
|
+
function commandRow(command, description) {
|
|
85
|
+
const padding = ' '.repeat(Math.max(2, 34 - command.length));
|
|
86
|
+
return ` ${formatCommand(command)}${padding}${paintMuted(description)}`;
|
|
87
|
+
}
|
|
88
|
+
function noteRow(text) {
|
|
89
|
+
return ` ${paintMuted(text)}`;
|
|
90
|
+
}
|
|
91
|
+
function renderCommandCatalog() {
|
|
92
|
+
return [
|
|
93
|
+
paintHeader('Usage'),
|
|
94
|
+
` ${formatCommand(`${CLI_NAME} <command> [arguments] [options]`)}`,
|
|
95
|
+
'',
|
|
96
|
+
paintHeader('Global Options'),
|
|
97
|
+
commandRow('--url <url>', 'Override API base URL for TEST, prod, local, or dedicated tenants'),
|
|
98
|
+
commandRow('--instance <uuid>', 'Override instance ID'),
|
|
99
|
+
commandRow('--token <apiKey>', 'Override API key for non-interactive runs'),
|
|
100
|
+
'',
|
|
101
|
+
paintHeader('Core'),
|
|
102
|
+
commandRow('revo -i', 'Show runtime info, config path, and auth state'),
|
|
103
|
+
commandRow('revo auth login', 'Paste API key and instance ID in the terminal'),
|
|
104
|
+
commandRow('revo auth status', 'Show the current authentication status'),
|
|
105
|
+
commandRow('revo auth logout', 'Remove stored credentials'),
|
|
106
|
+
commandRow('revo project [path]', 'Initialize ambient editor types for JS and TS'),
|
|
107
|
+
commandRow('revo project switch <instanceId>', 'Switch the current project to another stored instance'),
|
|
108
|
+
commandRow('revo endpoints', 'List available API endpoints'),
|
|
109
|
+
'',
|
|
110
|
+
paintHeader('Components'),
|
|
111
|
+
commandRow('revo component pull <componentId...> [--force] [--stale]', 'Pull one or more components safely'),
|
|
112
|
+
commandRow('revo component pull --all [--force] [--stale]', 'Pull every available component with confirmation'),
|
|
113
|
+
commandRow('revo component push <componentId...>', 'Push one or more local components'),
|
|
114
|
+
commandRow('revo component push --all [--force]', 'Push every local component.json with confirmation'),
|
|
115
|
+
commandRow('revo component debug <componentId> [BODY]', 'Debug one local component against sandbox'),
|
|
116
|
+
'',
|
|
117
|
+
paintHeader('Low-Level'),
|
|
118
|
+
commandRow('revo search <CODE|SIMPLE> <term>', 'Search code references or all platform content'),
|
|
119
|
+
commandRow('revo request <METHOD> <PATH> [BODY]', 'Make a raw API request with optional JSON body'),
|
|
120
|
+
].join('\n');
|
|
121
|
+
}
|
|
122
|
+
export function renderBanner(version) {
|
|
123
|
+
const revo = paint('revo', ANSI.dim + ANSI.cyan);
|
|
124
|
+
const engine = paint('engine', ANSI.bold + ANSI.blue);
|
|
125
|
+
const accent = paint('CLI for the RevoEngine Platform', ANSI.dim + ANSI.magenta);
|
|
126
|
+
return [
|
|
127
|
+
` ${revo} ${engine}`,
|
|
128
|
+
` ${accent}`,
|
|
129
|
+
`${paint(CLI_PACKAGE, ANSI.bold)} ${paint(`v${version}`, ANSI.yellow)}`,
|
|
130
|
+
row('Primary', CLI_NAME),
|
|
131
|
+
row('Alias', CLI_ALIAS),
|
|
132
|
+
row('Website', 'https://revoengine.com'),
|
|
133
|
+
].join('\n');
|
|
134
|
+
}
|
|
135
|
+
export function renderHelp(version) {
|
|
136
|
+
return [
|
|
137
|
+
renderBanner(version),
|
|
138
|
+
'',
|
|
139
|
+
renderCommandCatalog(),
|
|
140
|
+
].join('\n');
|
|
141
|
+
}
|
|
142
|
+
export function renderRuntimeInfo(input) {
|
|
143
|
+
const { auth, configDir, nodeVersion, packageVersion, runtime } = input;
|
|
144
|
+
const onboardingWarning = auth.status === 'not_authenticated'
|
|
145
|
+
? paintWarning(`Warning: Start onboarding with ${CLI_ALIAS} auth login`)
|
|
146
|
+
: '';
|
|
147
|
+
return [
|
|
148
|
+
renderBanner(packageVersion),
|
|
149
|
+
...(onboardingWarning ? ['', onboardingWarning] : []),
|
|
150
|
+
'',
|
|
151
|
+
paintHeader('Runtime'),
|
|
152
|
+
row('Node', nodeVersion),
|
|
153
|
+
row('Config', configDir),
|
|
154
|
+
row('Base URL', runtime.baseUrl),
|
|
155
|
+
row('Status', formatStatusValue(auth)),
|
|
156
|
+
row('Instance', runtime.instance || 'missing'),
|
|
157
|
+
...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
|
|
158
|
+
'',
|
|
159
|
+
paintHeader('Tips'),
|
|
160
|
+
commandRow('revo auth login', 'Save credentials interactively'),
|
|
161
|
+
commandRow('revo --help', 'Show the command reference'),
|
|
162
|
+
].join('\n');
|
|
163
|
+
}
|
|
164
|
+
export function renderAuthStatus(input) {
|
|
165
|
+
const { auth, configDir, runtime } = input;
|
|
166
|
+
return [
|
|
167
|
+
paintHeader('Auth Status'),
|
|
168
|
+
row('Status', formatStatusValue(auth)),
|
|
169
|
+
row('Config', configDir),
|
|
170
|
+
row('Base URL', runtime.baseUrl),
|
|
171
|
+
row('Instance', runtime.instance || 'missing'),
|
|
172
|
+
row('API key', runtime.token ? 'configured' : 'missing'),
|
|
173
|
+
...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
|
|
174
|
+
].join('\n');
|
|
175
|
+
}
|
|
176
|
+
export function renderOverview(input) {
|
|
177
|
+
return [
|
|
178
|
+
renderRuntimeInfo(input),
|
|
179
|
+
'',
|
|
180
|
+
renderCommandCatalog(),
|
|
181
|
+
].join('\n');
|
|
182
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ParsedArgs } from './types.ts';
|
|
2
|
+
export declare function readFlag(args: ParsedArgs, names: string[]): string;
|
|
3
|
+
export declare function readBoolFlag(args: ParsedArgs, names: string[]): boolean;
|
|
4
|
+
export declare function readValues(args: ParsedArgs, names: string[]): string[];
|
|
5
|
+
export declare function toArray(value: string | string[] | undefined): string[];
|
|
6
|
+
export declare function sanitizeSegment(value: string): string;
|
|
7
|
+
export declare function readJsonFile(filePath: string): any;
|
|
8
|
+
export declare function writeJsonFile(filePath: string, data: unknown): void;
|
|
9
|
+
export declare function deepClone<T>(value: T): T;
|
|
10
|
+
export declare function readPackageVersion(): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
export function readFlag(args, names) {
|
|
5
|
+
for (const name of names) {
|
|
6
|
+
const value = args[name];
|
|
7
|
+
if (typeof value === 'string') {
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
11
|
+
return value[0];
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
export function readBoolFlag(args, names) {
|
|
17
|
+
for (const name of names) {
|
|
18
|
+
const value = args[name];
|
|
19
|
+
if (typeof value === 'boolean') {
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
if (typeof value === 'string') {
|
|
23
|
+
return value !== 'false';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
export function readValues(args, names) {
|
|
29
|
+
const values = [];
|
|
30
|
+
for (const name of names) {
|
|
31
|
+
const value = args[name];
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
values.push(value);
|
|
34
|
+
}
|
|
35
|
+
else if (Array.isArray(value)) {
|
|
36
|
+
values.push(...value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return values;
|
|
40
|
+
}
|
|
41
|
+
export function toArray(value) {
|
|
42
|
+
if (!value) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
return Array.isArray(value) ? value : [value];
|
|
46
|
+
}
|
|
47
|
+
export function sanitizeSegment(value) {
|
|
48
|
+
const normalized = value
|
|
49
|
+
.trim()
|
|
50
|
+
.replace(/[<>:"/\\|?*\u0000-\u001f]/g, '_')
|
|
51
|
+
.replace(/\s+/g, '_');
|
|
52
|
+
return normalized.length > 0 ? normalized : 'untitled';
|
|
53
|
+
}
|
|
54
|
+
export function readJsonFile(filePath) {
|
|
55
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
56
|
+
return raw.trim() ? JSON.parse(raw) : {};
|
|
57
|
+
}
|
|
58
|
+
export function writeJsonFile(filePath, data) {
|
|
59
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
60
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`, {
|
|
61
|
+
mode: 0o600,
|
|
62
|
+
});
|
|
63
|
+
try {
|
|
64
|
+
fs.chmodSync(filePath, 0o600);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Best effort on platforms with limited chmod support.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function deepClone(value) {
|
|
71
|
+
return JSON.parse(JSON.stringify(value));
|
|
72
|
+
}
|
|
73
|
+
export function readPackageVersion() {
|
|
74
|
+
let currentDir = path.dirname(fileURLToPath(import.meta.url));
|
|
75
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
76
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
77
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
78
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf8');
|
|
79
|
+
return JSON.parse(raw).version;
|
|
80
|
+
}
|
|
81
|
+
currentDir = path.dirname(currentDir);
|
|
82
|
+
}
|
|
83
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
84
|
+
const raw = fs.readFileSync(packageJsonPath, 'utf8');
|
|
85
|
+
return JSON.parse(raw).version;
|
|
86
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@revoengine/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI package for the RevoEngine Platform API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"revo": "dist/bin/revo.js",
|
|
9
|
+
"revoengine": "dist/bin/revo.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc -p tsconfig.build.json",
|
|
13
|
+
"check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/ui.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
|
|
14
|
+
"dev": "node ./scripts/dev.ts",
|
|
15
|
+
"prepack": "npm run build",
|
|
16
|
+
"test": "node --test ./test/*.test.ts"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist/",
|
|
20
|
+
"README.md",
|
|
21
|
+
"tsconfig.json",
|
|
22
|
+
"tsconfig.build.json"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=24"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^25.5.0",
|
|
30
|
+
"typescript": "^6.0.3"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowImportingTsExtensions": true,
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"noEmit": false,
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"rootDir": ".",
|
|
9
|
+
"rewriteRelativeImportExtensions": true
|
|
10
|
+
},
|
|
11
|
+
"include": [
|
|
12
|
+
"src/**/*.ts",
|
|
13
|
+
"bin/**/*.ts"
|
|
14
|
+
]
|
|
15
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"types": [
|
|
10
|
+
"node"
|
|
11
|
+
],
|
|
12
|
+
"skipLibCheck": true
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*.ts",
|
|
16
|
+
"bin/**/*.ts",
|
|
17
|
+
"scripts/**/*.ts"
|
|
18
|
+
]
|
|
19
|
+
}
|