buildx-cli 1.0.8 → 1.0.10
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/.github/workflows/auto-publish.yml +254 -0
- package/.github/workflows/create-pr.yml +182 -0
- package/.prettierrc +8 -0
- package/README.md +316 -36
- package/eslint.config.mjs +115 -0
- package/jest.config.cjs +16 -0
- package/package.json +23 -1
- package/rollup.config.mjs +64 -0
- package/scripts/prepare-publish.js +12 -0
- package/src/__tests__/config.test.ts +102 -0
- package/src/__tests__/schema-types-convert.test.ts +147 -0
- package/src/commands/auth/login.ts +148 -0
- package/src/commands/auth/logout.ts +16 -0
- package/src/commands/auth/status.ts +52 -0
- package/src/commands/config/clear.ts +16 -0
- package/src/commands/config/index.ts +14 -0
- package/src/commands/config/setup.ts +108 -0
- package/src/commands/config/show.ts +96 -0
- package/src/commands/functions.ts +703 -0
- package/src/commands/projects/current.ts +36 -0
- package/src/commands/projects/list.ts +61 -0
- package/src/commands/projects/set-default.ts +59 -0
- package/src/commands/sync.ts +778 -0
- package/src/config/index.ts +169 -0
- package/src/index.ts +62 -0
- package/src/services/api.ts +198 -0
- package/src/services/schema-generator.ts +132 -0
- package/src/services/schema-types-convert.ts +361 -0
- package/src/types/index.ts +91 -0
- package/src/utils/env.ts +117 -0
- package/src/utils/logger.ts +29 -0
- package/src/utils/sync.ts +70 -0
- package/test.env +2 -0
- package/tsconfig.json +29 -0
- package/index.cjs +0 -21
- package/index.d.ts +0 -1
- package/index.js +0 -21
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ConfigManager } from "../config";
|
|
2
|
+
|
|
3
|
+
describe("ConfigManager", () => {
|
|
4
|
+
let configManager: ConfigManager;
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
configManager = new ConfigManager();
|
|
8
|
+
configManager.clear();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
configManager.clear();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe("Authentication", () => {
|
|
16
|
+
it("should store and retrieve auth token", () => {
|
|
17
|
+
const auth = {
|
|
18
|
+
token: "test-token",
|
|
19
|
+
expiresAt: new Date().toISOString()
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
configManager.setAuth(auth);
|
|
23
|
+
const retrieved = configManager.getAuth();
|
|
24
|
+
|
|
25
|
+
expect(retrieved).toEqual(auth);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should clear auth token", () => {
|
|
29
|
+
const auth = {
|
|
30
|
+
token: "test-token",
|
|
31
|
+
expiresAt: new Date().toISOString()
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
configManager.setAuth(auth);
|
|
35
|
+
configManager.clearAuth();
|
|
36
|
+
|
|
37
|
+
expect(configManager.getAuth()).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should check if authenticated", () => {
|
|
41
|
+
expect(configManager.isAuthenticated()).toBe(false);
|
|
42
|
+
|
|
43
|
+
const auth = {
|
|
44
|
+
token: "test-token",
|
|
45
|
+
expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() // 1 day from now
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
configManager.setAuth(auth);
|
|
49
|
+
expect(configManager.isAuthenticated()).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("should handle expired token", () => {
|
|
53
|
+
const auth = {
|
|
54
|
+
token: "test-token",
|
|
55
|
+
expiresAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString() // 1 day ago
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
configManager.setAuth(auth);
|
|
59
|
+
expect(configManager.isAuthenticated()).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("Projects", () => {
|
|
64
|
+
it("should store and retrieve projects", () => {
|
|
65
|
+
const projects = {
|
|
66
|
+
default: "project-1",
|
|
67
|
+
list: [
|
|
68
|
+
{ project_id: "project-1", name: "Project 1" },
|
|
69
|
+
{ project_id: "project-2", name: "Project 2" }
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
configManager.setProjects(projects);
|
|
74
|
+
const retrieved = configManager.getProjects();
|
|
75
|
+
|
|
76
|
+
expect(retrieved).toEqual(projects);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should set default project", () => {
|
|
80
|
+
const projects = {
|
|
81
|
+
list: [
|
|
82
|
+
{ project_id: "project-1", name: "Project 1" },
|
|
83
|
+
{ project_id: "project-2", name: "Project 2" }
|
|
84
|
+
]
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
configManager.setProjects(projects);
|
|
88
|
+
configManager.setDefaultProject("project-2");
|
|
89
|
+
|
|
90
|
+
const updated = configManager.getProjects();
|
|
91
|
+
expect(updated?.default).toBe("project-2");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should add project", () => {
|
|
95
|
+
const project = { project_id: "project-1", name: "Project 1" };
|
|
96
|
+
configManager.addProject(project);
|
|
97
|
+
|
|
98
|
+
const retrieved = configManager.getProject("project-1");
|
|
99
|
+
expect(retrieved).toEqual(project);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildCollectionsFromTypes,
|
|
3
|
+
convertTypeFieldToSchemaField,
|
|
4
|
+
mapScalarTypeToFieldType,
|
|
5
|
+
mergeCollectionWithBase
|
|
6
|
+
} from "../services/schema-types-convert";
|
|
7
|
+
|
|
8
|
+
describe("schema:types:convert internals", () => {
|
|
9
|
+
describe("mapScalarTypeToFieldType", () => {
|
|
10
|
+
it("maps buildx scalar aliases to expected field types", () => {
|
|
11
|
+
expect(mapScalarTypeToFieldType("BxLongText")).toBe("LongText");
|
|
12
|
+
expect(mapScalarTypeToFieldType("BxChoices")).toBe("Choices");
|
|
13
|
+
expect(mapScalarTypeToFieldType("BxUser")).toBe("User");
|
|
14
|
+
expect(mapScalarTypeToFieldType("BxDate")).toBe("Date");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("convertTypeFieldToSchemaField", () => {
|
|
19
|
+
it("converts single and multiple data object references", () => {
|
|
20
|
+
expect(convertTypeFieldToSchemaField("Partial<Departments> | BxDataObject", true)).toEqual({
|
|
21
|
+
type: "DataObject",
|
|
22
|
+
propertiesScheme: { ref: "Departments" }
|
|
23
|
+
});
|
|
24
|
+
expect(convertTypeFieldToSchemaField("Partial<TestRef>[] | BxDataObject[]", true)).toEqual({
|
|
25
|
+
type: "DataObjects",
|
|
26
|
+
propertiesScheme: { ref: "TestRef" }
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("buildCollectionsFromTypes", () => {
|
|
32
|
+
it("resolves relation refs using BxCollectionId enum", () => {
|
|
33
|
+
const typeContent = `
|
|
34
|
+
export enum BxCollectionId {
|
|
35
|
+
Departments = "departments",
|
|
36
|
+
Employees = "employees"
|
|
37
|
+
}
|
|
38
|
+
export type Employees = {
|
|
39
|
+
department?: Partial<Departments> | BxDataObject;
|
|
40
|
+
};
|
|
41
|
+
export type Departments = {
|
|
42
|
+
name?: BxText;
|
|
43
|
+
};
|
|
44
|
+
`;
|
|
45
|
+
const { collections, warnings } = buildCollectionsFromTypes(typeContent, []);
|
|
46
|
+
const employees = collections.find((c) => c.collection_id === "employees");
|
|
47
|
+
expect(warnings).toEqual([]);
|
|
48
|
+
expect(employees).not.toHaveProperty("name");
|
|
49
|
+
expect(employees?.form_schema?.department).toEqual({
|
|
50
|
+
type: "DataObject",
|
|
51
|
+
propertiesScheme: { ref: "departments" }
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("supports @bx annotations in JSDoc comments", () => {
|
|
56
|
+
const typeContent = `
|
|
57
|
+
export enum BxCollectionId {
|
|
58
|
+
Employees = "employees"
|
|
59
|
+
}
|
|
60
|
+
export type Employees = {
|
|
61
|
+
/** @bx.title Employment status @bx.choices active:Active|on_leave:On Leave|terminated: */
|
|
62
|
+
status?: BxChoices;
|
|
63
|
+
/** @bx.required */
|
|
64
|
+
employee_id?: BxText;
|
|
65
|
+
/** @bx.ref employees @bx.title Manager */
|
|
66
|
+
manager?: Partial<Employees> | BxDataObject;
|
|
67
|
+
};
|
|
68
|
+
`;
|
|
69
|
+
const { collections, warnings } = buildCollectionsFromTypes(typeContent, []);
|
|
70
|
+
const employees = collections.find((c) => c.collection_id === "employees");
|
|
71
|
+
expect(warnings).toEqual([]);
|
|
72
|
+
expect(employees?.form_schema?.status).toEqual({
|
|
73
|
+
type: "Choices",
|
|
74
|
+
title: "Employment status",
|
|
75
|
+
propertiesScheme: {
|
|
76
|
+
choices: [
|
|
77
|
+
{ value: "active", label: "Active" },
|
|
78
|
+
{ value: "on_leave", label: "On Leave" },
|
|
79
|
+
{ value: "terminated", label: "" }
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
expect(employees?.form_schema?.employee_id).toEqual({
|
|
84
|
+
type: "Text",
|
|
85
|
+
required: true
|
|
86
|
+
});
|
|
87
|
+
expect(employees?.form_schema?.manager).toEqual({
|
|
88
|
+
type: "DataObject",
|
|
89
|
+
title: "Manager",
|
|
90
|
+
propertiesScheme: { ref: "employees" }
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe("mergeCollectionWithBase", () => {
|
|
96
|
+
it("preserves relation metadata and avoids cross-type propertiesScheme leakage", () => {
|
|
97
|
+
const baseCollection = {
|
|
98
|
+
collection_id: "test",
|
|
99
|
+
name: "Test",
|
|
100
|
+
form_schema: {
|
|
101
|
+
reference: {
|
|
102
|
+
type: "DataObjects",
|
|
103
|
+
required: false,
|
|
104
|
+
propertiesScheme: {
|
|
105
|
+
ref: "test_ref",
|
|
106
|
+
local_key: "_id",
|
|
107
|
+
foreign_key: "parent_id",
|
|
108
|
+
schema: {
|
|
109
|
+
name: { type: "Text", required: true }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
timestamps: true,
|
|
115
|
+
auditable: false
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const inferredCollection = {
|
|
119
|
+
collection_id: "test",
|
|
120
|
+
name: "Test",
|
|
121
|
+
form_schema: {
|
|
122
|
+
reference: {
|
|
123
|
+
type: "DataObjects",
|
|
124
|
+
propertiesScheme: {
|
|
125
|
+
ref: "test_ref",
|
|
126
|
+
children: [{ name: "item", type: "Text" }]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const merged = mergeCollectionWithBase(baseCollection, inferredCollection);
|
|
133
|
+
expect(merged.form_schema.reference).toEqual({
|
|
134
|
+
type: "DataObjects",
|
|
135
|
+
required: false,
|
|
136
|
+
propertiesScheme: {
|
|
137
|
+
ref: "test_ref",
|
|
138
|
+
local_key: "_id",
|
|
139
|
+
foreign_key: "parent_id",
|
|
140
|
+
schema: {
|
|
141
|
+
name: { type: "Text", required: true }
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { configManager } from '../../config/index';
|
|
6
|
+
import { apiService } from '../../services/api';
|
|
7
|
+
import { LoginOptions } from '../../types/index';
|
|
8
|
+
|
|
9
|
+
export const loginCommand = new Command('auth:login')
|
|
10
|
+
.description('Authenticate with BuildX API')
|
|
11
|
+
.option('-t, --token <token>', 'API token for authentication')
|
|
12
|
+
.option('-u, --username <username>', 'Username for authentication')
|
|
13
|
+
.option('-p, --password <password>', 'Password for authentication')
|
|
14
|
+
.option('-i, --interactive', 'Interactive login mode')
|
|
15
|
+
.action(async (options: LoginOptions) => {
|
|
16
|
+
try {
|
|
17
|
+
// Check if API is configured
|
|
18
|
+
if (!apiService.isConfigured()) {
|
|
19
|
+
console.error(chalk.red('❌ API not configured'));
|
|
20
|
+
console.log(chalk.yellow('Please configure your API endpoint and API key first:'));
|
|
21
|
+
console.log(chalk.cyan(' buildx config setup'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let token = options.token;
|
|
26
|
+
let username = options.username;
|
|
27
|
+
let password = options.password;
|
|
28
|
+
|
|
29
|
+
// If no credentials provided, prompt for them
|
|
30
|
+
if (!token && !username && !password) {
|
|
31
|
+
const answers = await inquirer.prompt([
|
|
32
|
+
{
|
|
33
|
+
type: 'list',
|
|
34
|
+
name: 'authMethod',
|
|
35
|
+
message: 'Choose authentication method:',
|
|
36
|
+
choices: [
|
|
37
|
+
{ name: 'Username & Password', value: 'credentials' },
|
|
38
|
+
{ name: 'API Token', value: 'token' }
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
if (answers.authMethod === 'credentials') {
|
|
44
|
+
const credentialAnswers = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'username',
|
|
48
|
+
message: 'Enter your username:',
|
|
49
|
+
validate: (input: string) => {
|
|
50
|
+
if (!input || input.trim().length === 0) {
|
|
51
|
+
return 'Username is required';
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'password',
|
|
58
|
+
name: 'password',
|
|
59
|
+
message: 'Enter your password:',
|
|
60
|
+
validate: (input: string) => {
|
|
61
|
+
if (!input || input.trim().length === 0) {
|
|
62
|
+
return 'Password is required';
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
]);
|
|
68
|
+
username = credentialAnswers.username;
|
|
69
|
+
password = credentialAnswers.password;
|
|
70
|
+
} else {
|
|
71
|
+
const tokenAnswer = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'password',
|
|
74
|
+
name: 'token',
|
|
75
|
+
message: 'Enter your API token:',
|
|
76
|
+
validate: (input: string) => {
|
|
77
|
+
if (!input || input.trim().length === 0) {
|
|
78
|
+
return 'Token is required';
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
token = tokenAnswer.token;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const spinner = ora('Authenticating...').start();
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
// Only support username/password authentication
|
|
92
|
+
if (!username || !password) {
|
|
93
|
+
const credentialAnswers = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: 'input',
|
|
96
|
+
name: 'username',
|
|
97
|
+
message: 'Enter your username:',
|
|
98
|
+
validate: (input: string) => {
|
|
99
|
+
if (!input || input.trim().length === 0) {
|
|
100
|
+
return 'Username is required';
|
|
101
|
+
}
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
type: 'password',
|
|
107
|
+
name: 'password',
|
|
108
|
+
message: 'Enter your password:',
|
|
109
|
+
validate: (input: string) => {
|
|
110
|
+
if (!input || input.trim().length === 0) {
|
|
111
|
+
return 'Password is required';
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]);
|
|
117
|
+
username = credentialAnswers.username;
|
|
118
|
+
password = credentialAnswers.password;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Username/password authentication only
|
|
122
|
+
const loginResponse = await apiService.login({ username, password });
|
|
123
|
+
let authToken = loginResponse.token;
|
|
124
|
+
let expiresAt = loginResponse.expiresAt;
|
|
125
|
+
username = username ?? '';
|
|
126
|
+
authToken = authToken ?? '';
|
|
127
|
+
expiresAt = expiresAt ?? new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString();
|
|
128
|
+
configManager.setAuth({
|
|
129
|
+
token: authToken,
|
|
130
|
+
expiresAt: expiresAt,
|
|
131
|
+
username: username
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
spinner.succeed('Successfully authenticated!');
|
|
135
|
+
console.log(chalk.green('✓ Authentication stored securely'));
|
|
136
|
+
console.log(chalk.blue('You can now use other BuildX CLI commands'));
|
|
137
|
+
|
|
138
|
+
} catch (error) {
|
|
139
|
+
spinner.fail('Authentication failed');
|
|
140
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { configManager } from '../../config/index';
|
|
4
|
+
|
|
5
|
+
export const logoutCommand = new Command('auth:logout')
|
|
6
|
+
.description('Logout and clear stored authentication')
|
|
7
|
+
.action(() => {
|
|
8
|
+
try {
|
|
9
|
+
configManager.clearAuth();
|
|
10
|
+
console.log(chalk.green('✓ Successfully logged out'));
|
|
11
|
+
console.log(chalk.blue('Authentication token has been cleared'));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { configManager } from '../../config/index';
|
|
4
|
+
import { apiService } from '../../services/api';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
|
|
7
|
+
export const authStatusCommand = new Command('auth:status')
|
|
8
|
+
.description('Check authentication status')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
try {
|
|
11
|
+
// Check API configuration
|
|
12
|
+
if (!apiService.isConfigured()) {
|
|
13
|
+
console.error(chalk.red('❌ API not configured'));
|
|
14
|
+
console.log(chalk.yellow('Please configure your API endpoint and API key first:'));
|
|
15
|
+
console.log(chalk.cyan(' buildx config setup'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const isAuthenticated = configManager.isAuthenticated();
|
|
19
|
+
const auth = configManager.getAuth();
|
|
20
|
+
|
|
21
|
+
if (isAuthenticated && auth) {
|
|
22
|
+
const spinner = ora('Validating session...').start();
|
|
23
|
+
try {
|
|
24
|
+
// Call /auth/me endpoint
|
|
25
|
+
const me = await apiService.getMe();
|
|
26
|
+
spinner.succeed('Authenticated');
|
|
27
|
+
console.log(chalk.green('✓ Authenticated'));
|
|
28
|
+
console.log(chalk.blue('Username:'), me.username);
|
|
29
|
+
if (auth.token) {
|
|
30
|
+
console.log(chalk.blue('Token:'), auth.token.substring(0, 8) + '...');
|
|
31
|
+
}
|
|
32
|
+
if (auth.expiresAt) {
|
|
33
|
+
console.log(chalk.blue('Expires:'), new Date(auth.expiresAt).toLocaleString());
|
|
34
|
+
}
|
|
35
|
+
// console.log(chalk.gray('Raw response:'), JSON.stringify(me, null, 2));
|
|
36
|
+
if (me.username) {
|
|
37
|
+
console.log(chalk.blue(`You are authenticated as ${me.source}:${me.username}`));
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail('Session invalid');
|
|
41
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
console.log(chalk.red('✗ Not authenticated'));
|
|
46
|
+
console.log(chalk.yellow('Run "buildx login" to authenticate'));
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { configManager } from '../../config/index';
|
|
4
|
+
|
|
5
|
+
export const configClearCommand = new Command('config:clear')
|
|
6
|
+
.description('Clear all CLI configuration (API, auth, projects, sync)')
|
|
7
|
+
.action(() => {
|
|
8
|
+
try {
|
|
9
|
+
configManager.clear();
|
|
10
|
+
console.log(chalk.green('✅ All configuration cleared.'));
|
|
11
|
+
console.log(chalk.yellow('You must run "buildx config setup" before using the CLI again.'));
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(chalk.red('❌ Failed to clear configuration:'), error instanceof Error ? error.message : 'Unknown error');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { setupCommand } from './setup';
|
|
3
|
+
import { configShowCommand } from './show';
|
|
4
|
+
import { configClearCommand } from './clear';
|
|
5
|
+
|
|
6
|
+
export const configCommand = new Command('config')
|
|
7
|
+
.description('Manage CLI configuration')
|
|
8
|
+
.addCommand(setupCommand)
|
|
9
|
+
.addCommand(configShowCommand)
|
|
10
|
+
.addCommand(configClearCommand);
|
|
11
|
+
|
|
12
|
+
export { setupCommand as configSetupCommand } from './setup';
|
|
13
|
+
export { configShowCommand } from './show';
|
|
14
|
+
export { configClearCommand } from './clear';
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { configManager } from '../../config/index';
|
|
5
|
+
import { loadEnvConfig } from '../../utils/env';
|
|
6
|
+
|
|
7
|
+
export const setupCommand = new Command('config:setup')
|
|
8
|
+
.description('Configure API endpoint and API key')
|
|
9
|
+
.option('-e, --endpoint <endpoint>', 'API endpoint URL')
|
|
10
|
+
.option('-k, --api-key <apiKey>', 'API key')
|
|
11
|
+
.action(async (options) => {
|
|
12
|
+
try {
|
|
13
|
+
let endpoint = options.endpoint;
|
|
14
|
+
let apiKey = options.apiKey;
|
|
15
|
+
|
|
16
|
+
// If not provided via options, prompt interactively
|
|
17
|
+
if (!endpoint || !apiKey) {
|
|
18
|
+
console.log(chalk.blue('🔧 BuildX CLI Setup'));
|
|
19
|
+
console.log(chalk.gray('Configure your API endpoint and API key to get started.'));
|
|
20
|
+
console.log(chalk.gray('Supports .env / env aliases: NEXT_PUBLIC_API_ENDPOINT, NEXT_PUBLIC_API_KEY, NEXT_PUBLIC_BUILDX_API_ENDPOINT, NEXT_PUBLIC_BUILDX_API_KEY, BUILDX_API_ENDPOINT, BUILDX_API_KEY.\n'));
|
|
21
|
+
|
|
22
|
+
const currentConfig = configManager.getApiConfig();
|
|
23
|
+
const envConfig = loadEnvConfig();
|
|
24
|
+
|
|
25
|
+
if (!endpoint && !apiKey && envConfig.BUILDX_API_ENDPOINT && envConfig.BUILDX_API_KEY) {
|
|
26
|
+
const envApiKey = envConfig.BUILDX_API_KEY;
|
|
27
|
+
const slicedApiKey = envApiKey.substring(0, 2);
|
|
28
|
+
const endpointSource = envConfig._sources?.endpointKey || "unknown";
|
|
29
|
+
const apiKeySource = envConfig._sources?.apiKeyKey || "unknown";
|
|
30
|
+
|
|
31
|
+
console.log(chalk.yellow('Detected API config from environment/.env'));
|
|
32
|
+
console.log(chalk.gray(` Endpoint: ${envConfig.BUILDX_API_ENDPOINT} (${endpointSource})`));
|
|
33
|
+
console.log(chalk.gray(` API Key: ${slicedApiKey}${'*'.repeat(Math.min(envApiKey.length - slicedApiKey.length, 8))}... (${apiKeySource})`));
|
|
34
|
+
|
|
35
|
+
const { useEnvConfig } = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: 'confirm',
|
|
38
|
+
name: 'useEnvConfig',
|
|
39
|
+
message: 'Use detected environment values?',
|
|
40
|
+
default: true
|
|
41
|
+
}
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
if (useEnvConfig) {
|
|
45
|
+
endpoint = envConfig.BUILDX_API_ENDPOINT;
|
|
46
|
+
apiKey = envConfig.BUILDX_API_KEY;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!endpoint || !apiKey) {
|
|
51
|
+
const answers = await inquirer.prompt([
|
|
52
|
+
{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'endpoint',
|
|
55
|
+
message: 'Enter your API endpoint URL:',
|
|
56
|
+
default: endpoint || currentConfig?.endpoint || envConfig.BUILDX_API_ENDPOINT || 'https://api.buildx.com',
|
|
57
|
+
validate: (input: string) => {
|
|
58
|
+
if (!input.trim()) {
|
|
59
|
+
return 'Endpoint URL is required';
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
new URL(input);
|
|
63
|
+
return true;
|
|
64
|
+
} catch {
|
|
65
|
+
return 'Please enter a valid URL';
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'password',
|
|
71
|
+
name: 'apiKey',
|
|
72
|
+
message: 'Enter your API key:',
|
|
73
|
+
default: apiKey || currentConfig?.apiKey || envConfig.BUILDX_API_KEY || '',
|
|
74
|
+
validate: (input: string) => {
|
|
75
|
+
if (!input.trim()) {
|
|
76
|
+
return 'API key is required';
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
endpoint = answers.endpoint;
|
|
84
|
+
apiKey = answers.apiKey;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Save the configuration
|
|
89
|
+
configManager.setApiConfig({
|
|
90
|
+
endpoint: endpoint.trim(),
|
|
91
|
+
apiKey: apiKey.trim()
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Show only 2 first characters of the api key
|
|
95
|
+
const slicedApiKey = apiKey.substring(0, 2);
|
|
96
|
+
|
|
97
|
+
console.log(chalk.green('✅ Configuration saved successfully!'));
|
|
98
|
+
console.log(chalk.gray(`Endpoint: ${endpoint}`));
|
|
99
|
+
console.log(chalk.gray(`API Key: ${slicedApiKey}${'*'.repeat(Math.min(apiKey.length - slicedApiKey.length, 8))}...`));
|
|
100
|
+
console.log(chalk.blue('\nYou can now use the CLI commands. Start with:'));
|
|
101
|
+
console.log(chalk.cyan(' buildx auth:login'));
|
|
102
|
+
console.log(chalk.cyan(' buildx projects:list'));
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error(chalk.red('❌ Setup failed:'), error instanceof Error ? error.message : 'Unknown error');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
});
|