snan-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/.astrorc.example +6 -0
- package/README.md +168 -0
- package/bin/astro +3 -0
- package/package.json +28 -0
- package/src/commands/setup.js +77 -0
- package/src/config/loader.js +45 -0
- package/src/http/client.js +56 -0
- package/src/index.js +147 -0
- package/src/modules/asset.js +28 -0
- package/src/modules/data.js +23 -0
- package/src/modules/index.js +28 -0
- package/src/modules/metadata.js +28 -0
- package/src/modules/standard.js +23 -0
- package/src/output/formatter.js +23 -0
package/.astrorc.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Astro CLI
|
|
2
|
+
|
|
3
|
+
A Node.js CLI tool that exposes backend HTTP interfaces as command-line commands. Supports modular extension without core file modifications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g snan-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configuration
|
|
12
|
+
|
|
13
|
+
Configure the CLI with one of three methods (priority: env vars > ~/.astrorc > .env):
|
|
14
|
+
|
|
15
|
+
### 1. Environment Variables (Highest Priority)
|
|
16
|
+
|
|
17
|
+
**macOS / Linux / Git Bash(bash 语法)**
|
|
18
|
+
|
|
19
|
+
`export` 仅在 bash/zsh 等 shell 中有效。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export ASTRO_TOKEN=your-bearer-token
|
|
23
|
+
export ASTRO_BASE_URL=http://127.0.0.1:8080
|
|
24
|
+
export ASTRO_PREFIX=/astro-server/mcp
|
|
25
|
+
export ASTRO_TIMEOUT=30000
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Windows PowerShell**
|
|
29
|
+
|
|
30
|
+
PowerShell 不支持 `export`。在当前会话中设置环境变量请使用 `$env:NAME = "value"`(关闭终端后失效):
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
$env:ASTRO_TOKEN = "your-bearer-token"
|
|
34
|
+
$env:ASTRO_BASE_URL = "http://127.0.0.1:8080"
|
|
35
|
+
$env:ASTRO_PREFIX = "/astro-server/mcp"
|
|
36
|
+
$env:ASTRO_TIMEOUT = "30000"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
检查是否生效:`$env:ASTRO_TOKEN`
|
|
40
|
+
|
|
41
|
+
若要在 **用户级永久** 生效(新开 PowerShell 仍有效),可使用:
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
[System.Environment]::SetEnvironmentVariable('ASTRO_TOKEN', 'your-bearer-token', 'User')
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
(`ASTRO_BASE_URL` 等同理替换变量名与值。)
|
|
48
|
+
|
|
49
|
+
### 2. Configuration File (~/.astrorc)
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"baseUrl": "http://127.0.0.1:8080",
|
|
53
|
+
"token": "your-bearer-token-here",
|
|
54
|
+
"prefix": "/astro-server/mcp",
|
|
55
|
+
"timeout": 30000
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 3. .env File
|
|
60
|
+
```
|
|
61
|
+
ASTRO_TOKEN=your-bearer-token
|
|
62
|
+
ASTRO_BASE_URL=http://127.0.0.1:8080
|
|
63
|
+
ASTRO_PREFIX=/astro-server/mcp
|
|
64
|
+
ASTRO_TIMEOUT=30000
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
### Basic Commands
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# View available commands
|
|
73
|
+
astro --help
|
|
74
|
+
|
|
75
|
+
# View module commands
|
|
76
|
+
astro data --help
|
|
77
|
+
|
|
78
|
+
# View action options
|
|
79
|
+
astro data search --help
|
|
80
|
+
|
|
81
|
+
# Execute action with JSON body
|
|
82
|
+
astro data search --body '{"sql":"SELECT * FROM table LIMIT 10"}'
|
|
83
|
+
|
|
84
|
+
# Output compact JSON (no pretty-printing)
|
|
85
|
+
astro data search --body '{"sql":"..."}' --raw
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Command Structure
|
|
89
|
+
|
|
90
|
+
Commands follow the pattern: `astro <module> <action> [options]`
|
|
91
|
+
|
|
92
|
+
- **Module**: Data query/manipulation, standard operations, etc.
|
|
93
|
+
- **Action**: search, insert, update, delete, etc.
|
|
94
|
+
- **Options**: `--body <json>` for request payload, `--raw` for compact output
|
|
95
|
+
|
|
96
|
+
## Error Handling
|
|
97
|
+
|
|
98
|
+
The CLI provides clear error messages for:
|
|
99
|
+
- Missing token configuration
|
|
100
|
+
- Invalid JSON in `--body`
|
|
101
|
+
- HTTP errors (with status code and response details)
|
|
102
|
+
- Network errors
|
|
103
|
+
|
|
104
|
+
## Adding New Modules
|
|
105
|
+
|
|
106
|
+
Create a new module file in `src/modules/`:
|
|
107
|
+
|
|
108
|
+
**src/modules/standard.js**
|
|
109
|
+
```js
|
|
110
|
+
module.exports = {
|
|
111
|
+
name: 'standard',
|
|
112
|
+
description: 'Standard data operations',
|
|
113
|
+
actions: {
|
|
114
|
+
query: {
|
|
115
|
+
description: 'Query data with filters',
|
|
116
|
+
},
|
|
117
|
+
transform: {
|
|
118
|
+
description: 'Transform data format',
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The module is automatically discovered and registered. No core files need modification.
|
|
125
|
+
|
|
126
|
+
### Usage
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
astro standard query --body '{"filter":"..."}'
|
|
130
|
+
astro standard transform --body '{"format":"json"}'
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Project Structure
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
.
|
|
137
|
+
├── bin/
|
|
138
|
+
│ └── astro # Executable entry point
|
|
139
|
+
├── src/
|
|
140
|
+
│ ├── index.js # Main CLI orchestrator
|
|
141
|
+
│ ├── config/
|
|
142
|
+
│ │ └── loader.js # Configuration reader
|
|
143
|
+
│ ├── http/
|
|
144
|
+
│ │ └── client.js # HTTP client with axios
|
|
145
|
+
│ ├── modules/
|
|
146
|
+
│ │ ├── index.js # Module auto-loader
|
|
147
|
+
│ │ └── data.js # Example data module
|
|
148
|
+
│ └── output/
|
|
149
|
+
│ └── formatter.js # Output formatting & error handling
|
|
150
|
+
├── .astrorc.example # Configuration template
|
|
151
|
+
└── package.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## API Endpoint Format
|
|
155
|
+
|
|
156
|
+
Requests are sent to: `{baseUrl}{prefix}/{module}/{action}`
|
|
157
|
+
|
|
158
|
+
Example: `http://127.0.0.1:8080/astro-server/mcp/data/search`
|
|
159
|
+
|
|
160
|
+
## Dependencies
|
|
161
|
+
|
|
162
|
+
- **axios**: HTTP client
|
|
163
|
+
- **commander**: CLI argument parsing
|
|
164
|
+
- **dotenv**: Environment variable loading
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
package/bin/astro
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "snan-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to expose backend HTTP interfaces as command-line commands",
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"bin": {
|
|
7
|
+
"astro": "bin/astro"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node bin/astro",
|
|
12
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"http",
|
|
17
|
+
"command-line"
|
|
18
|
+
],
|
|
19
|
+
"author": "",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"axios": "^1.6.0",
|
|
23
|
+
"commander": "^11.0.0",
|
|
24
|
+
"dotenv": "^16.3.1",
|
|
25
|
+
"enquirer": "^2.4.1",
|
|
26
|
+
"omelette": "^0.4.17"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { prompt } = require('enquirer');
|
|
5
|
+
|
|
6
|
+
const ZSHRC = path.join(os.homedir(), '.zshrc');
|
|
7
|
+
const BASHRC = path.join(os.homedir(), '.bashrc');
|
|
8
|
+
const MARKER_BEGIN = '# begin astro config';
|
|
9
|
+
const MARKER_END = '# end astro config';
|
|
10
|
+
|
|
11
|
+
const DEFAULT_BASE_URL = 'https://data.snanai.com';
|
|
12
|
+
|
|
13
|
+
function detectShellRc() {
|
|
14
|
+
const shell = process.env.SHELL || '';
|
|
15
|
+
if (shell.includes('zsh') && fs.existsSync(ZSHRC)) return ZSHRC;
|
|
16
|
+
if (fs.existsSync(BASHRC)) return BASHRC;
|
|
17
|
+
return ZSHRC;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function writeExports(rcFile, baseUrl, token) {
|
|
21
|
+
let content = fs.existsSync(rcFile) ? fs.readFileSync(rcFile, 'utf-8') : '';
|
|
22
|
+
|
|
23
|
+
const lines = [
|
|
24
|
+
MARKER_BEGIN,
|
|
25
|
+
`export ASTRO_BASE_URL="${baseUrl}"`,
|
|
26
|
+
];
|
|
27
|
+
if (token) {
|
|
28
|
+
lines.push(`export ASTRO_TOKEN="${token}"`);
|
|
29
|
+
}
|
|
30
|
+
lines.push(MARKER_END);
|
|
31
|
+
|
|
32
|
+
const block = lines.join('\n');
|
|
33
|
+
const regex = new RegExp(`${MARKER_BEGIN}[\\s\\S]*?${MARKER_END}`, 'm');
|
|
34
|
+
|
|
35
|
+
if (regex.test(content)) {
|
|
36
|
+
content = content.replace(regex, block);
|
|
37
|
+
} else {
|
|
38
|
+
content = content.trimEnd() + '\n\n' + block + '\n';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(rcFile, content, 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function runSetup() {
|
|
45
|
+
console.log('Astro CLI Setup\n');
|
|
46
|
+
|
|
47
|
+
const answers = await prompt([
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'baseUrl',
|
|
51
|
+
message: 'API Base URL (host only)',
|
|
52
|
+
initial: DEFAULT_BASE_URL,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'password',
|
|
56
|
+
name: 'token',
|
|
57
|
+
message: 'API Key (token, leave empty to skip)',
|
|
58
|
+
initial: '',
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
|
|
62
|
+
// Strip trailing slash
|
|
63
|
+
const baseUrl = answers.baseUrl.replace(/\/+$/, '');
|
|
64
|
+
|
|
65
|
+
const rcFile = detectShellRc();
|
|
66
|
+
writeExports(rcFile, baseUrl, answers.token);
|
|
67
|
+
|
|
68
|
+
console.log(`\nSaved to ${rcFile}:`);
|
|
69
|
+
console.log(` ASTRO_BASE_URL=${baseUrl}`);
|
|
70
|
+
if (answers.token) {
|
|
71
|
+
console.log(` ASTRO_TOKEN=****`);
|
|
72
|
+
}
|
|
73
|
+
console.log('\nApply now by running:');
|
|
74
|
+
console.log(` source ${rcFile}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { runSetup };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
require('dotenv').config();
|
|
5
|
+
|
|
6
|
+
function loadConfig() {
|
|
7
|
+
// Default config
|
|
8
|
+
const defaults = {
|
|
9
|
+
baseUrl: 'https://data.snanai.com',
|
|
10
|
+
prefix: '/astro-server/mcp',
|
|
11
|
+
timeout: 30000,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let config = { ...defaults };
|
|
15
|
+
|
|
16
|
+
// Load from ~/.astrorc
|
|
17
|
+
const rcPath = path.join(os.homedir(), '.astrorc');
|
|
18
|
+
if (fs.existsSync(rcPath)) {
|
|
19
|
+
try {
|
|
20
|
+
const rcContent = fs.readFileSync(rcPath, 'utf-8');
|
|
21
|
+
const rcConfig = JSON.parse(rcContent);
|
|
22
|
+
config = { ...config, ...rcConfig };
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error(`Warning: Failed to parse ~/.astrorc: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Override with environment variables
|
|
29
|
+
if (process.env.ASTRO_BASE_URL) {
|
|
30
|
+
config.baseUrl = process.env.ASTRO_BASE_URL;
|
|
31
|
+
}
|
|
32
|
+
if (process.env.ASTRO_TOKEN) {
|
|
33
|
+
config.token = process.env.ASTRO_TOKEN;
|
|
34
|
+
}
|
|
35
|
+
if (process.env.ASTRO_PREFIX) {
|
|
36
|
+
config.prefix = process.env.ASTRO_PREFIX;
|
|
37
|
+
}
|
|
38
|
+
if (process.env.ASTRO_TIMEOUT) {
|
|
39
|
+
config.timeout = parseInt(process.env.ASTRO_TIMEOUT, 10);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return config;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { loadConfig };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
function createHttpClient(config) {
|
|
4
|
+
const instance = axios.create({
|
|
5
|
+
baseURL: config.baseUrl,
|
|
6
|
+
timeout: config.timeout || 30000,
|
|
7
|
+
headers: {
|
|
8
|
+
'Authorization': `Bearer ${config.token}`,
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Response interceptor for error handling
|
|
13
|
+
instance.interceptors.response.use(
|
|
14
|
+
response => response.data,
|
|
15
|
+
error => {
|
|
16
|
+
if (error.response) {
|
|
17
|
+
return {
|
|
18
|
+
status: error.response.status,
|
|
19
|
+
message: error.response.data?.message || error.response.statusText || 'HTTP Error',
|
|
20
|
+
data: error.response.data,
|
|
21
|
+
isHttpError: true,
|
|
22
|
+
};
|
|
23
|
+
} else if (error.request) {
|
|
24
|
+
return {
|
|
25
|
+
status: 0,
|
|
26
|
+
message: error.message || 'Network Error',
|
|
27
|
+
isHttpError: false,
|
|
28
|
+
};
|
|
29
|
+
} else {
|
|
30
|
+
return {
|
|
31
|
+
status: 0,
|
|
32
|
+
message: error.message || 'Request Error',
|
|
33
|
+
isHttpError: false,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 统一请求方法
|
|
41
|
+
* @param {string} method - 'GET' | 'POST'
|
|
42
|
+
* @param {string} url
|
|
43
|
+
* @param {object} params - GET 时作为 query string,POST 时作为 request body
|
|
44
|
+
*/
|
|
45
|
+
instance.request2 = function(method, url, params = {}) {
|
|
46
|
+
if (method.toUpperCase() === 'GET') {
|
|
47
|
+
return instance.get(url, { params });
|
|
48
|
+
} else {
|
|
49
|
+
return instance.post(url, params);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return instance;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { createHttpClient };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
const { program } = require('commander');
|
|
2
|
+
const omelette = require('omelette');
|
|
3
|
+
const { loadConfig } = require('./config/loader');
|
|
4
|
+
const { createHttpClient } = require('./http/client');
|
|
5
|
+
const { loadModules } = require('./modules');
|
|
6
|
+
const { print, error: printError } = require('./output/formatter');
|
|
7
|
+
const { runSetup } = require('./commands/setup');
|
|
8
|
+
|
|
9
|
+
function setupCompletion(modules) {
|
|
10
|
+
const completionTree = {};
|
|
11
|
+
Object.entries(modules).forEach(([moduleName, moduleDef]) => {
|
|
12
|
+
completionTree[moduleName] = Object.keys(moduleDef.actions);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const completion = omelette('astro <module> <action>');
|
|
16
|
+
|
|
17
|
+
completion.on('module', ({ reply }) => {
|
|
18
|
+
reply(Object.keys(completionTree));
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
completion.on('action', ({ before, reply }) => {
|
|
22
|
+
reply(completionTree[before] || []);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
completion.init();
|
|
26
|
+
|
|
27
|
+
if (process.argv.includes('--setup-completion')) {
|
|
28
|
+
completion.setupShellInitFile();
|
|
29
|
+
console.log('Tab completion configured! Restart your shell or run:');
|
|
30
|
+
console.log(' source ~/.zshrc (zsh)');
|
|
31
|
+
console.log(' source ~/.bashrc (bash)');
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const modules = loadModules();
|
|
38
|
+
|
|
39
|
+
setupCompletion(modules);
|
|
40
|
+
|
|
41
|
+
// setup 命令不需要 token,提前注册并处理
|
|
42
|
+
program
|
|
43
|
+
.name('astro')
|
|
44
|
+
.description('CLI tool to expose backend HTTP interfaces')
|
|
45
|
+
.version('1.0.0');
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('setup')
|
|
49
|
+
.description('Configure API base URL and token (writes to shell rc file)')
|
|
50
|
+
.action(async () => {
|
|
51
|
+
await runSetup();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// 如果是 setup 命令,直接解析执行,跳过 token 检查
|
|
56
|
+
if (process.argv[2] === 'setup') {
|
|
57
|
+
await program.parseAsync(process.argv);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const config = loadConfig();
|
|
62
|
+
|
|
63
|
+
if (!config.token) {
|
|
64
|
+
console.error('Error: ASTRO_TOKEN is not configured');
|
|
65
|
+
console.error('');
|
|
66
|
+
console.error('Run "astro setup" to configure interactively, or:');
|
|
67
|
+
console.error('1. Set environment variable: export ASTRO_TOKEN=your-token');
|
|
68
|
+
console.error('2. Create ~/.astrorc with: { "token": "your-token" }');
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const httpClient = createHttpClient(config);
|
|
73
|
+
|
|
74
|
+
Object.entries(modules).forEach(([moduleName, moduleDefinition]) => {
|
|
75
|
+
const urlPrefix = moduleDefinition.urlPrefix || `${config.prefix}/${moduleName}`;
|
|
76
|
+
|
|
77
|
+
const moduleCommand = program
|
|
78
|
+
.command(moduleName)
|
|
79
|
+
.description(moduleDefinition.description);
|
|
80
|
+
|
|
81
|
+
Object.entries(moduleDefinition.actions).forEach(([actionName, actionDef]) => {
|
|
82
|
+
const cmd = moduleCommand
|
|
83
|
+
.command(actionName)
|
|
84
|
+
.description(actionDef.description);
|
|
85
|
+
|
|
86
|
+
const method = (actionDef.method || 'POST').toUpperCase();
|
|
87
|
+
const paramDefs = actionDef.params || [];
|
|
88
|
+
|
|
89
|
+
if (method === 'GET') {
|
|
90
|
+
paramDefs.forEach(p => {
|
|
91
|
+
cmd.option(`--${p.name} <value>`, p.description);
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
cmd.option('--body <json>', 'JSON body for the request');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cmd.option('--raw', 'Output compact JSON');
|
|
98
|
+
|
|
99
|
+
cmd.action(async (options) => {
|
|
100
|
+
try {
|
|
101
|
+
// action.path 优先(支持子路径),否则自动拼接 /actionName
|
|
102
|
+
const url = actionDef.path ? `${urlPrefix}${actionDef.path}` : `${urlPrefix}/${actionName}`;
|
|
103
|
+
let requestParams = {};
|
|
104
|
+
|
|
105
|
+
if (method === 'GET') {
|
|
106
|
+
paramDefs.forEach(p => {
|
|
107
|
+
if (options[p.name] !== undefined) {
|
|
108
|
+
requestParams[p.name] = options[p.name];
|
|
109
|
+
} else if (p.required) {
|
|
110
|
+
console.error(`Error: --${p.name} is required`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
if (options.body) {
|
|
116
|
+
try {
|
|
117
|
+
requestParams = JSON.parse(options.body);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error(`Error: Invalid JSON in --body: ${err.message}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const response = await httpClient.request2(method, url, requestParams);
|
|
126
|
+
|
|
127
|
+
if (response && response.isHttpError) {
|
|
128
|
+
printError(response);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
print(response, { raw: options.raw });
|
|
133
|
+
} catch (err) {
|
|
134
|
+
printError(err);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
await program.parseAsync(process.argv);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
main().catch(err => {
|
|
145
|
+
console.error('Fatal error:', err.message);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'asset',
|
|
3
|
+
description: '数据资产:数据模型和指标查询',
|
|
4
|
+
urlPrefix: '/astro-server/openapi/v1/asset',
|
|
5
|
+
actions: {
|
|
6
|
+
dataModels: {
|
|
7
|
+
description: '获取数据资产模型列表(含可用表信息)',
|
|
8
|
+
method: 'GET',
|
|
9
|
+
params: [
|
|
10
|
+
{ name: 'keyword', description: '查询关键字,多个关键字用空格或|分隔,为空查询全部', required: false },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
dataModelDetail: {
|
|
14
|
+
description: '获取数据模型详情(含逻辑实体和属性字段)',
|
|
15
|
+
method: 'GET',
|
|
16
|
+
params: [
|
|
17
|
+
{ name: 'nameEn', description: '模型英文名(唯一标识),如 SalesOrder', required: true },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
indicators: {
|
|
21
|
+
description: '获取所有指标列表',
|
|
22
|
+
method: 'GET',
|
|
23
|
+
params: [
|
|
24
|
+
{ name: 'keyword', description: '查询关键字,多个关键字用空格或|分隔,为空查询全部', required: false },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'data',
|
|
3
|
+
description: '数据查询:原始数据库和数仓数据查询',
|
|
4
|
+
urlPrefix: '/astro-server/openapi/v1/data',
|
|
5
|
+
actions: {
|
|
6
|
+
queryOrigin: {
|
|
7
|
+
description: '查询源数据库原始数据(ERP、CRM 等原始系统)',
|
|
8
|
+
method: 'POST',
|
|
9
|
+
path: '/queryOrigin',
|
|
10
|
+
params: [
|
|
11
|
+
{ name: 'body', description: 'JSON body: { "dsCode": "数据源编码", "sql": "SQL查询语句" }', required: true },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
queryDw: {
|
|
15
|
+
description: '查询治理好的数仓资产数据',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
path: '/queryDw',
|
|
18
|
+
params: [
|
|
19
|
+
{ name: 'body', description: 'JSON body: { "sql": "SQL查询语句", "rule": "数据统计口径(可选)" }', required: true },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function loadModules() {
|
|
5
|
+
const modulesDir = __dirname;
|
|
6
|
+
const modules = {};
|
|
7
|
+
|
|
8
|
+
const files = fs.readdirSync(modulesDir).filter(file => {
|
|
9
|
+
return file.endsWith('.js') && file !== 'index.js';
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
files.forEach(file => {
|
|
13
|
+
try {
|
|
14
|
+
const modulePath = path.join(modulesDir, file);
|
|
15
|
+
const moduleDefinition = require(modulePath);
|
|
16
|
+
|
|
17
|
+
if (moduleDefinition.name && moduleDefinition.actions) {
|
|
18
|
+
modules[moduleDefinition.name] = moduleDefinition;
|
|
19
|
+
}
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(`Warning: Failed to load module ${file}: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return modules;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { loadModules };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'metadata',
|
|
3
|
+
description: '元数据查询:数据源和数仓表结构查询',
|
|
4
|
+
urlPrefix: '/astro-server/openapi/v1/metadata',
|
|
5
|
+
actions: {
|
|
6
|
+
sources: {
|
|
7
|
+
description: '获取当前租户下所有元数据来源列表(查表前先调此接口获取 systemAbbr)',
|
|
8
|
+
method: 'GET',
|
|
9
|
+
params: [],
|
|
10
|
+
},
|
|
11
|
+
tables: {
|
|
12
|
+
description: '根据元数据来源简称和关键字查询表列表(最多 200 条)',
|
|
13
|
+
method: 'GET',
|
|
14
|
+
params: [
|
|
15
|
+
{ name: 'systemAbbr', description: '元数据来源简称,传 doris_prod 查数仓表,传数据源简称查数据源表', required: true },
|
|
16
|
+
{ name: 'keyword', description: '查询关键字,匹配表名或描述,多关键字用空格或|分隔,为空返回全部', required: false },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
tableDetail: {
|
|
20
|
+
description: '获取表详情,包含字段列表和字段类型',
|
|
21
|
+
method: 'GET',
|
|
22
|
+
params: [
|
|
23
|
+
{ name: 'systemAbbr', description: '元数据来源简称,传 doris_prod 查数仓表,传数据源简称查数据源表', required: true },
|
|
24
|
+
{ name: 'schemaTable', description: '表引用,schema.tablename 形式,如 ods.ods_order', required: true },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'standard',
|
|
3
|
+
description: '数据标准:数据标准和词根的创建与修改',
|
|
4
|
+
urlPrefix: '/astro-server/openapi/v1/standard',
|
|
5
|
+
actions: {
|
|
6
|
+
dataStandard: {
|
|
7
|
+
description: '创建或修改数据标准(按中文名称判断:已存在则修改,否则新建)',
|
|
8
|
+
method: 'POST',
|
|
9
|
+
path: '/dataStandard',
|
|
10
|
+
params: [
|
|
11
|
+
{ name: 'body', description: 'DataStandardRequestDTO JSON 对象', required: true },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
nameRoot: {
|
|
15
|
+
description: '创建或修改词根/数据字典(按中文名称判断:已存在则修改,否则新建)',
|
|
16
|
+
method: 'POST',
|
|
17
|
+
path: '/nameRoot',
|
|
18
|
+
params: [
|
|
19
|
+
{ name: 'body', description: 'StandardDictRequestDTO JSON 对象', required: true },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
function print(data, options = {}) {
|
|
2
|
+
if (options.raw) {
|
|
3
|
+
console.log(JSON.stringify(data));
|
|
4
|
+
} else {
|
|
5
|
+
console.log(JSON.stringify(data, null, 2));
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function error(err) {
|
|
10
|
+
if (err.isHttpError) {
|
|
11
|
+
console.error(`HTTP ${err.status}: ${err.message}`);
|
|
12
|
+
if (err.data !== undefined) {
|
|
13
|
+
console.error(typeof err.data === 'string' ? err.data : JSON.stringify(err.data, null, 2));
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
console.error(`Error: ${err.message}`);
|
|
17
|
+
if (err.stack) {
|
|
18
|
+
console.error(err.stack);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { print, error };
|