@ultikits/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/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/dist/bin/ultikits.d.ts +2 -0
- package/dist/bin/ultikits.js +23 -0
- package/dist/src/commands/init.d.ts +2 -0
- package/dist/src/commands/init.js +62 -0
- package/dist/src/commands/login.d.ts +2 -0
- package/dist/src/commands/login.js +120 -0
- package/dist/src/commands/logout.d.ts +2 -0
- package/dist/src/commands/logout.js +12 -0
- package/dist/src/commands/modules.d.ts +2 -0
- package/dist/src/commands/modules.js +92 -0
- package/dist/src/commands/publish.d.ts +12 -0
- package/dist/src/commands/publish.js +171 -0
- package/dist/src/commands/versions.d.ts +2 -0
- package/dist/src/commands/versions.js +76 -0
- package/dist/src/commands/whoami.d.ts +2 -0
- package/dist/src/commands/whoami.js +32 -0
- package/dist/src/lib/api-client.d.ts +12 -0
- package/dist/src/lib/api-client.js +71 -0
- package/dist/src/lib/config.d.ts +6 -0
- package/dist/src/lib/config.js +39 -0
- package/dist/src/lib/jar-reader.d.ts +3 -0
- package/dist/src/lib/jar-reader.js +42 -0
- package/dist/src/lib/logger.d.ts +5 -0
- package/dist/src/lib/logger.js +18 -0
- package/dist/src/lib/project-config.d.ts +3 -0
- package/dist/src/lib/project-config.js +18 -0
- package/dist/src/types.d.ts +38 -0
- package/dist/src/types.js +2 -0
- package/package.json +63 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.0.0 (2026-02-14)
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- **Authentication**: Browser-based magic link login and developer access token support
|
|
10
|
+
- **CI/CD**: `ULTIKITS_TOKEN` environment variable for automated pipelines
|
|
11
|
+
- **Publishing**: `ultikits publish` with automatic JAR detection and `plugin.yml` metadata extraction
|
|
12
|
+
- **Project config**: `ultikits init` creates `ultikits.json` for persistent module metadata
|
|
13
|
+
- **Module management**: List, inspect, and delete modules via `ultikits modules`
|
|
14
|
+
- **Version management**: List and delete versions via `ultikits versions`
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 UltiKits
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# @ultikits/cli
|
|
2
|
+
|
|
3
|
+
Command-line tool for publishing [UltiTools](https://github.com/UltiKits/UltiTools-Reborn) plugin modules to [UltiCloud](https://panel.ultikits.com) marketplace.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @ultikits/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 18 or later.
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Log in to your UltiKits developer account
|
|
17
|
+
ultikits login
|
|
18
|
+
|
|
19
|
+
# 2. Initialize project config (optional, recommended)
|
|
20
|
+
ultikits init
|
|
21
|
+
|
|
22
|
+
# 3. Build your plugin and publish
|
|
23
|
+
mvn clean package
|
|
24
|
+
ultikits publish
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Authentication
|
|
28
|
+
|
|
29
|
+
### Interactive login (browser)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ultikits login
|
|
33
|
+
# Choose "Browser login (magic link)"
|
|
34
|
+
# A browser window opens — approve the request
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Access token (developer portal)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
ultikits login
|
|
41
|
+
# Choose "Access token (from developer portal)"
|
|
42
|
+
# Paste your access token from panel.ultikits.com/developer
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### CI/CD (environment variable)
|
|
46
|
+
|
|
47
|
+
Set the `ULTIKITS_TOKEN` environment variable with your developer access token. No `login` step is needed.
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
# GitHub Actions example
|
|
51
|
+
- name: Publish to UltiCloud
|
|
52
|
+
env:
|
|
53
|
+
ULTIKITS_TOKEN: ${{ secrets.ULTIKITS_TOKEN }}
|
|
54
|
+
run: ultikits publish --yes
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Credentials are stored in `~/.ultikits/credentials.json`.
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
### `ultikits login`
|
|
62
|
+
|
|
63
|
+
Authenticate with UltiKits. Choose between browser-based magic link or pasting an access token.
|
|
64
|
+
|
|
65
|
+
### `ultikits logout`
|
|
66
|
+
|
|
67
|
+
Remove stored credentials.
|
|
68
|
+
|
|
69
|
+
### `ultikits whoami`
|
|
70
|
+
|
|
71
|
+
Check current authentication status.
|
|
72
|
+
|
|
73
|
+
### `ultikits init`
|
|
74
|
+
|
|
75
|
+
Create an `ultikits.json` project config in the current directory. Interactive prompts for module ID, name, description, and category.
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"identifyString": "my.awesome.plugin",
|
|
80
|
+
"name": "My Awesome Plugin",
|
|
81
|
+
"shortDescription": "A great UltiTools module",
|
|
82
|
+
"categoryId": 1
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `ultikits publish [jar-path]`
|
|
87
|
+
|
|
88
|
+
Publish a plugin module JAR to UltiCloud.
|
|
89
|
+
|
|
90
|
+
If no JAR path is given, automatically finds the built JAR in `./target/` (skips `-sources.jar` and `-javadoc.jar`).
|
|
91
|
+
|
|
92
|
+
**Options:**
|
|
93
|
+
|
|
94
|
+
| Flag | Description |
|
|
95
|
+
|------|-------------|
|
|
96
|
+
| `--id <string>` | Module identify string |
|
|
97
|
+
| `--name <string>` | Module display name |
|
|
98
|
+
| `--version <string>` | Module version |
|
|
99
|
+
| `--changelog <text>` | Version changelog |
|
|
100
|
+
| `--short-description <text>` | Short description |
|
|
101
|
+
| `-y, --yes` | Skip confirmation prompts |
|
|
102
|
+
|
|
103
|
+
**Metadata resolution priority:** CLI flags > `ultikits.json` > `plugin.yml` from JAR.
|
|
104
|
+
|
|
105
|
+
**Example:**
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Auto-detect JAR and metadata from plugin.yml + ultikits.json
|
|
109
|
+
ultikits publish
|
|
110
|
+
|
|
111
|
+
# Explicit JAR path with overrides
|
|
112
|
+
ultikits publish target/MyPlugin-1.0.0.jar --version 1.0.1 --changelog "Bug fix"
|
|
113
|
+
|
|
114
|
+
# CI/CD mode (no prompts)
|
|
115
|
+
ultikits publish --yes
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `ultikits modules list`
|
|
119
|
+
|
|
120
|
+
List all your published modules.
|
|
121
|
+
|
|
122
|
+
### `ultikits modules info <identifyString>`
|
|
123
|
+
|
|
124
|
+
Show details of a specific module.
|
|
125
|
+
|
|
126
|
+
### `ultikits modules delete <identifyString>`
|
|
127
|
+
|
|
128
|
+
Delete a module (with confirmation prompt).
|
|
129
|
+
|
|
130
|
+
### `ultikits versions list <identifyString>`
|
|
131
|
+
|
|
132
|
+
List all versions of a module.
|
|
133
|
+
|
|
134
|
+
### `ultikits versions delete <identifyString> <version>`
|
|
135
|
+
|
|
136
|
+
Delete a specific version (with confirmation prompt).
|
|
137
|
+
|
|
138
|
+
## Project Config (`ultikits.json`)
|
|
139
|
+
|
|
140
|
+
Create with `ultikits init` or manually. Fields are used as fallback metadata during `publish`:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"identifyString": "com.example.myplugin",
|
|
145
|
+
"name": "My Plugin",
|
|
146
|
+
"shortDescription": "Does amazing things",
|
|
147
|
+
"categoryId": 1
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Categories:**
|
|
152
|
+
|
|
153
|
+
| ID | Name |
|
|
154
|
+
|----|------|
|
|
155
|
+
| 1 | Utilities |
|
|
156
|
+
| 2 | Economy |
|
|
157
|
+
| 3 | World Management |
|
|
158
|
+
| 4 | Social & Chat |
|
|
159
|
+
| 5 | Permissions & Security |
|
|
160
|
+
| 6 | Gameplay |
|
|
161
|
+
|
|
162
|
+
## Development
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
git clone https://github.com/UltiKits/ultikits-cli.git
|
|
166
|
+
cd ultikits-cli
|
|
167
|
+
npm install
|
|
168
|
+
npm run build
|
|
169
|
+
npm test
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## License
|
|
173
|
+
|
|
174
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { registerLoginCommand } from '../src/commands/login.js';
|
|
4
|
+
import { registerLogoutCommand } from '../src/commands/logout.js';
|
|
5
|
+
import { registerWhoamiCommand } from '../src/commands/whoami.js';
|
|
6
|
+
import { registerInitCommand } from '../src/commands/init.js';
|
|
7
|
+
import { registerPublishCommand } from '../src/commands/publish.js';
|
|
8
|
+
import { registerModulesCommand } from '../src/commands/modules.js';
|
|
9
|
+
import { registerVersionsCommand } from '../src/commands/versions.js';
|
|
10
|
+
const program = new Command();
|
|
11
|
+
program
|
|
12
|
+
.name('ultikits')
|
|
13
|
+
.description('CLI for publishing UltiTools plugin modules to UltiCloud')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
registerLoginCommand(program);
|
|
16
|
+
registerLogoutCommand(program);
|
|
17
|
+
registerWhoamiCommand(program);
|
|
18
|
+
registerInitCommand(program);
|
|
19
|
+
registerPublishCommand(program);
|
|
20
|
+
registerModulesCommand(program);
|
|
21
|
+
registerVersionsCommand(program);
|
|
22
|
+
program.parse();
|
|
23
|
+
//# sourceMappingURL=ultikits.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { readProjectConfig, writeProjectConfig } from '../lib/project-config.js';
|
|
3
|
+
import * as log from '../lib/logger.js';
|
|
4
|
+
const CATEGORIES = [
|
|
5
|
+
{ name: 'Utilities', value: 1 },
|
|
6
|
+
{ name: 'Economy', value: 2 },
|
|
7
|
+
{ name: 'World Management', value: 3 },
|
|
8
|
+
{ name: 'Social & Chat', value: 4 },
|
|
9
|
+
{ name: 'Permissions & Security', value: 5 },
|
|
10
|
+
{ name: 'Gameplay', value: 6 },
|
|
11
|
+
];
|
|
12
|
+
export function registerInitCommand(program) {
|
|
13
|
+
program
|
|
14
|
+
.command('init')
|
|
15
|
+
.description('Create ultikits.json project config')
|
|
16
|
+
.action(async () => {
|
|
17
|
+
const existing = readProjectConfig();
|
|
18
|
+
if (existing) {
|
|
19
|
+
const { overwrite } = await inquirer.prompt([
|
|
20
|
+
{
|
|
21
|
+
type: 'confirm',
|
|
22
|
+
name: 'overwrite',
|
|
23
|
+
message: 'ultikits.json already exists. Overwrite?',
|
|
24
|
+
default: false,
|
|
25
|
+
},
|
|
26
|
+
]);
|
|
27
|
+
if (!overwrite)
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const answers = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
type: 'input',
|
|
33
|
+
name: 'identifyString',
|
|
34
|
+
message: 'Module identify string (e.g., my.awesome.plugin):',
|
|
35
|
+
validate: (v) => /^[a-z][a-z0-9_.]{2,63}$/.test(v)
|
|
36
|
+
? true
|
|
37
|
+
: 'Must be 3-64 chars, lowercase, start with letter, only a-z 0-9 . _',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'name',
|
|
42
|
+
message: 'Display name:',
|
|
43
|
+
validate: (v) => (v.trim().length > 0 ? true : 'Required'),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'shortDescription',
|
|
48
|
+
message: 'Short description (max 200 chars):',
|
|
49
|
+
validate: (v) => v.length <= 200 ? true : 'Must be 200 characters or fewer',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
type: 'list',
|
|
53
|
+
name: 'categoryId',
|
|
54
|
+
message: 'Category:',
|
|
55
|
+
choices: CATEGORIES,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
writeProjectConfig(answers);
|
|
59
|
+
log.success('Created ultikits.json');
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import open from 'open';
|
|
3
|
+
import { writeCredentials } from '../lib/config.js';
|
|
4
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
5
|
+
import * as log from '../lib/logger.js';
|
|
6
|
+
const POLL_INTERVAL_MS = 2000;
|
|
7
|
+
const API_URL = 'https://api.ultikits.com';
|
|
8
|
+
export function registerLoginCommand(program) {
|
|
9
|
+
program
|
|
10
|
+
.command('login')
|
|
11
|
+
.description('Authenticate with UltiKits')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const { method } = await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
type: 'list',
|
|
16
|
+
name: 'method',
|
|
17
|
+
message: 'How would you like to authenticate?',
|
|
18
|
+
choices: [
|
|
19
|
+
{ name: 'Browser login (magic link)', value: 'magic-link' },
|
|
20
|
+
{ name: 'Access token (from developer portal)', value: 'access-token' },
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
]);
|
|
24
|
+
if (method === 'magic-link') {
|
|
25
|
+
await loginWithMagicLink();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
await loginWithAccessToken();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function loginWithMagicLink() {
|
|
33
|
+
const spin = log.spinner('Requesting authentication...');
|
|
34
|
+
spin.start();
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`${API_URL}/auth/cli/request`, { method: 'POST' });
|
|
37
|
+
const body = await response.json();
|
|
38
|
+
if (body.code !== '200') {
|
|
39
|
+
spin.fail('Failed to create auth request: ' + body.msg);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const { requestId, code, url } = body.data;
|
|
43
|
+
spin.stop();
|
|
44
|
+
console.log();
|
|
45
|
+
log.info(`Opening browser to authenticate...`);
|
|
46
|
+
log.info(`If it doesn't open, visit: ${url}`);
|
|
47
|
+
console.log();
|
|
48
|
+
try {
|
|
49
|
+
await open(url);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Browser didn't open — user has the URL
|
|
53
|
+
}
|
|
54
|
+
spin.text = 'Waiting for authentication...';
|
|
55
|
+
spin.start();
|
|
56
|
+
// Poll for completion
|
|
57
|
+
const deadline = Date.now() + 10 * 60 * 1000; // 10 minute timeout
|
|
58
|
+
while (Date.now() < deadline) {
|
|
59
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
60
|
+
const pollRes = await fetch(`${API_URL}/auth/cli/poll?requestId=${requestId}`);
|
|
61
|
+
const pollBody = await pollRes.json();
|
|
62
|
+
if (pollBody.data?.status === 'completed') {
|
|
63
|
+
writeCredentials({
|
|
64
|
+
type: 'oauth',
|
|
65
|
+
access_token: pollBody.data.access_token,
|
|
66
|
+
refresh_token: pollBody.data.refresh_token,
|
|
67
|
+
expires_at: new Date(Date.now() + 3600 * 1000).toISOString(),
|
|
68
|
+
api_url: API_URL,
|
|
69
|
+
});
|
|
70
|
+
spin.succeed('Logged in successfully');
|
|
71
|
+
log.info('Credentials saved to ~/.ultikits/credentials.json');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (pollBody.data?.status === 'expired') {
|
|
75
|
+
spin.fail('Authentication request expired. Run `ultikits login` again.');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
spin.fail('Authentication timed out.');
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
spin.fail('Login failed: ' + err.message);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function loginWithAccessToken() {
|
|
86
|
+
const { token } = await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'password',
|
|
89
|
+
name: 'token',
|
|
90
|
+
message: 'Paste your access token:',
|
|
91
|
+
mask: '●',
|
|
92
|
+
},
|
|
93
|
+
]);
|
|
94
|
+
const spin = log.spinner('Validating token...');
|
|
95
|
+
spin.start();
|
|
96
|
+
try {
|
|
97
|
+
// Validate by trying to list developer modules
|
|
98
|
+
const client = new ApiClient({
|
|
99
|
+
type: 'access_token',
|
|
100
|
+
token,
|
|
101
|
+
api_url: API_URL,
|
|
102
|
+
});
|
|
103
|
+
const res = await client.get('/developer/modules');
|
|
104
|
+
if (res.code !== '200') {
|
|
105
|
+
spin.fail('Invalid token. Get yours at panel.ultikits.com/developer');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
writeCredentials({
|
|
109
|
+
type: 'access_token',
|
|
110
|
+
token,
|
|
111
|
+
api_url: API_URL,
|
|
112
|
+
});
|
|
113
|
+
spin.succeed('Token verified — logged in');
|
|
114
|
+
log.info('Credentials saved to ~/.ultikits/credentials.json');
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
spin.fail('Token validation failed: ' + err.message);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=login.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { deleteCredentials } from '../lib/config.js';
|
|
2
|
+
import * as log from '../lib/logger.js';
|
|
3
|
+
export function registerLogoutCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command('logout')
|
|
6
|
+
.description('Remove stored credentials')
|
|
7
|
+
.action(() => {
|
|
8
|
+
deleteCredentials();
|
|
9
|
+
log.success('Logged out. Credentials removed.');
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=logout.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { readCredentials } from '../lib/config.js';
|
|
3
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
4
|
+
import * as log from '../lib/logger.js';
|
|
5
|
+
export function registerModulesCommand(program) {
|
|
6
|
+
const modules = program
|
|
7
|
+
.command('modules')
|
|
8
|
+
.description('Manage your marketplace modules');
|
|
9
|
+
modules
|
|
10
|
+
.command('list')
|
|
11
|
+
.description('List your modules')
|
|
12
|
+
.action(async () => {
|
|
13
|
+
const client = getClient();
|
|
14
|
+
const spin = log.spinner('Loading modules...');
|
|
15
|
+
spin.start();
|
|
16
|
+
const res = await client.get('/developer/modules');
|
|
17
|
+
if (res.code !== '200') {
|
|
18
|
+
spin.fail('Failed to list modules: ' + res.msg);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
spin.stop();
|
|
22
|
+
const modulesList = res.data;
|
|
23
|
+
if (modulesList.length === 0) {
|
|
24
|
+
log.info('No modules found. Create one with `ultikits publish`.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log();
|
|
28
|
+
for (const mod of modulesList) {
|
|
29
|
+
const version = mod.latestVersion?.version ?? 'no versions';
|
|
30
|
+
console.log(` ${mod.identifyString} — ${mod.name} (${version})`);
|
|
31
|
+
}
|
|
32
|
+
console.log();
|
|
33
|
+
log.info(`${modulesList.length} module(s)`);
|
|
34
|
+
});
|
|
35
|
+
modules
|
|
36
|
+
.command('info <identifyString>')
|
|
37
|
+
.description('Show module details')
|
|
38
|
+
.action(async (identifyString) => {
|
|
39
|
+
const client = getClient();
|
|
40
|
+
const res = await client.get(`/plugin/get?identifyString=${identifyString}`);
|
|
41
|
+
if (res.code !== '200' || !res.data) {
|
|
42
|
+
log.error('Module not found: ' + identifyString);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const mod = res.data;
|
|
46
|
+
console.log();
|
|
47
|
+
console.log(` Name: ${mod.name}`);
|
|
48
|
+
console.log(` ID: ${mod.identifyString}`);
|
|
49
|
+
console.log(` Description: ${mod.shortDescription || 'N/A'}`);
|
|
50
|
+
console.log(` Downloads: ${mod.downloads ?? 0}`);
|
|
51
|
+
console.log(` Version: ${mod.latestVersion?.version ?? 'N/A'}`);
|
|
52
|
+
console.log();
|
|
53
|
+
});
|
|
54
|
+
modules
|
|
55
|
+
.command('delete <identifyString>')
|
|
56
|
+
.description('Delete a module')
|
|
57
|
+
.action(async (identifyString) => {
|
|
58
|
+
const client = getClient();
|
|
59
|
+
// Get module ID first
|
|
60
|
+
const infoRes = await client.get(`/plugin/get?identifyString=${identifyString}`);
|
|
61
|
+
if (infoRes.code !== '200' || !infoRes.data) {
|
|
62
|
+
log.error('Module not found: ' + identifyString);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const { confirm } = await inquirer.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'confirm',
|
|
68
|
+
name: 'confirm',
|
|
69
|
+
message: `Delete module "${identifyString}"? This cannot be undone.`,
|
|
70
|
+
default: false,
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
if (!confirm)
|
|
74
|
+
return;
|
|
75
|
+
const res = await client.delete(`/developer/modules/${infoRes.data.id}`);
|
|
76
|
+
if (res.code !== '200') {
|
|
77
|
+
log.error('Failed to delete: ' + res.msg);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
log.success(`Deleted module: ${identifyString}`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function getClient() {
|
|
84
|
+
const credentials = readCredentials();
|
|
85
|
+
const client = ApiClient.fromEnvOrCredentials(credentials);
|
|
86
|
+
if (!client) {
|
|
87
|
+
log.error('Not logged in. Run `ultikits login` or set ULTIKITS_TOKEN.');
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=modules.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import type { ModuleMetadata, ProjectConfig, PluginYml } from '../types.js';
|
|
3
|
+
interface PublishFlags {
|
|
4
|
+
id?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
version?: string;
|
|
7
|
+
changelog?: string;
|
|
8
|
+
shortDescription?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function resolveMetadata(flags: PublishFlags, projectConfig: ProjectConfig | null, pluginYml: PluginYml | null): ModuleMetadata;
|
|
11
|
+
export declare function registerPublishCommand(program: Command): void;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { readCredentials } from '../lib/config.js';
|
|
5
|
+
import { readProjectConfig } from '../lib/project-config.js';
|
|
6
|
+
import { readPluginYml, findJarInTarget } from '../lib/jar-reader.js';
|
|
7
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
8
|
+
import * as log from '../lib/logger.js';
|
|
9
|
+
export function resolveMetadata(flags, projectConfig, pluginYml) {
|
|
10
|
+
const identifyString = flags.id ?? projectConfig?.identifyString;
|
|
11
|
+
const name = flags.name ?? projectConfig?.name ?? pluginYml?.name;
|
|
12
|
+
const version = flags.version ?? pluginYml?.version;
|
|
13
|
+
const shortDescription = flags.shortDescription ?? projectConfig?.shortDescription ?? pluginYml?.description;
|
|
14
|
+
const changelog = flags.changelog;
|
|
15
|
+
const categoryId = projectConfig?.categoryId;
|
|
16
|
+
const missing = [];
|
|
17
|
+
if (!identifyString)
|
|
18
|
+
missing.push('identifyString (use --id or ultikits.json)');
|
|
19
|
+
if (!name)
|
|
20
|
+
missing.push('name (use --name, ultikits.json, or plugin.yml)');
|
|
21
|
+
if (!version)
|
|
22
|
+
missing.push('version (use --version or plugin.yml in JAR)');
|
|
23
|
+
if (missing.length > 0) {
|
|
24
|
+
throw new Error('Missing required fields:\n - ' + missing.join('\n - '));
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
identifyString: identifyString,
|
|
28
|
+
name: name,
|
|
29
|
+
version: version,
|
|
30
|
+
shortDescription,
|
|
31
|
+
changelog,
|
|
32
|
+
categoryId,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function registerPublishCommand(program) {
|
|
36
|
+
program
|
|
37
|
+
.command('publish [jar-path]')
|
|
38
|
+
.description('Publish a plugin module to UltiCloud')
|
|
39
|
+
.option('--id <identifyString>', 'Module identify string')
|
|
40
|
+
.option('--name <name>', 'Module display name')
|
|
41
|
+
.option('--version <version>', 'Module version')
|
|
42
|
+
.option('--changelog <text>', 'Version changelog')
|
|
43
|
+
.option('--short-description <text>', 'Short description')
|
|
44
|
+
.option('-y, --yes', 'Skip confirmation prompts')
|
|
45
|
+
.action(async (jarPath, options) => {
|
|
46
|
+
// 1. Resolve auth
|
|
47
|
+
const credentials = readCredentials();
|
|
48
|
+
const client = ApiClient.fromEnvOrCredentials(credentials);
|
|
49
|
+
if (!client) {
|
|
50
|
+
log.error('Not logged in. Run `ultikits login` or set ULTIKITS_TOKEN.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
// 2. Resolve JAR path
|
|
54
|
+
const resolvedJarPath = jarPath ?? findJarInTarget();
|
|
55
|
+
if (!resolvedJarPath) {
|
|
56
|
+
log.error('No JAR file specified and none found in ./target/');
|
|
57
|
+
log.info('Usage: ultikits publish <path-to-jar>');
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
if (!fs.existsSync(resolvedJarPath)) {
|
|
61
|
+
log.error(`File not found: ${resolvedJarPath}`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
// 3. Read plugin.yml from JAR
|
|
65
|
+
const pluginYml = await readPluginYml(resolvedJarPath);
|
|
66
|
+
if (pluginYml) {
|
|
67
|
+
log.success(`Read plugin.yml from JAR: name=${pluginYml.name}, version=${pluginYml.version}`);
|
|
68
|
+
}
|
|
69
|
+
// 4. Read project config
|
|
70
|
+
const projectConfig = readProjectConfig();
|
|
71
|
+
if (projectConfig) {
|
|
72
|
+
log.success(`Using config from ultikits.json: id=${projectConfig.identifyString}`);
|
|
73
|
+
}
|
|
74
|
+
// 5. Resolve metadata
|
|
75
|
+
let metadata;
|
|
76
|
+
try {
|
|
77
|
+
metadata = resolveMetadata(options, projectConfig, pluginYml);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
log.error(err.message);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
// 6. Show summary
|
|
84
|
+
const fileSize = fs.statSync(resolvedJarPath).size;
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(` Module: ${metadata.name}`);
|
|
87
|
+
console.log(` ID: ${metadata.identifyString}`);
|
|
88
|
+
console.log(` Version: ${metadata.version}`);
|
|
89
|
+
console.log(` Size: ${(fileSize / 1024).toFixed(0)} KB`);
|
|
90
|
+
console.log();
|
|
91
|
+
// 7. Ask for changelog if not provided
|
|
92
|
+
if (!metadata.changelog && !options.yes) {
|
|
93
|
+
const { changelog } = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'changelog',
|
|
97
|
+
message: 'Changelog (optional):',
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
if (changelog)
|
|
101
|
+
metadata.changelog = changelog;
|
|
102
|
+
}
|
|
103
|
+
// 8. Confirm
|
|
104
|
+
if (!options.yes) {
|
|
105
|
+
const { confirm } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: 'confirm',
|
|
108
|
+
name: 'confirm',
|
|
109
|
+
message: `Publish ${metadata.identifyString}@${metadata.version}?`,
|
|
110
|
+
default: true,
|
|
111
|
+
},
|
|
112
|
+
]);
|
|
113
|
+
if (!confirm)
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// 9. Check if module exists
|
|
117
|
+
const spin = log.spinner('Checking module...');
|
|
118
|
+
spin.start();
|
|
119
|
+
const pluginRes = await client.get(`/plugin/get?identifyString=${metadata.identifyString}`);
|
|
120
|
+
let moduleId;
|
|
121
|
+
if (pluginRes.code !== '200' || !pluginRes.data) {
|
|
122
|
+
// Module doesn't exist — create it
|
|
123
|
+
spin.text = 'Creating module...';
|
|
124
|
+
const createRes = await client.post('/developer/modules', {
|
|
125
|
+
name: metadata.name,
|
|
126
|
+
identifyString: metadata.identifyString,
|
|
127
|
+
shortDescription: metadata.shortDescription ?? '',
|
|
128
|
+
categoryId: metadata.categoryId,
|
|
129
|
+
});
|
|
130
|
+
if (createRes.code !== '200') {
|
|
131
|
+
spin.fail('Failed to create module: ' + createRes.msg);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
moduleId = createRes.data.id;
|
|
135
|
+
spin.succeed('Module created');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
moduleId = pluginRes.data.id;
|
|
139
|
+
spin.succeed('Module found');
|
|
140
|
+
}
|
|
141
|
+
// 10. Create version
|
|
142
|
+
const versionSpin = log.spinner(`Creating version ${metadata.version}...`);
|
|
143
|
+
versionSpin.start();
|
|
144
|
+
const versionRes = await client.post(`/developer/modules/${moduleId}/versions`, {
|
|
145
|
+
version: metadata.version,
|
|
146
|
+
changelog: metadata.changelog,
|
|
147
|
+
});
|
|
148
|
+
if (versionRes.code !== '200') {
|
|
149
|
+
versionSpin.fail('Failed to create version: ' + versionRes.msg);
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const { uploadToken } = versionRes.data;
|
|
153
|
+
versionSpin.succeed('Upload token acquired');
|
|
154
|
+
// 11. Upload JAR
|
|
155
|
+
const uploadSpin = log.spinner('Uploading JAR...');
|
|
156
|
+
uploadSpin.start();
|
|
157
|
+
const fileBuffer = fs.readFileSync(resolvedJarPath);
|
|
158
|
+
const blob = new Blob([fileBuffer]);
|
|
159
|
+
const formData = new FormData();
|
|
160
|
+
formData.append('file', blob, path.basename(resolvedJarPath));
|
|
161
|
+
const uploadRes = await client.postMultipart(`/developer/modules/${moduleId}/upload`, formData, { 'X-Upload-Token': uploadToken });
|
|
162
|
+
if (uploadRes.code !== '200') {
|
|
163
|
+
uploadSpin.fail('Upload failed: ' + uploadRes.msg);
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
uploadSpin.succeed(`Published ${metadata.identifyString}@${metadata.version}`);
|
|
167
|
+
console.log();
|
|
168
|
+
log.info(`https://panel.ultikits.com/modules/${metadata.identifyString}`);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { readCredentials } from '../lib/config.js';
|
|
3
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
4
|
+
import * as log from '../lib/logger.js';
|
|
5
|
+
export function registerVersionsCommand(program) {
|
|
6
|
+
const versions = program
|
|
7
|
+
.command('versions')
|
|
8
|
+
.description('Manage module versions');
|
|
9
|
+
versions
|
|
10
|
+
.command('list <identifyString>')
|
|
11
|
+
.description('List versions of a module')
|
|
12
|
+
.action(async (identifyString) => {
|
|
13
|
+
const client = getClient();
|
|
14
|
+
// Get module ID
|
|
15
|
+
const infoRes = await client.get(`/plugin/get?identifyString=${identifyString}`);
|
|
16
|
+
if (infoRes.code !== '200' || !infoRes.data) {
|
|
17
|
+
log.error('Module not found: ' + identifyString);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const res = await client.get(`/plugin/${infoRes.data.id}/versions`);
|
|
21
|
+
if (res.code !== '200') {
|
|
22
|
+
log.error('Failed to list versions: ' + res.msg);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const versionsList = res.data;
|
|
26
|
+
if (versionsList.length === 0) {
|
|
27
|
+
log.info('No versions published yet.');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log();
|
|
31
|
+
for (const v of versionsList) {
|
|
32
|
+
const latest = v.is_latest ? ' (latest)' : '';
|
|
33
|
+
const size = v.file_size ? ` ${(v.file_size / 1024).toFixed(0)} KB` : '';
|
|
34
|
+
console.log(` ${v.version}${latest}${size} — ${v.created_at}`);
|
|
35
|
+
}
|
|
36
|
+
console.log();
|
|
37
|
+
log.info(`${versionsList.length} version(s)`);
|
|
38
|
+
});
|
|
39
|
+
versions
|
|
40
|
+
.command('delete <identifyString> <version>')
|
|
41
|
+
.description('Delete a specific version')
|
|
42
|
+
.action(async (identifyString, version) => {
|
|
43
|
+
const client = getClient();
|
|
44
|
+
const infoRes = await client.get(`/plugin/get?identifyString=${identifyString}`);
|
|
45
|
+
if (infoRes.code !== '200' || !infoRes.data) {
|
|
46
|
+
log.error('Module not found: ' + identifyString);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
const { confirm } = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'confirm',
|
|
52
|
+
name: 'confirm',
|
|
53
|
+
message: `Delete version ${version} of "${identifyString}"?`,
|
|
54
|
+
default: false,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
if (!confirm)
|
|
58
|
+
return;
|
|
59
|
+
const res = await client.delete(`/developer/modules/${infoRes.data.id}/versions/${version}`);
|
|
60
|
+
if (res.code !== '200') {
|
|
61
|
+
log.error('Failed to delete version: ' + res.msg);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
log.success(`Deleted version ${version}`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function getClient() {
|
|
68
|
+
const credentials = readCredentials();
|
|
69
|
+
const client = ApiClient.fromEnvOrCredentials(credentials);
|
|
70
|
+
if (!client) {
|
|
71
|
+
log.error('Not logged in. Run `ultikits login` or set ULTIKITS_TOKEN.');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
return client;
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=versions.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readCredentials } from '../lib/config.js';
|
|
2
|
+
import { ApiClient } from '../lib/api-client.js';
|
|
3
|
+
import * as log from '../lib/logger.js';
|
|
4
|
+
export function registerWhoamiCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command('whoami')
|
|
7
|
+
.description('Show current authenticated user')
|
|
8
|
+
.action(async () => {
|
|
9
|
+
const credentials = readCredentials();
|
|
10
|
+
const client = ApiClient.fromEnvOrCredentials(credentials);
|
|
11
|
+
if (!client) {
|
|
12
|
+
log.error('Not logged in. Run `ultikits login` or set ULTIKITS_TOKEN.');
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
const spin = log.spinner('Checking...');
|
|
16
|
+
spin.start();
|
|
17
|
+
try {
|
|
18
|
+
const res = await client.get('/developer/modules');
|
|
19
|
+
if (res.code === '200') {
|
|
20
|
+
spin.succeed('Authenticated as developer');
|
|
21
|
+
log.info(`Auth type: ${credentials?.type ?? 'env var'}`);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
spin.fail('Authentication failed: ' + res.msg);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
spin.fail('Failed to verify: ' + err.message);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Credentials, ApiResponse } from '../types.js';
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
private credentials;
|
|
5
|
+
constructor(credentials: Credentials);
|
|
6
|
+
static fromEnvOrCredentials(credentials: Credentials | null): ApiClient | null;
|
|
7
|
+
private getHeaders;
|
|
8
|
+
get<T = any>(path: string): Promise<ApiResponse<T>>;
|
|
9
|
+
post<T = any>(path: string, body?: any): Promise<ApiResponse<T>>;
|
|
10
|
+
postMultipart<T = any>(path: string, formData: FormData, extraHeaders?: Record<string, string>): Promise<ApiResponse<T>>;
|
|
11
|
+
delete<T = any>(path: string): Promise<ApiResponse<T>>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
export class ApiClient {
|
|
2
|
+
baseUrl;
|
|
3
|
+
credentials;
|
|
4
|
+
constructor(credentials) {
|
|
5
|
+
this.credentials = credentials;
|
|
6
|
+
this.baseUrl = credentials.api_url;
|
|
7
|
+
}
|
|
8
|
+
static fromEnvOrCredentials(credentials) {
|
|
9
|
+
const envToken = process.env.ULTIKITS_TOKEN;
|
|
10
|
+
if (envToken) {
|
|
11
|
+
return new ApiClient({
|
|
12
|
+
type: 'access_token',
|
|
13
|
+
token: envToken,
|
|
14
|
+
api_url: 'https://api.ultikits.com',
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
if (credentials) {
|
|
18
|
+
return new ApiClient(credentials);
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
getHeaders() {
|
|
23
|
+
const headers = {};
|
|
24
|
+
if (this.credentials.type === 'oauth') {
|
|
25
|
+
headers['Authorization'] = `Bearer ${this.credentials.access_token}`;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
headers['X-Access-Token'] = this.credentials.token;
|
|
29
|
+
}
|
|
30
|
+
return headers;
|
|
31
|
+
}
|
|
32
|
+
async get(path) {
|
|
33
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
headers: this.getHeaders(),
|
|
36
|
+
});
|
|
37
|
+
return response.json();
|
|
38
|
+
}
|
|
39
|
+
async post(path, body) {
|
|
40
|
+
const headers = {
|
|
41
|
+
...this.getHeaders(),
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
};
|
|
44
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers,
|
|
47
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
48
|
+
});
|
|
49
|
+
return response.json();
|
|
50
|
+
}
|
|
51
|
+
async postMultipart(path, formData, extraHeaders) {
|
|
52
|
+
const headers = {
|
|
53
|
+
...this.getHeaders(),
|
|
54
|
+
...(extraHeaders ?? {}),
|
|
55
|
+
};
|
|
56
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers,
|
|
59
|
+
body: formData,
|
|
60
|
+
});
|
|
61
|
+
return response.json();
|
|
62
|
+
}
|
|
63
|
+
async delete(path) {
|
|
64
|
+
const response = await fetch(`${this.baseUrl}${path}`, {
|
|
65
|
+
method: 'DELETE',
|
|
66
|
+
headers: this.getHeaders(),
|
|
67
|
+
});
|
|
68
|
+
return response.json();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Credentials } from '../types.js';
|
|
2
|
+
export declare function getConfigDir(): string;
|
|
3
|
+
export declare function readCredentials(configDir?: string): Credentials | null;
|
|
4
|
+
export declare function writeCredentials(credentials: Credentials, configDir?: string): void;
|
|
5
|
+
export declare function deleteCredentials(configDir?: string): void;
|
|
6
|
+
export declare function getApiUrl(credentials: Credentials | null): string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
const CREDENTIALS_FILE = 'credentials.json';
|
|
5
|
+
const DEFAULT_API_URL = 'https://api.ultikits.com';
|
|
6
|
+
export function getConfigDir() {
|
|
7
|
+
return path.join(os.homedir(), '.ultikits');
|
|
8
|
+
}
|
|
9
|
+
export function readCredentials(configDir) {
|
|
10
|
+
const dir = configDir ?? getConfigDir();
|
|
11
|
+
const filePath = path.join(dir, CREDENTIALS_FILE);
|
|
12
|
+
try {
|
|
13
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
14
|
+
return JSON.parse(content);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function writeCredentials(credentials, configDir) {
|
|
21
|
+
const dir = configDir ?? getConfigDir();
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
23
|
+
const filePath = path.join(dir, CREDENTIALS_FILE);
|
|
24
|
+
fs.writeFileSync(filePath, JSON.stringify(credentials, null, 2), { mode: 0o600 });
|
|
25
|
+
}
|
|
26
|
+
export function deleteCredentials(configDir) {
|
|
27
|
+
const dir = configDir ?? getConfigDir();
|
|
28
|
+
const filePath = path.join(dir, CREDENTIALS_FILE);
|
|
29
|
+
try {
|
|
30
|
+
fs.unlinkSync(filePath);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Already gone
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function getApiUrl(credentials) {
|
|
37
|
+
return credentials?.api_url ?? DEFAULT_API_URL;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import JSZip from 'jszip';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
export async function readPluginYml(jarPath) {
|
|
6
|
+
try {
|
|
7
|
+
const data = fs.readFileSync(jarPath);
|
|
8
|
+
const zip = await JSZip.loadAsync(data);
|
|
9
|
+
const pluginYml = zip.file('plugin.yml');
|
|
10
|
+
if (!pluginYml)
|
|
11
|
+
return null;
|
|
12
|
+
const content = await pluginYml.async('text');
|
|
13
|
+
const parsed = yaml.load(content);
|
|
14
|
+
return {
|
|
15
|
+
name: parsed.name,
|
|
16
|
+
version: String(parsed.version),
|
|
17
|
+
description: parsed.description,
|
|
18
|
+
authors: parsed.authors,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function findJarInTarget(dir) {
|
|
26
|
+
const targetDir = path.join(dir ?? process.cwd(), 'target');
|
|
27
|
+
try {
|
|
28
|
+
const files = fs.readdirSync(targetDir);
|
|
29
|
+
const jars = files.filter((f) => f.endsWith('.jar') && !f.endsWith('-sources.jar') && !f.endsWith('-javadoc.jar'));
|
|
30
|
+
if (jars.length === 0)
|
|
31
|
+
return null;
|
|
32
|
+
if (jars.length === 1)
|
|
33
|
+
return path.join(targetDir, jars[0]);
|
|
34
|
+
// Multiple JARs — prefer the shaded/fat JAR (without classifier)
|
|
35
|
+
const simple = jars.find((f) => !f.includes('-original'));
|
|
36
|
+
return path.join(targetDir, simple ?? jars[0]);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=jar-reader.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function success(msg: string): void;
|
|
2
|
+
export declare function error(msg: string): void;
|
|
3
|
+
export declare function info(msg: string): void;
|
|
4
|
+
export declare function warn(msg: string): void;
|
|
5
|
+
export declare function spinner(text: string): import("ora").Ora;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
export function success(msg) {
|
|
4
|
+
console.log(chalk.green('✓') + ' ' + msg);
|
|
5
|
+
}
|
|
6
|
+
export function error(msg) {
|
|
7
|
+
console.error(chalk.red('✗') + ' ' + msg);
|
|
8
|
+
}
|
|
9
|
+
export function info(msg) {
|
|
10
|
+
console.log(chalk.blue('ℹ') + ' ' + msg);
|
|
11
|
+
}
|
|
12
|
+
export function warn(msg) {
|
|
13
|
+
console.log(chalk.yellow('⚠') + ' ' + msg);
|
|
14
|
+
}
|
|
15
|
+
export function spinner(text) {
|
|
16
|
+
return ora({ text, color: 'cyan' });
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const CONFIG_FILE = 'ultikits.json';
|
|
4
|
+
export function readProjectConfig(dir) {
|
|
5
|
+
const filePath = path.join(dir ?? process.cwd(), CONFIG_FILE);
|
|
6
|
+
try {
|
|
7
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
8
|
+
return JSON.parse(content);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function writeProjectConfig(config, dir) {
|
|
15
|
+
const filePath = path.join(dir ?? process.cwd(), CONFIG_FILE);
|
|
16
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n');
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=project-config.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface OAuthCredentials {
|
|
2
|
+
type: 'oauth';
|
|
3
|
+
access_token: string;
|
|
4
|
+
refresh_token: string;
|
|
5
|
+
expires_at: string;
|
|
6
|
+
api_url: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AccessTokenCredentials {
|
|
9
|
+
type: 'access_token';
|
|
10
|
+
token: string;
|
|
11
|
+
api_url: string;
|
|
12
|
+
}
|
|
13
|
+
export type Credentials = OAuthCredentials | AccessTokenCredentials;
|
|
14
|
+
export interface ProjectConfig {
|
|
15
|
+
identifyString: string;
|
|
16
|
+
name: string;
|
|
17
|
+
shortDescription?: string;
|
|
18
|
+
categoryId?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface PluginYml {
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
authors?: string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ModuleMetadata {
|
|
27
|
+
identifyString: string;
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
shortDescription?: string;
|
|
31
|
+
changelog?: string;
|
|
32
|
+
categoryId?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface ApiResponse<T = any> {
|
|
35
|
+
code: string;
|
|
36
|
+
msg: string;
|
|
37
|
+
data?: T;
|
|
38
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ultikits/cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool for publishing UltiTools plugin modules to UltiCloud marketplace",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ultikits": "dist/bin/ultikits.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"dev": "tsc --watch",
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"prepublishOnly": "npm run build && npm test"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ultikits",
|
|
18
|
+
"minecraft",
|
|
19
|
+
"spigot",
|
|
20
|
+
"paper",
|
|
21
|
+
"plugin",
|
|
22
|
+
"cli",
|
|
23
|
+
"marketplace",
|
|
24
|
+
"module",
|
|
25
|
+
"publish"
|
|
26
|
+
],
|
|
27
|
+
"author": "UltiKits <dev@ultikits.com> (https://ultikits.com)",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/UltiKits/ultikits-cli.git"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/UltiKits/ultikits-cli#readme",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/UltiKits/ultikits-cli/issues"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18"
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist/**/*.js",
|
|
42
|
+
"dist/**/*.d.ts",
|
|
43
|
+
"LICENSE",
|
|
44
|
+
"README.md",
|
|
45
|
+
"CHANGELOG.md"
|
|
46
|
+
],
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"commander": "^12.1.0",
|
|
49
|
+
"inquirer": "^9.3.0",
|
|
50
|
+
"ora": "^8.1.0",
|
|
51
|
+
"chalk": "^5.3.0",
|
|
52
|
+
"jszip": "^3.10.1",
|
|
53
|
+
"js-yaml": "^4.1.0",
|
|
54
|
+
"open": "^10.1.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/inquirer": "^9.0.7",
|
|
58
|
+
"@types/js-yaml": "^4.0.9",
|
|
59
|
+
"@types/node": "^20.14.0",
|
|
60
|
+
"typescript": "^5.5.0",
|
|
61
|
+
"vitest": "^2.0.0"
|
|
62
|
+
}
|
|
63
|
+
}
|