@upfluxhq/cli 0.1.0-beta.1
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 +195 -0
- package/dist/client/apiClient.d.ts +1 -0
- package/dist/client/apiClient.js +43 -0
- package/dist/commands/generate-manifest.d.ts +12 -0
- package/dist/commands/generate-manifest.js +152 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +124 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.js +83 -0
- package/dist/commands/release.d.ts +3 -0
- package/dist/commands/release.js +554 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.js +90 -0
- package/dist/config/configStore.d.ts +73 -0
- package/dist/config/configStore.js +174 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +23 -0
- package/dist/types/index.d.ts +85 -0
- package/dist/types/index.js +2 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# @upfluxhq/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for managing Upflux OTA updates.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @upfluxhq/cli
|
|
9
|
+
# or
|
|
10
|
+
pnpm add -g @upfluxhq/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Authenticate with your publish keys (supports multiple!)
|
|
17
|
+
upflux login --key <staging-publish-key>
|
|
18
|
+
upflux login --key <production-publish-key>
|
|
19
|
+
|
|
20
|
+
# 2. Upload a release (will prompt if multiple deployments)
|
|
21
|
+
upflux release --platform android --label v1.0.0 --mandatory
|
|
22
|
+
|
|
23
|
+
# 3. Check release status
|
|
24
|
+
upflux status
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Commands
|
|
28
|
+
|
|
29
|
+
### `login`
|
|
30
|
+
|
|
31
|
+
Authenticate with the Upflux server. **Supports multiple credentials** - each login adds a new deployment.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
upflux login --key <publishKey>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Option | Description |
|
|
38
|
+
|--------|-------------|
|
|
39
|
+
| `-k, --key <publishKey>` | Publish key for authentication |
|
|
40
|
+
|
|
41
|
+
**Example output with multiple logins:**
|
|
42
|
+
```
|
|
43
|
+
✓ Login successful!
|
|
44
|
+
App ID: app-970384d3
|
|
45
|
+
Platform: android
|
|
46
|
+
Deployment: production
|
|
47
|
+
|
|
48
|
+
Stored credentials (2):
|
|
49
|
+
→ 1. production (app-970384d3)
|
|
50
|
+
2. staging (app-970384d3)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### `logout`
|
|
56
|
+
|
|
57
|
+
Remove stored credentials.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
upflux logout [options]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
| Option | Description |
|
|
64
|
+
|--------|-------------|
|
|
65
|
+
| `--all` | Remove all credentials |
|
|
66
|
+
|
|
67
|
+
**Example:**
|
|
68
|
+
```bash
|
|
69
|
+
# Interactive selection to remove one
|
|
70
|
+
upflux logout
|
|
71
|
+
|
|
72
|
+
# Remove all credentials
|
|
73
|
+
upflux logout --all
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### `release`
|
|
79
|
+
|
|
80
|
+
Bundle and upload a new release. **Prompts to select deployment** if multiple are stored.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
upflux release [options]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
| Option | Required | Description |
|
|
87
|
+
|--------|----------|-------------|
|
|
88
|
+
| `-l, --label <label>` | ✓ | Release label (e.g., v1.0.0) |
|
|
89
|
+
| `-p, --platform <platform>` | | Target platform (`ios` or `android`, auto-detected) |
|
|
90
|
+
| `-a, --app <appId>` | | Application ID (auto-detected) |
|
|
91
|
+
| `-d, --deployment <deploymentId>` | | Deployment ID (prompts if multiple) |
|
|
92
|
+
| `-e, --entry-file <path>` | | Entry file for bundler (default: `index.js`) |
|
|
93
|
+
| `-o, --output-dir <path>` | | Output directory for bundle (default: `./dist`) |
|
|
94
|
+
| `-b, --bundle <path>` | | Skip bundling, use existing bundle file |
|
|
95
|
+
| `--assets <paths...>` | | Skip auto-detection, use these asset files |
|
|
96
|
+
| `-m, --mandatory` | | Mark update as mandatory |
|
|
97
|
+
| `-r, --rollout <number>` | | Rollout percentage (0-100, default: 100) |
|
|
98
|
+
| `-v, --version-range <range>` | | Binary version range (e.g., >=1.0.0) |
|
|
99
|
+
| `--dev` | | Create development bundle |
|
|
100
|
+
| `--skip-bundle` | | Skip bundling, use existing files in output dir |
|
|
101
|
+
|
|
102
|
+
**Example with deployment selection:**
|
|
103
|
+
```
|
|
104
|
+
$ upflux release --platform android --label v1.2.0
|
|
105
|
+
|
|
106
|
+
? Select deployment to release to:
|
|
107
|
+
❯ production (app-970384d3)
|
|
108
|
+
staging (app-970384d3)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### `status`
|
|
114
|
+
|
|
115
|
+
Check release status for an app or deployment.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
upflux status [options]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Option | Description |
|
|
122
|
+
|--------|-------------|
|
|
123
|
+
| `-a, --app <appId>` | Filter by application ID |
|
|
124
|
+
| `-d, --deployment <deploymentId>` | Filter by deployment ID |
|
|
125
|
+
| `-n, --limit <number>` | Number of releases to show (default: 10) |
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
### `generate-manifest`
|
|
130
|
+
|
|
131
|
+
Fetch and display the manifest for a specific release.
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
upflux generate-manifest [options]
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| Option | Required | Description |
|
|
138
|
+
|--------|----------|-------------|
|
|
139
|
+
| `-r, --release-id <id>` | ✓ | Release ID (MongoDB ObjectId) |
|
|
140
|
+
| `-o, --output <path>` | | Save manifest to file |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Configuration
|
|
145
|
+
|
|
146
|
+
### Environment Variables
|
|
147
|
+
|
|
148
|
+
| Variable | Description | Default |
|
|
149
|
+
|----------|-------------|---------|
|
|
150
|
+
| `UPFLUX_API_URL` | API server URL | `http://localhost:3000/api` |
|
|
151
|
+
|
|
152
|
+
### Auth File
|
|
153
|
+
|
|
154
|
+
The CLI stores credentials in `.upflux` in the current directory:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"credentials": [
|
|
159
|
+
{
|
|
160
|
+
"key": "pk_xxx...",
|
|
161
|
+
"appId": "app-970384d3",
|
|
162
|
+
"platform": "android",
|
|
163
|
+
"deploymentId": "6933e683aa32fe18a1311bbb",
|
|
164
|
+
"deploymentName": "production"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"key": "pk_yyy...",
|
|
168
|
+
"appId": "app-970384d3",
|
|
169
|
+
"platform": "android",
|
|
170
|
+
"deploymentId": "6933e683aa32fe18a1311ccc",
|
|
171
|
+
"deploymentName": "staging"
|
|
172
|
+
}
|
|
173
|
+
],
|
|
174
|
+
"activeIndex": 0
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Development
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
# Install dependencies
|
|
184
|
+
pnpm install
|
|
185
|
+
|
|
186
|
+
# Build
|
|
187
|
+
pnpm run build
|
|
188
|
+
|
|
189
|
+
# Run locally
|
|
190
|
+
node dist/index.js --help
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const apiClient: import("axios").AxiosInstance;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.apiClient = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const configStore_1 = require("../config/configStore");
|
|
9
|
+
/**
|
|
10
|
+
* Default API base URL (can be overridden via environment variable)
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULT_BASE_URL = "https://api.upflux.in/api"; //"http://localhost:3000/api";
|
|
13
|
+
console.log(process.env.UPFLUX_API_URL || DEFAULT_BASE_URL);
|
|
14
|
+
exports.apiClient = axios_1.default.create({
|
|
15
|
+
baseURL: process.env.UPFLUX_API_URL || DEFAULT_BASE_URL,
|
|
16
|
+
});
|
|
17
|
+
/**
|
|
18
|
+
* Request interceptor to add publish key authentication.
|
|
19
|
+
* Uses x-publish-key header as expected by the backend middleware.
|
|
20
|
+
*/
|
|
21
|
+
exports.apiClient.interceptors.request.use((config) => {
|
|
22
|
+
const key = configStore_1.configStore.getAuthKey();
|
|
23
|
+
if (key) {
|
|
24
|
+
config.headers["x-publish-key"] = key;
|
|
25
|
+
}
|
|
26
|
+
return config;
|
|
27
|
+
});
|
|
28
|
+
/**
|
|
29
|
+
* Response interceptor for error handling
|
|
30
|
+
*/
|
|
31
|
+
exports.apiClient.interceptors.response.use((response) => response, (error) => {
|
|
32
|
+
var _a;
|
|
33
|
+
if (error.response) {
|
|
34
|
+
// Server responded with error status
|
|
35
|
+
const message = ((_a = error.response.data) === null || _a === void 0 ? void 0 : _a.error) || error.response.statusText;
|
|
36
|
+
error.message = `API Error (${error.response.status}): ${message}`;
|
|
37
|
+
}
|
|
38
|
+
else if (error.request) {
|
|
39
|
+
// Request made but no response
|
|
40
|
+
error.message = "Unable to reach API server. Is it running?";
|
|
41
|
+
}
|
|
42
|
+
return Promise.reject(error);
|
|
43
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Generate Manifest Command
|
|
4
|
+
*
|
|
5
|
+
* Fetches the release manifest from the API and optionally saves to file.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* upflux generate-manifest --release-id <mongoId>
|
|
9
|
+
* upflux generate-manifest --release-id <mongoId> --output ./manifest.json
|
|
10
|
+
*/
|
|
11
|
+
declare const generateManifest: Command;
|
|
12
|
+
export default generateManifest;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const commander_1 = require("commander");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
43
|
+
const apiClient_1 = require("../client/apiClient");
|
|
44
|
+
/**
|
|
45
|
+
* Validate MongoDB ObjectId format
|
|
46
|
+
*/
|
|
47
|
+
function isValidObjectId(id) {
|
|
48
|
+
return /^[a-fA-F0-9]{24}$/.test(id);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Validate output path is writable
|
|
52
|
+
*/
|
|
53
|
+
function isWritablePath(filePath) {
|
|
54
|
+
try {
|
|
55
|
+
const dir = path.dirname(filePath);
|
|
56
|
+
// Check if directory exists or can be created
|
|
57
|
+
if (!fs.existsSync(dir)) {
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
// Check if we can write to this location
|
|
61
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
catch (_a) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate Manifest Command
|
|
70
|
+
*
|
|
71
|
+
* Fetches the release manifest from the API and optionally saves to file.
|
|
72
|
+
*
|
|
73
|
+
* Usage:
|
|
74
|
+
* upflux generate-manifest --release-id <mongoId>
|
|
75
|
+
* upflux generate-manifest --release-id <mongoId> --output ./manifest.json
|
|
76
|
+
*/
|
|
77
|
+
const generateManifest = new commander_1.Command("generate-manifest")
|
|
78
|
+
.description("Generate a release manifest file")
|
|
79
|
+
.requiredOption("-r, --release-id <id>", "Release ID (MongoDB ObjectId)")
|
|
80
|
+
.option("-o, --output <path>", "Output file path (optional, prints to stdout if not specified)")
|
|
81
|
+
.action(async (opts) => {
|
|
82
|
+
var _a, _b, _c;
|
|
83
|
+
try {
|
|
84
|
+
const releaseId = (_a = opts.releaseId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
85
|
+
// Validation 1: Release ID is provided
|
|
86
|
+
if (!releaseId) {
|
|
87
|
+
console.error(chalk_1.default.red("✗ Release ID is required"));
|
|
88
|
+
console.error(chalk_1.default.gray("Use 'upflux status' to list available releases and their IDs"));
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
// Validation 2: Release ID format (MongoDB ObjectId)
|
|
92
|
+
if (!isValidObjectId(releaseId)) {
|
|
93
|
+
console.error(chalk_1.default.red("✗ Invalid release ID format"));
|
|
94
|
+
console.error(chalk_1.default.gray("Release ID should be a 24-character MongoDB ObjectId"));
|
|
95
|
+
console.error(chalk_1.default.gray("Example: 693507a19f6d6a79267633e8"));
|
|
96
|
+
console.error(chalk_1.default.gray("\nUse 'upflux status' to find valid release IDs"));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
// Validation 3: Output path is writable (if provided)
|
|
100
|
+
if (opts.output) {
|
|
101
|
+
const outputPath = path.resolve(opts.output);
|
|
102
|
+
if (!isWritablePath(outputPath)) {
|
|
103
|
+
console.error(chalk_1.default.red(`✗ Cannot write to: ${outputPath}`));
|
|
104
|
+
console.error(chalk_1.default.gray("Check that the directory exists and you have write permissions"));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log(chalk_1.default.blue(`⚡ Fetching manifest for release ${releaseId}...\n`));
|
|
109
|
+
// Fetch manifest from API
|
|
110
|
+
const response = await apiClient_1.apiClient.get(`/releases/${releaseId}/manifest`);
|
|
111
|
+
const manifest = response.data;
|
|
112
|
+
// Validation 4: Manifest data is valid
|
|
113
|
+
if (!manifest || !manifest.releaseLabel || !manifest.bundleUrl) {
|
|
114
|
+
console.error(chalk_1.default.red("✗ Received invalid manifest data from server"));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
// Pretty-print the manifest
|
|
118
|
+
const manifestJson = JSON.stringify(manifest, null, 2);
|
|
119
|
+
if (opts.output) {
|
|
120
|
+
// Write to file
|
|
121
|
+
const outputPath = path.resolve(opts.output);
|
|
122
|
+
fs.writeFileSync(outputPath, manifestJson);
|
|
123
|
+
console.log(chalk_1.default.green(`✓ Manifest saved to: ${outputPath}`));
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Print to stdout
|
|
127
|
+
console.log(chalk_1.default.white("Manifest:"));
|
|
128
|
+
console.log(chalk_1.default.gray("─────────────────────────────────"));
|
|
129
|
+
console.log(manifestJson);
|
|
130
|
+
console.log(chalk_1.default.gray("─────────────────────────────────"));
|
|
131
|
+
}
|
|
132
|
+
// Summary
|
|
133
|
+
console.log(chalk_1.default.green("\n✓ Manifest generated successfully!"));
|
|
134
|
+
console.log(` Label: ${manifest.releaseLabel}`);
|
|
135
|
+
console.log(` Bundle URL: ${manifest.bundleUrl}`);
|
|
136
|
+
console.log(` Assets: ${((_b = manifest.assets) === null || _b === void 0 ? void 0 : _b.length) || 0} file(s)`);
|
|
137
|
+
if (manifest.mandatory) {
|
|
138
|
+
console.log(chalk_1.default.red(` Mandatory: Yes`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 404) {
|
|
143
|
+
console.error(chalk_1.default.red(`✗ Release not found: ${opts.releaseId}`));
|
|
144
|
+
console.error(chalk_1.default.gray("Use 'upflux status' to list available releases."));
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
console.error(chalk_1.default.red(`✗ Failed to fetch manifest: ${error.message}`));
|
|
148
|
+
}
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
exports.default = generateManifest;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const commander_1 = require("commander");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const configStore_1 = require("../config/configStore");
|
|
9
|
+
const apiClient_1 = require("../client/apiClient");
|
|
10
|
+
/**
|
|
11
|
+
* Validate publish key format
|
|
12
|
+
* Expected format: upflux_pub_<prefix>_<secret>
|
|
13
|
+
*/
|
|
14
|
+
function validateKeyFormat(key) {
|
|
15
|
+
// Basic format check
|
|
16
|
+
if (!key || typeof key !== "string") {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
// Check minimum length (at least 20 chars for a valid key)
|
|
20
|
+
if (key.length < 20) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
// Check for common publish key prefixes
|
|
24
|
+
const validPrefixes = ["upflux_pub_", "upflux_cli_", "pk_"];
|
|
25
|
+
const hasValidPrefix = validPrefixes.some(prefix => key.startsWith(prefix));
|
|
26
|
+
// Also allow generic keys that are alphanumeric with underscores
|
|
27
|
+
const isAlphanumeric = /^[a-zA-Z0-9_-]+$/.test(key);
|
|
28
|
+
return hasValidPrefix || isAlphanumeric;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Verify key with the server
|
|
32
|
+
*/
|
|
33
|
+
async function verifyKeyWithServer(key) {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
try {
|
|
36
|
+
// Temporarily set the key for this request
|
|
37
|
+
const response = await apiClient_1.apiClient.get("/keys/verify", {
|
|
38
|
+
headers: { "x-publish-key": key }
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
valid: true,
|
|
42
|
+
appId: response.data.appId,
|
|
43
|
+
platform: response.data.platform,
|
|
44
|
+
deploymentId: response.data.deploymentId,
|
|
45
|
+
deploymentName: response.data.deploymentName,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
|
|
50
|
+
return { valid: false, error: "Invalid or expired key" };
|
|
51
|
+
}
|
|
52
|
+
if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 403) {
|
|
53
|
+
return { valid: false, error: "Key does not have publish permissions" };
|
|
54
|
+
}
|
|
55
|
+
// Network error - fail the verification
|
|
56
|
+
return { valid: false, error: "Unable to reach server. Check your connection and try again." };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const login = new commander_1.Command("login")
|
|
60
|
+
.description("Authenticate with Upflux server")
|
|
61
|
+
.requiredOption("-k, --key <publishKey>", "Publish key for authentication")
|
|
62
|
+
.action(async (opts) => {
|
|
63
|
+
var _a;
|
|
64
|
+
try {
|
|
65
|
+
const key = (_a = opts.key) === null || _a === void 0 ? void 0 : _a.trim();
|
|
66
|
+
// Validation 1: Key is provided
|
|
67
|
+
if (!key) {
|
|
68
|
+
console.error(chalk_1.default.red("✗ Publish key is required"));
|
|
69
|
+
console.error(chalk_1.default.gray("Usage: upflux login --key <your-publish-key>"));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
// Validation 2: Key format
|
|
73
|
+
if (!validateKeyFormat(key)) {
|
|
74
|
+
console.error(chalk_1.default.red("✗ Invalid key format"));
|
|
75
|
+
console.error(chalk_1.default.gray("Key should be at least 20 characters and alphanumeric"));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
console.log(chalk_1.default.blue("🔐 Verifying with server..."));
|
|
79
|
+
// Validation 3: Server verification (mandatory)
|
|
80
|
+
const verification = await verifyKeyWithServer(key);
|
|
81
|
+
if (!verification.valid) {
|
|
82
|
+
console.error(chalk_1.default.red(`✗ ${verification.error}`));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
if (!verification.appId || !verification.deploymentId) {
|
|
86
|
+
console.error(chalk_1.default.red("✗ Key verification returned incomplete data"));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
// Create credential and save
|
|
90
|
+
const credential = {
|
|
91
|
+
key,
|
|
92
|
+
appId: verification.appId,
|
|
93
|
+
platform: verification.platform,
|
|
94
|
+
deploymentId: verification.deploymentId,
|
|
95
|
+
deploymentName: verification.deploymentName,
|
|
96
|
+
};
|
|
97
|
+
configStore_1.configStore.addCredential(credential);
|
|
98
|
+
console.log(chalk_1.default.green("✓ Login successful!"));
|
|
99
|
+
// Display saved credential info
|
|
100
|
+
const deploymentDisplay = verification.deploymentName
|
|
101
|
+
? `${verification.deploymentName}`
|
|
102
|
+
: verification.deploymentId;
|
|
103
|
+
console.log(chalk_1.default.gray(` App ID: ${verification.appId}`));
|
|
104
|
+
if (verification.platform) {
|
|
105
|
+
console.log(chalk_1.default.gray(` Platform: ${verification.platform}`));
|
|
106
|
+
}
|
|
107
|
+
console.log(chalk_1.default.gray(` Deployment: ${deploymentDisplay}`));
|
|
108
|
+
// Show all stored credentials
|
|
109
|
+
const allCreds = configStore_1.configStore.getCredentials();
|
|
110
|
+
if (allCreds.length > 1) {
|
|
111
|
+
console.log(chalk_1.default.gray(`\n Stored credentials (${allCreds.length}):`));
|
|
112
|
+
allCreds.forEach((cred, i) => {
|
|
113
|
+
const name = cred.deploymentName || cred.deploymentId;
|
|
114
|
+
const marker = cred.deploymentId === verification.deploymentId ? chalk_1.default.green("→ ") : " ";
|
|
115
|
+
console.log(chalk_1.default.gray(` ${marker}${i + 1}. ${name} (${cred.appId})`));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
console.error(chalk_1.default.red(`✗ Login failed: ${error.message}`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
exports.default = login;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const commander_1 = require("commander");
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
9
|
+
const configStore_1 = require("../config/configStore");
|
|
10
|
+
const logout = new commander_1.Command("logout")
|
|
11
|
+
.description("Remove stored credentials")
|
|
12
|
+
.option("--all", "Remove all credentials")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
try {
|
|
15
|
+
const credentials = configStore_1.configStore.getCredentials();
|
|
16
|
+
if (credentials.length === 0) {
|
|
17
|
+
console.log(chalk_1.default.yellow("No credentials stored."));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (opts.all) {
|
|
21
|
+
// Remove all credentials
|
|
22
|
+
configStore_1.configStore.removeAllCredentials();
|
|
23
|
+
console.log(chalk_1.default.green(`✓ Removed all ${credentials.length} credential(s).`));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (credentials.length === 1) {
|
|
27
|
+
// Only one credential, ask for confirmation
|
|
28
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: "confirm",
|
|
31
|
+
name: "confirm",
|
|
32
|
+
message: `Remove credential for ${credentials[0].deploymentName || credentials[0].deploymentId}?`,
|
|
33
|
+
default: false,
|
|
34
|
+
}
|
|
35
|
+
]);
|
|
36
|
+
if (confirm) {
|
|
37
|
+
configStore_1.configStore.removeCredential(credentials[0].deploymentId);
|
|
38
|
+
console.log(chalk_1.default.green("✓ Credential removed."));
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
console.log(chalk_1.default.gray("Cancelled."));
|
|
42
|
+
}
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
// Multiple credentials - show selection
|
|
46
|
+
const choices = credentials.map((cred, i) => ({
|
|
47
|
+
name: `${cred.deploymentName || cred.deploymentId} (${cred.appId})`,
|
|
48
|
+
value: cred.deploymentId,
|
|
49
|
+
}));
|
|
50
|
+
const { deploymentId } = await inquirer_1.default.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: "list",
|
|
53
|
+
name: "deploymentId",
|
|
54
|
+
message: "Select credential to remove:",
|
|
55
|
+
choices: [
|
|
56
|
+
...choices,
|
|
57
|
+
new inquirer_1.default.Separator(),
|
|
58
|
+
{ name: "Cancel", value: null },
|
|
59
|
+
],
|
|
60
|
+
}
|
|
61
|
+
]);
|
|
62
|
+
if (!deploymentId) {
|
|
63
|
+
console.log(chalk_1.default.gray("Cancelled."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
configStore_1.configStore.removeCredential(deploymentId);
|
|
67
|
+
console.log(chalk_1.default.green("✓ Credential removed."));
|
|
68
|
+
// Show remaining credentials
|
|
69
|
+
const remaining = configStore_1.configStore.getCredentials();
|
|
70
|
+
if (remaining.length > 0) {
|
|
71
|
+
console.log(chalk_1.default.gray(`\n Remaining credentials (${remaining.length}):`));
|
|
72
|
+
remaining.forEach((cred, i) => {
|
|
73
|
+
const name = cred.deploymentName || cred.deploymentId;
|
|
74
|
+
console.log(chalk_1.default.gray(` ${i + 1}. ${name} (${cred.appId})`));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(chalk_1.default.red(`✗ Logout failed: ${error.message}`));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
exports.default = logout;
|