@snowieedev/shipkit 1.0.0 → 1.1.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/dist/commands/api.js +51 -0
- package/dist/commands/init.js +2 -6
- package/dist/commands/login.js +8 -1
- package/dist/commands/logout.js +2 -0
- package/dist/commands/providers.js +3 -7
- package/dist/commands/whoami.js +36 -0
- package/dist/index.js +4 -0
- package/dist/lib/api.js +15 -1
- package/dist/lib/apiKeyManager.js +51 -0
- package/dist/lib/authGuard.js +26 -0
- package/package.json +2 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { api } from '../lib/api.js';
|
|
6
|
+
import { AuthGuard } from '../lib/authGuard.js';
|
|
7
|
+
import { APIKeyManager } from '../lib/apiKeyManager.js';
|
|
8
|
+
export const apiCommand = new Command('api')
|
|
9
|
+
.description('Connect a ShipKit API key to the local CLI')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const auth = AuthGuard.requireAuth();
|
|
12
|
+
console.log(`\nShipKit API Connection\n`);
|
|
13
|
+
const answers = await inquirer.prompt([
|
|
14
|
+
{
|
|
15
|
+
type: 'password',
|
|
16
|
+
name: 'apiKey',
|
|
17
|
+
message: 'Enter API Key\n›',
|
|
18
|
+
mask: '*',
|
|
19
|
+
validate: (input) => {
|
|
20
|
+
if (!input || !input.startsWith('sk_live_')) {
|
|
21
|
+
return 'Please enter a valid live ShipKit API key (starts with sk_live_)';
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]);
|
|
27
|
+
console.log('');
|
|
28
|
+
const spinner = ora('Verifying API key...').start();
|
|
29
|
+
try {
|
|
30
|
+
const response = await api.verifyApiKey(answers.apiKey);
|
|
31
|
+
if (response.success) {
|
|
32
|
+
APIKeyManager.setApiKey(answers.apiKey, auth.user.id);
|
|
33
|
+
spinner.stop();
|
|
34
|
+
console.log(`${chalk.green('✓')} API key verified\n`);
|
|
35
|
+
console.log(`Connected to ShipKit.`);
|
|
36
|
+
console.log(`Project access enabled.`);
|
|
37
|
+
console.log(`Provider access enabled.`);
|
|
38
|
+
console.log(`\n${chalk.dim('CLI ready.')}\n`);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw new Error('Invalid response from server');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
spinner.stop();
|
|
46
|
+
console.log(`${chalk.red('✗')} Invalid API key\n`);
|
|
47
|
+
console.log(`The supplied API key could not be verified.`);
|
|
48
|
+
console.log(`${chalk.dim('Please check your dashboard and try again.')}\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
package/dist/commands/init.js
CHANGED
|
@@ -5,15 +5,11 @@ import chalk from 'chalk';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
7
|
import { api } from '../lib/api.js';
|
|
8
|
-
import {
|
|
8
|
+
import { AuthGuard } from '../lib/authGuard.js';
|
|
9
9
|
export const initCommand = new Command('init')
|
|
10
10
|
.description('Create a ShipKit project')
|
|
11
11
|
.action(async () => {
|
|
12
|
-
|
|
13
|
-
if (!auth || !auth.token) {
|
|
14
|
-
console.log(`\n${chalk.red('✗')} Unauthorized\n\nPlease run ${chalk.cyan('shipkit login')} first.\n`);
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
12
|
+
AuthGuard.requireFullyAuthenticated();
|
|
17
13
|
console.log('');
|
|
18
14
|
const answers = await inquirer.prompt([
|
|
19
15
|
{
|
package/dist/commands/login.js
CHANGED
|
@@ -4,6 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { api } from '../lib/api.js';
|
|
6
6
|
import { setAuth } from '../lib/storage.js';
|
|
7
|
+
import { APIKeyManager } from '../lib/apiKeyManager.js';
|
|
7
8
|
export const loginCommand = new Command('login')
|
|
8
9
|
.description('Authenticate with ShipKit')
|
|
9
10
|
.action(async () => {
|
|
@@ -43,7 +44,13 @@ export const loginCommand = new Command('login')
|
|
|
43
44
|
spinner.stop();
|
|
44
45
|
console.log(`${chalk.green('✓')} Authenticated successfully\n`);
|
|
45
46
|
console.log(`Connected as:\n${chalk.cyan(response.user.email)}\n`);
|
|
46
|
-
|
|
47
|
+
const apiKey = APIKeyManager.getApiKey();
|
|
48
|
+
if (!apiKey) {
|
|
49
|
+
console.log(`ℹ No API key connected.\n\nRun:\n ${chalk.cyan('shipkit api')}\n\nto connect your ShipKit account.\n`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(`${chalk.dim('Account ready.')}\n`);
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
else {
|
|
49
56
|
throw new Error('Invalid response from server');
|
package/dist/commands/logout.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { clearAuth } from '../lib/storage.js';
|
|
4
|
+
import { APIKeyManager } from '../lib/apiKeyManager.js';
|
|
4
5
|
export const logoutCommand = new Command('logout')
|
|
5
6
|
.description('Remove local session')
|
|
6
7
|
.action(() => {
|
|
7
8
|
try {
|
|
8
9
|
clearAuth();
|
|
10
|
+
APIKeyManager.removeApiKey();
|
|
9
11
|
console.log(`\n${chalk.green('✓')} Logged out successfully\n\n${chalk.dim('Local credentials removed.')}\n`);
|
|
10
12
|
}
|
|
11
13
|
catch (error) {
|
|
@@ -2,16 +2,12 @@ import { Command } from 'commander';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { api } from '../lib/api.js';
|
|
5
|
-
import {
|
|
5
|
+
import { AuthGuard } from '../lib/authGuard.js';
|
|
6
6
|
export const providersCommand = new Command('providers')
|
|
7
7
|
.description('View connected providers')
|
|
8
8
|
.action(async () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
console.log(`\n${chalk.red('✗')} Unauthorized\n\nPlease run ${chalk.cyan('shipkit login')} first.\n`);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
}
|
|
14
|
-
const spinner = ora('Fetching provider status...').start();
|
|
9
|
+
AuthGuard.requireFullyAuthenticated();
|
|
10
|
+
const spinner = ora('Fetching providers...').start();
|
|
15
11
|
try {
|
|
16
12
|
const providers = await api.getProviders();
|
|
17
13
|
spinner.stop();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { api } from '../lib/api.js';
|
|
5
|
+
import { AuthGuard } from '../lib/authGuard.js';
|
|
6
|
+
import { APIKeyManager } from '../lib/apiKeyManager.js';
|
|
7
|
+
export const whoamiCommand = new Command('whoami')
|
|
8
|
+
.description('Show currently authenticated ShipKit user')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
// Require user to be logged in first
|
|
11
|
+
AuthGuard.requireAuth();
|
|
12
|
+
console.log('');
|
|
13
|
+
const spinner = ora('Fetching profile...').start();
|
|
14
|
+
try {
|
|
15
|
+
const response = await api.whoami();
|
|
16
|
+
if (response.success && response.user) {
|
|
17
|
+
spinner.stop();
|
|
18
|
+
const apiKey = APIKeyManager.getApiKey();
|
|
19
|
+
const apiConnected = apiKey ? 'Yes' : 'No';
|
|
20
|
+
console.log(`${chalk.bold(response.user.full_name)}\n`);
|
|
21
|
+
console.log(`${chalk.cyan(response.user.email)}\n`);
|
|
22
|
+
console.log(`User ID\n${chalk.dim(response.user.id)}\n`);
|
|
23
|
+
console.log(`Plan\n${chalk.dim(response.user.plan || 'Pro')}\n`);
|
|
24
|
+
console.log(`API Connected\n${apiKey ? chalk.green(apiConnected) : chalk.yellow(apiConnected)}\n`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error('Invalid response from server');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
spinner.stop();
|
|
32
|
+
console.log(`${chalk.red('✗')} Failed to fetch profile\n`);
|
|
33
|
+
console.log(`${error.message || 'Unknown error occurred'}\n`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { loginCommand } from './commands/login.js';
|
|
|
7
7
|
import { logoutCommand } from './commands/logout.js';
|
|
8
8
|
import { initCommand } from './commands/init.js';
|
|
9
9
|
import { providersCommand } from './commands/providers.js';
|
|
10
|
+
import { apiCommand } from './commands/api.js';
|
|
11
|
+
import { whoamiCommand } from './commands/whoami.js';
|
|
10
12
|
const program = new Command();
|
|
11
13
|
const displayBanner = () => {
|
|
12
14
|
console.log('\n');
|
|
@@ -35,4 +37,6 @@ program.addCommand(loginCommand);
|
|
|
35
37
|
program.addCommand(logoutCommand);
|
|
36
38
|
program.addCommand(initCommand);
|
|
37
39
|
program.addCommand(providersCommand);
|
|
40
|
+
program.addCommand(apiCommand);
|
|
41
|
+
program.addCommand(whoamiCommand);
|
|
38
42
|
program.parse(process.argv);
|
package/dist/lib/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { getAuth } from './storage.js';
|
|
3
|
-
const API_URL = process.env.SHIPKIT_API_URL || '
|
|
3
|
+
const API_URL = process.env.SHIPKIT_API_URL || 'https://shipkit-bice.vercel.app';
|
|
4
4
|
export const apiClient = axios.create({
|
|
5
5
|
baseURL: `${API_URL}/api`,
|
|
6
6
|
timeout: 10000,
|
|
@@ -12,6 +12,12 @@ apiClient.interceptors.request.use((config) => {
|
|
|
12
12
|
}
|
|
13
13
|
return config;
|
|
14
14
|
});
|
|
15
|
+
apiClient.interceptors.response.use((response) => response, (error) => {
|
|
16
|
+
if (error.code === 'ECONNREFUSED' || (error.name === 'AggregateError' && !error.message)) {
|
|
17
|
+
error.message = `Could not connect to ShipKit API at ${error.config?.baseURL || API_URL}. Is the server running?`;
|
|
18
|
+
}
|
|
19
|
+
return Promise.reject(error);
|
|
20
|
+
});
|
|
15
21
|
export const api = {
|
|
16
22
|
login: async (email, password) => {
|
|
17
23
|
const { data } = await apiClient.post('/cli/login', { email, password });
|
|
@@ -29,4 +35,12 @@ export const api = {
|
|
|
29
35
|
const { data } = await apiClient.get('/providers');
|
|
30
36
|
return data.providers;
|
|
31
37
|
},
|
|
38
|
+
verifyApiKey: async (apiKey) => {
|
|
39
|
+
const { data } = await apiClient.post('/cli/verify-key', { apiKey });
|
|
40
|
+
return data;
|
|
41
|
+
},
|
|
42
|
+
whoami: async () => {
|
|
43
|
+
const { data } = await apiClient.get('/cli/whoami');
|
|
44
|
+
return data;
|
|
45
|
+
},
|
|
32
46
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
export class APIKeyManager {
|
|
5
|
+
static get configDir() {
|
|
6
|
+
return path.join(os.homedir(), '.shipkit');
|
|
7
|
+
}
|
|
8
|
+
static get configPath() {
|
|
9
|
+
return path.join(this.configDir, 'api.json');
|
|
10
|
+
}
|
|
11
|
+
static ensureDir() {
|
|
12
|
+
if (!fs.existsSync(this.configDir)) {
|
|
13
|
+
fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
static getApiKey() {
|
|
17
|
+
try {
|
|
18
|
+
if (!fs.existsSync(this.configPath)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const raw = fs.readFileSync(this.configPath, 'utf-8');
|
|
22
|
+
const data = JSON.parse(raw);
|
|
23
|
+
if (!data || !data.apiKey || typeof data.apiKey !== 'string' || !data.apiKey.startsWith('sk_live_')) {
|
|
24
|
+
// Data is corrupt or invalid
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
// Handle corruption gracefully
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
static setApiKey(apiKey, userId) {
|
|
35
|
+
if (!apiKey.startsWith('sk_live_')) {
|
|
36
|
+
throw new Error('Invalid API key format. Must start with sk_live_');
|
|
37
|
+
}
|
|
38
|
+
this.ensureDir();
|
|
39
|
+
const data = {
|
|
40
|
+
apiKey,
|
|
41
|
+
createdAt: new Date().toISOString(),
|
|
42
|
+
userId
|
|
43
|
+
};
|
|
44
|
+
fs.writeFileSync(this.configPath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
45
|
+
}
|
|
46
|
+
static removeApiKey() {
|
|
47
|
+
if (fs.existsSync(this.configPath)) {
|
|
48
|
+
fs.unlinkSync(this.configPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { getAuth } from './storage.js';
|
|
2
|
+
import { APIKeyManager } from './apiKeyManager.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
export class AuthGuard {
|
|
5
|
+
static requireAuth() {
|
|
6
|
+
const auth = getAuth();
|
|
7
|
+
if (!auth || !auth.token) {
|
|
8
|
+
console.log(`\n${chalk.red('✗')} No active session found.\n\nRun:\n ${chalk.cyan('shipkit login')}\n\nto continue.\n`);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
}
|
|
11
|
+
return auth;
|
|
12
|
+
}
|
|
13
|
+
static requireApiKey() {
|
|
14
|
+
const apiKeyData = APIKeyManager.getApiKey();
|
|
15
|
+
if (!apiKeyData) {
|
|
16
|
+
console.log(`\n${chalk.red('✗')} No ShipKit API key connected.\n\nRun:\n ${chalk.cyan('shipkit api')}\n\nto connect your account.\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
return apiKeyData;
|
|
20
|
+
}
|
|
21
|
+
static requireFullyAuthenticated() {
|
|
22
|
+
const auth = this.requireAuth();
|
|
23
|
+
const apiKeyData = this.requireApiKey();
|
|
24
|
+
return { auth, apiKeyData };
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowieedev/shipkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "The command line interface for ShipKit - Build Faster. Ship Smarter.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"test": "vitest run",
|
|
11
11
|
"test:coverage": "vitest run --coverage",
|
|
12
12
|
"build": "tsc",
|
|
13
|
+
"prepublishOnly": "pnpm run build",
|
|
13
14
|
"start": "node ./dist/index.js"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [
|