@ptkl/toolkit 0.6.1 → 0.7.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 +139 -0
- package/dist/bin/toolkit.js +20 -1
- package/dist/commands/apps.js +29 -7
- package/dist/commands/profile.js +63 -19
- package/dist/lib/util.js +16 -3
- package/package.json +25 -8
package/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Protokol Toolkit
|
|
2
|
+
|
|
3
|
+
A command-line toolkit for managing Protokol platform applications, profiles, functions, and components.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @ptkl/toolkit
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Getting Started
|
|
12
|
+
|
|
13
|
+
### 1. Initialize the Toolkit
|
|
14
|
+
|
|
15
|
+
Before using the toolkit, initialize it to create the configuration directory:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
ptkl init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
This creates a `~/.ptkl` directory where profiles and settings are stored.
|
|
22
|
+
|
|
23
|
+
### 2. Create a Profile
|
|
24
|
+
|
|
25
|
+
A profile stores your authentication credentials and connection settings for a Protokol instance.
|
|
26
|
+
|
|
27
|
+
#### Secure Method (Recommended)
|
|
28
|
+
|
|
29
|
+
Omit the password or use `--password` flag without a value to be prompted securely:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ptkl profile new \
|
|
33
|
+
-n production \
|
|
34
|
+
-u user@example.com \
|
|
35
|
+
-P my-project \
|
|
36
|
+
-h https://api.example.com \
|
|
37
|
+
--password
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You'll be prompted to enter your password securely (hidden input).
|
|
41
|
+
|
|
42
|
+
#### With Password in CLI (Not Recommended)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
ptkl profile new \
|
|
46
|
+
-n production \
|
|
47
|
+
-u user@example.com \
|
|
48
|
+
-P my-project \
|
|
49
|
+
-h https://api.example.com \
|
|
50
|
+
--password "myPassword"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
⚠️ **Security Warning**: This will show a warning as the password may be visible in shell history.
|
|
54
|
+
|
|
55
|
+
#### Options
|
|
56
|
+
|
|
57
|
+
- `-n, --name <name>` - Profile name (e.g., "production", "staging")
|
|
58
|
+
- `-u, --username <username>` - Email or API username
|
|
59
|
+
- `-P, --project <project>` - Project identifier
|
|
60
|
+
- `-h, --host <host>` - API host URL
|
|
61
|
+
- `-p, --password [password]` - Password (optional value for secure prompt)
|
|
62
|
+
|
|
63
|
+
## Profile Management
|
|
64
|
+
|
|
65
|
+
### List All Profiles
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
ptkl profile list
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Shows all available profiles. The currently active profile is marked with `*`.
|
|
72
|
+
|
|
73
|
+
### View Current Profile
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
ptkl profile
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Switch to a Different Profile
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ptkl profile use staging
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Sets `staging` as the active profile for all subsequent commands.
|
|
86
|
+
|
|
87
|
+
### Inspect Profile Details
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
ptkl profile inspect production
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Re-authenticate a Profile
|
|
94
|
+
|
|
95
|
+
If your token expires or you need to update credentials:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ptkl profile auth --password
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Or for a specific profile:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ptkl profile auth --profile production --password
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
#### Options
|
|
108
|
+
|
|
109
|
+
- `-p, --password [password]` - Password (omit value for secure prompt)
|
|
110
|
+
- `-t, --token <token>` - Directly provide a token
|
|
111
|
+
- `-P, --project <project>` - Update the project
|
|
112
|
+
|
|
113
|
+
### Delete a Profile
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
ptkl profile delete staging
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Using the --profile Flag
|
|
120
|
+
|
|
121
|
+
You can override the active profile for any command using the global `--profile` flag:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
ptkl --profile production apps upload -a app -d ./my-app
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Version
|
|
128
|
+
|
|
129
|
+
Current version: 0.7.0
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
GNU General Public License v3.0 (GPL-3.0)
|
|
134
|
+
|
|
135
|
+
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
136
|
+
|
|
137
|
+
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
138
|
+
|
|
139
|
+
Any modifications or derivative works must also be licensed under GPL-3.0 and the source code must be made available.
|
package/dist/bin/toolkit.js
CHANGED
|
@@ -2,8 +2,27 @@
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { commands } from "../commands/index.js";
|
|
4
4
|
const program = new Command();
|
|
5
|
+
// Add global --profile option
|
|
6
|
+
program.option('--profile <profileName>', 'Use a specific profile for this command');
|
|
7
|
+
// Add a preAction hook to handle the global --profile flag
|
|
8
|
+
program.hook('preAction', (thisCommand, actionCommand) => {
|
|
9
|
+
const opts = thisCommand.opts();
|
|
10
|
+
if (opts.profile) {
|
|
11
|
+
// Store the profile name in a global variable or process.env so commands can access it
|
|
12
|
+
process.env.PTKL_ACTIVE_PROFILE = opts.profile;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
5
15
|
commands.forEach(c => program.addCommand(c));
|
|
6
|
-
program.parseAsync(process.argv)
|
|
16
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
17
|
+
const { response } = err;
|
|
18
|
+
if (response && response.data) {
|
|
19
|
+
console.error(response.data.message);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.error(err.message || err);
|
|
23
|
+
}
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
7
26
|
process.on('uncaughtException', (err) => {
|
|
8
27
|
const { response } = err;
|
|
9
28
|
if (response && response.data) {
|
package/dist/commands/apps.js
CHANGED
|
@@ -78,10 +78,10 @@ class AppsCommand {
|
|
|
78
78
|
const profile = util.getCurrentProfile();
|
|
79
79
|
const client = new Api({ token: profile.token, host: profile.host }).app(apptype);
|
|
80
80
|
try {
|
|
81
|
-
|
|
81
|
+
const chunks = [];
|
|
82
82
|
const bufferStream = new Writable({
|
|
83
83
|
write(chunk, encoding, callback) {
|
|
84
|
-
|
|
84
|
+
chunks.push(Buffer.from(chunk));
|
|
85
85
|
callback();
|
|
86
86
|
},
|
|
87
87
|
});
|
|
@@ -93,14 +93,35 @@ class AppsCommand {
|
|
|
93
93
|
.on('error', reject);
|
|
94
94
|
});
|
|
95
95
|
console.log('Archive created successfully');
|
|
96
|
-
//
|
|
96
|
+
// Convert chunks to a single ArrayBuffer to avoid Node Buffer issues
|
|
97
|
+
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
98
|
+
const arrayBuffer = new ArrayBuffer(totalLength);
|
|
99
|
+
const uint8View = new Uint8Array(arrayBuffer);
|
|
100
|
+
let offset = 0;
|
|
101
|
+
for (const chunk of chunks) {
|
|
102
|
+
uint8View.set(new Uint8Array(chunk), offset);
|
|
103
|
+
offset += chunk.length;
|
|
104
|
+
}
|
|
105
|
+
console.log('ArrayBuffer size:', arrayBuffer.byteLength);
|
|
106
|
+
// Create FormData with ArrayBuffer
|
|
97
107
|
const formData = new FormData();
|
|
98
|
-
const blob = new Blob([
|
|
108
|
+
const blob = new Blob([arrayBuffer], { type: 'application/gzip' });
|
|
109
|
+
console.log('Blob size:', blob.size);
|
|
99
110
|
formData.append('app', blob, 'app.tar.gz');
|
|
100
111
|
if (build) {
|
|
101
112
|
formData.append('build', 'true');
|
|
102
113
|
}
|
|
103
|
-
|
|
114
|
+
console.log('About to call client.upload...');
|
|
115
|
+
try {
|
|
116
|
+
const result = await client.upload(formData);
|
|
117
|
+
console.log('Upload successful');
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.log('Upload failed with error:', error.message);
|
|
122
|
+
console.log('Error details:', error);
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
104
125
|
}
|
|
105
126
|
catch (error) {
|
|
106
127
|
throw error;
|
|
@@ -164,14 +185,15 @@ class AppsCommand {
|
|
|
164
185
|
const manifestPath = `${distPath}/manifest.json`;
|
|
165
186
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 4));
|
|
166
187
|
if (upload) {
|
|
167
|
-
|
|
188
|
+
const chunks = [];
|
|
168
189
|
const bufferStream = new Writable({
|
|
169
190
|
write(chunk, encoding, callback) {
|
|
170
|
-
|
|
191
|
+
chunks.push(chunk);
|
|
171
192
|
callback();
|
|
172
193
|
},
|
|
173
194
|
});
|
|
174
195
|
c({ gzip: true, cwd: distPath }, ['.']).pipe(bufferStream).on('finish', () => {
|
|
196
|
+
const buffer = Buffer.concat(chunks);
|
|
175
197
|
client.app().bundleUpload(buffer).then(() => {
|
|
176
198
|
console.log('Bundle uploaded successfully');
|
|
177
199
|
})
|
package/dist/commands/profile.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import util from "../lib/util.js";
|
|
3
3
|
import cli from "../lib/cli.js";
|
|
4
4
|
import { APIUser, User } from "@ptkl/sdk";
|
|
5
|
+
import password from '@inquirer/password';
|
|
5
6
|
class ProfileCommand {
|
|
6
7
|
register() {
|
|
7
8
|
return new Command('profile')
|
|
@@ -12,14 +13,15 @@ class ProfileCommand {
|
|
|
12
13
|
.description('Create new profile')
|
|
13
14
|
.option("-n, --name <name>")
|
|
14
15
|
.option("-u, --username <username>")
|
|
15
|
-
.option("-p, --password
|
|
16
|
+
.option("-p, --password [password]", "Password (optional value - if omitted, you will be prompted securely)")
|
|
16
17
|
.option("-P, --project <project>")
|
|
17
18
|
.option("-h, --host <host>")
|
|
18
19
|
.action(this.new))
|
|
19
20
|
.addCommand(new Command('auth')
|
|
20
21
|
.description('Profile auth')
|
|
21
|
-
.option("-p, --password
|
|
22
|
+
.option("-p, --password [password]", "Password (optional value - if omitted, you will be prompted securely)")
|
|
22
23
|
.option("-t, --token <token>")
|
|
24
|
+
.option("-P, --project <project>")
|
|
23
25
|
.action(this.auth))
|
|
24
26
|
.addCommand(new Command('list')
|
|
25
27
|
.description('List all available profiles')
|
|
@@ -43,23 +45,39 @@ class ProfileCommand {
|
|
|
43
45
|
}
|
|
44
46
|
async new(options) {
|
|
45
47
|
// try to find profile with the name
|
|
46
|
-
const { name, username,
|
|
48
|
+
const { name, username, project, host } = options;
|
|
49
|
+
let userPassword = options.password;
|
|
50
|
+
// Handle password input
|
|
51
|
+
if (typeof userPassword === 'string' && userPassword.length > 0) {
|
|
52
|
+
// Password was provided as CLI argument - show security warning
|
|
53
|
+
console.warn('⚠️ WARNING: Providing password via CLI argument is insecure and may be visible in shell history.');
|
|
54
|
+
console.warn('⚠️ Consider using --password flag without a value to be prompted securely.\n');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// Prompt for password securely (either --password flag was used without value, or not provided at all)
|
|
58
|
+
userPassword = await password({
|
|
59
|
+
message: 'Enter password:',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
47
62
|
let token = "";
|
|
48
63
|
// if username is email then login to platform as user
|
|
49
64
|
if (username && username.includes('@')) {
|
|
50
65
|
const user = new User({
|
|
51
|
-
username,
|
|
52
|
-
password,
|
|
53
66
|
host,
|
|
54
67
|
});
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
try {
|
|
69
|
+
const { data } = await user.auth(username, userPassword, project);
|
|
70
|
+
token = "SESSION:" + data.token;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
57
75
|
}
|
|
58
76
|
else {
|
|
59
77
|
const user = new APIUser({
|
|
60
78
|
host,
|
|
61
79
|
});
|
|
62
|
-
const { data } = await user.auth(username,
|
|
80
|
+
const { data } = await user.auth(username, userPassword, project);
|
|
63
81
|
token = data.Token;
|
|
64
82
|
}
|
|
65
83
|
const profiles = util.getProfiles() ?? [];
|
|
@@ -72,25 +90,51 @@ class ProfileCommand {
|
|
|
72
90
|
name,
|
|
73
91
|
username,
|
|
74
92
|
token,
|
|
75
|
-
host
|
|
93
|
+
host,
|
|
94
|
+
project,
|
|
76
95
|
});
|
|
77
96
|
util.updateProfiles(profiles);
|
|
78
97
|
}
|
|
79
98
|
async auth(options) {
|
|
80
99
|
// try to find profile with the name
|
|
81
|
-
|
|
100
|
+
let { token, project } = options;
|
|
101
|
+
let userPassword = options.password;
|
|
82
102
|
const profile = util.getCurrentProfile();
|
|
83
103
|
if (!token) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
// Handle password input
|
|
105
|
+
if (typeof userPassword === 'string' && userPassword.length > 0) {
|
|
106
|
+
// Password was provided as CLI argument - show security warning
|
|
107
|
+
console.warn('⚠️ WARNING: Providing password via CLI argument is insecure and may be visible in shell history.');
|
|
108
|
+
console.warn('⚠️ Consider using --password flag without a value to be prompted securely.\n');
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Prompt for password securely
|
|
112
|
+
userPassword = await password({
|
|
113
|
+
message: 'Enter password:',
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (profile.username && profile.username.includes('@')) {
|
|
117
|
+
const user = new User({
|
|
118
|
+
host: profile.host,
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
const { data } = await user.auth(profile.username, userPassword, project ?? profile.project);
|
|
122
|
+
token = "SESSION:" + data.token;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
const user = new APIUser({
|
|
130
|
+
host: profile.host,
|
|
131
|
+
});
|
|
132
|
+
const { data } = await user.auth(profile.username, userPassword, project ?? profile.project);
|
|
133
|
+
token = data.Token;
|
|
134
|
+
}
|
|
93
135
|
}
|
|
136
|
+
profile.project = project ?? profile.project;
|
|
137
|
+
profile.token = token;
|
|
94
138
|
util.updateProfile(profile);
|
|
95
139
|
}
|
|
96
140
|
list() {
|
package/dist/lib/util.js
CHANGED
|
@@ -48,9 +48,22 @@ export default class Util {
|
|
|
48
48
|
}
|
|
49
49
|
static getCurrentProfile() {
|
|
50
50
|
const profiles = Util.getProfiles();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
// Check if --profile flag was used globally
|
|
52
|
+
const profileNameFromFlag = process.env.PTKL_ACTIVE_PROFILE;
|
|
53
|
+
let profile;
|
|
54
|
+
if (profileNameFromFlag) {
|
|
55
|
+
// Use the profile specified via --profile flag
|
|
56
|
+
profile = profiles?.find(p => p.name === profileNameFromFlag);
|
|
57
|
+
if (!profile) {
|
|
58
|
+
throw new Error(`Profile '${profileNameFromFlag}' not found. Available profiles: ${profiles?.map(p => p.name).join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Fall back to current profile
|
|
63
|
+
profile = profiles?.find(p => p.current);
|
|
64
|
+
if (!profile) {
|
|
65
|
+
throw new Error("Current profile is not set. Run command `profile use {profile-name}` or use --profile flag");
|
|
66
|
+
}
|
|
54
67
|
}
|
|
55
68
|
return profile;
|
|
56
69
|
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ptkl/toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "A command-line toolkit for managing Protokol platform applications, profiles, functions, and components",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"protokol",
|
|
7
|
+
"cli",
|
|
8
|
+
"toolkit",
|
|
9
|
+
"deployment",
|
|
10
|
+
"serverless",
|
|
11
|
+
"functions"
|
|
12
|
+
],
|
|
13
|
+
"author": "Protokol",
|
|
14
|
+
"license": "GPL-3.0",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://your-gitea-instance.com/your-org/protokol-toolkit.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://your-gitea-instance.com/your-org/protokol-toolkit/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://your-gitea-instance.com/your-org/protokol-toolkit#readme",
|
|
23
|
+
"type": "module",
|
|
4
24
|
"scripts": {
|
|
5
25
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
6
26
|
"build": "npx tsc",
|
|
7
|
-
"toolkit": "tsc --build && node dist/bin/toolkit"
|
|
27
|
+
"toolkit": "tsc --build && node dist/bin/toolkit",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
8
29
|
},
|
|
9
|
-
"keywords": [],
|
|
10
|
-
"author": "",
|
|
11
|
-
"license": "MIT",
|
|
12
|
-
"description": "",
|
|
13
|
-
"type": "module",
|
|
14
30
|
"dependencies": {
|
|
15
31
|
"@babel/standalone": "^7.26.10",
|
|
16
|
-
"@
|
|
32
|
+
"@inquirer/password": "^4.0.21",
|
|
33
|
+
"@ptkl/sdk": "^0.9.12",
|
|
17
34
|
"@types/axios": "^0.14.0",
|
|
18
35
|
"@types/commander": "^2.12.2",
|
|
19
36
|
"@types/js-yaml": "^4.0.9",
|