carto-cli 0.1.0-rc.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/.nvmrc +1 -0
- package/ARCHITECTURE.md +497 -0
- package/CHANGELOG.md +28 -0
- package/LICENSE +15 -0
- package/MAP_JSON.md +516 -0
- package/README.md +1595 -0
- package/WORKFLOW_JSON.md +623 -0
- package/dist/api.js +489 -0
- package/dist/auth-oauth.js +485 -0
- package/dist/auth-server.js +432 -0
- package/dist/browser.js +30 -0
- package/dist/colors.js +45 -0
- package/dist/commands/activity.js +427 -0
- package/dist/commands/admin.js +177 -0
- package/dist/commands/ai.js +489 -0
- package/dist/commands/auth.js +652 -0
- package/dist/commands/connections.js +412 -0
- package/dist/commands/credentials.js +606 -0
- package/dist/commands/imports.js +234 -0
- package/dist/commands/maps.js +1022 -0
- package/dist/commands/org.js +195 -0
- package/dist/commands/sql.js +326 -0
- package/dist/commands/users.js +459 -0
- package/dist/commands/workflows.js +1025 -0
- package/dist/config.js +320 -0
- package/dist/download.js +108 -0
- package/dist/help.js +285 -0
- package/dist/http.js +139 -0
- package/dist/index.js +1133 -0
- package/dist/logo.js +11 -0
- package/dist/prompt.js +67 -0
- package/dist/schedule-parser.js +287 -0
- package/jest.config.ts +43 -0
- package/package.json +53 -0
|
@@ -0,0 +1,234 @@
|
|
|
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.importsCreate = importsCreate;
|
|
7
|
+
const api_1 = require("../api");
|
|
8
|
+
const colors_1 = require("../colors");
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const crypto_1 = require("crypto");
|
|
12
|
+
const http_1 = require("../http");
|
|
13
|
+
const MAX_FILE_SIZE = 1 * 1024 * 1024 * 1024; // 1GB
|
|
14
|
+
const POLL_INTERVAL = 3000; // 3 seconds
|
|
15
|
+
async function importsCreate(options, token, baseUrl, jsonOutput, debug = false, profile) {
|
|
16
|
+
try {
|
|
17
|
+
const client = await api_1.ApiClient.create(token, baseUrl, debug, profile);
|
|
18
|
+
// Validate required parameters
|
|
19
|
+
if (!options.connection) {
|
|
20
|
+
throw new Error('--connection is required');
|
|
21
|
+
}
|
|
22
|
+
if (!options.destination) {
|
|
23
|
+
throw new Error('--destination is required');
|
|
24
|
+
}
|
|
25
|
+
// Validate that either --file or --url is provided (not both)
|
|
26
|
+
if (!options.file && !options.url) {
|
|
27
|
+
throw new Error('Either --file or --url must be provided');
|
|
28
|
+
}
|
|
29
|
+
if (options.file && options.url) {
|
|
30
|
+
throw new Error('Cannot specify both --file and --url');
|
|
31
|
+
}
|
|
32
|
+
let importUrl;
|
|
33
|
+
let previewSchema = null;
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
// If --file is provided, upload it first
|
|
36
|
+
if (options.file) {
|
|
37
|
+
if (!jsonOutput) {
|
|
38
|
+
console.log((0, colors_1.dim)('Uploading file...'));
|
|
39
|
+
}
|
|
40
|
+
importUrl = await uploadFile(client, options.file, options.connection, jsonOutput, debug);
|
|
41
|
+
if (!jsonOutput) {
|
|
42
|
+
console.log((0, colors_1.success)('✓ File uploaded successfully'));
|
|
43
|
+
}
|
|
44
|
+
// Note: Preview/schema detection skipped - let import API handle it automatically
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
importUrl = options.url;
|
|
48
|
+
}
|
|
49
|
+
// Create import job
|
|
50
|
+
if (!jsonOutput) {
|
|
51
|
+
console.log((0, colors_1.dim)('Creating import job...'));
|
|
52
|
+
}
|
|
53
|
+
const importBody = {
|
|
54
|
+
connection: options.connection,
|
|
55
|
+
url: importUrl,
|
|
56
|
+
destination: options.destination,
|
|
57
|
+
};
|
|
58
|
+
// For file uploads, add client workspace marker
|
|
59
|
+
if (options.file) {
|
|
60
|
+
importBody.client = 'workspace';
|
|
61
|
+
}
|
|
62
|
+
if (options.overwrite) {
|
|
63
|
+
importBody.overwrite = true;
|
|
64
|
+
}
|
|
65
|
+
if (options.noAutoguessing) {
|
|
66
|
+
importBody.autoguessing = false;
|
|
67
|
+
}
|
|
68
|
+
const jobResponse = await client.post('/v3/imports', importBody);
|
|
69
|
+
if (!jsonOutput) {
|
|
70
|
+
console.log((0, colors_1.dim)(`Job ID: ${jobResponse.jobId}`));
|
|
71
|
+
}
|
|
72
|
+
// If --async flag is set, return immediately
|
|
73
|
+
if (options.async) {
|
|
74
|
+
if (jsonOutput) {
|
|
75
|
+
console.log(JSON.stringify({
|
|
76
|
+
success: true,
|
|
77
|
+
jobId: jobResponse.jobId,
|
|
78
|
+
status: jobResponse.status
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log((0, colors_1.success)(`✓ Import job created: ${jobResponse.jobId}`));
|
|
83
|
+
console.log((0, colors_1.dim)(`Status: ${jobResponse.status}`));
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Poll for completion
|
|
88
|
+
const finalStatus = await pollImportStatus(client, jobResponse.jobId, jsonOutput);
|
|
89
|
+
const elapsedTime = Math.round((Date.now() - startTime) / 1000);
|
|
90
|
+
if (finalStatus.status === 'success') {
|
|
91
|
+
if (jsonOutput) {
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
success: true,
|
|
94
|
+
jobId: finalStatus.jobId,
|
|
95
|
+
status: finalStatus.status,
|
|
96
|
+
destination: finalStatus.destination,
|
|
97
|
+
rowCount: finalStatus.rowCount,
|
|
98
|
+
elapsedTime
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
console.log((0, colors_1.success)(`✓ Import completed successfully in ${elapsedTime}s`));
|
|
103
|
+
console.log((0, colors_1.bold)('Table: ') + finalStatus.destination);
|
|
104
|
+
if (finalStatus.rowCount) {
|
|
105
|
+
console.log((0, colors_1.bold)('Rows: ') + finalStatus.rowCount.toLocaleString());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
if (jsonOutput) {
|
|
111
|
+
console.log(JSON.stringify({
|
|
112
|
+
success: false,
|
|
113
|
+
jobId: finalStatus.jobId,
|
|
114
|
+
status: finalStatus.status,
|
|
115
|
+
error: finalStatus.error,
|
|
116
|
+
elapsedTime
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
console.log((0, colors_1.error)(`✗ Import failed after ${elapsedTime}s`));
|
|
121
|
+
if (finalStatus.error) {
|
|
122
|
+
console.log((0, colors_1.error)('Error: ') + finalStatus.error);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (jsonOutput) {
|
|
130
|
+
console.log(JSON.stringify({ success: false, error: err.message }));
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
if (err.message.includes('401') || err.message.includes('Token not defined')) {
|
|
134
|
+
console.log((0, colors_1.error)('✗ Authentication required'));
|
|
135
|
+
console.log('Please run: carto auth login');
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log((0, colors_1.error)('✗ Failed to create import: ' + err.message));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function uploadFile(client, filePath, connectionName, jsonOutput, debug) {
|
|
145
|
+
// Validate file exists
|
|
146
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
147
|
+
throw new Error(`File not found: ${filePath}`);
|
|
148
|
+
}
|
|
149
|
+
// Validate file size
|
|
150
|
+
const stats = fs_1.default.statSync(filePath);
|
|
151
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
152
|
+
throw new Error(`File size exceeds 1GB limit (${Math.round(stats.size / 1024 / 1024)}MB)`);
|
|
153
|
+
}
|
|
154
|
+
if (!jsonOutput && debug) {
|
|
155
|
+
console.log((0, colors_1.dim)(`File size: ${Math.round(stats.size / 1024 / 1024 * 100) / 100} MB`));
|
|
156
|
+
}
|
|
157
|
+
// Get user ID
|
|
158
|
+
const userId = await getUserId(client);
|
|
159
|
+
// Generate unique filename
|
|
160
|
+
const ext = path_1.default.extname(filePath);
|
|
161
|
+
const filename = `${userId}/${(0, crypto_1.randomUUID)()}${ext}`;
|
|
162
|
+
// Get signed upload URL
|
|
163
|
+
const signedUrlResponse = await client.postWorkspace('/storage/sign', {
|
|
164
|
+
type: 'import',
|
|
165
|
+
filename,
|
|
166
|
+
connectionName
|
|
167
|
+
});
|
|
168
|
+
// Upload file to GCS
|
|
169
|
+
const fileContent = fs_1.default.readFileSync(filePath);
|
|
170
|
+
const uploadHeaders = {
|
|
171
|
+
'Content-Length': stats.size.toString(),
|
|
172
|
+
...signedUrlResponse.headers
|
|
173
|
+
};
|
|
174
|
+
if (debug) {
|
|
175
|
+
console.error((0, colors_1.dim)('\n--- Upload Debug ---'));
|
|
176
|
+
console.error((0, colors_1.bold)('Method: ') + 'PUT');
|
|
177
|
+
console.error((0, colors_1.bold)('URL: ') + signedUrlResponse.url);
|
|
178
|
+
console.error((0, colors_1.bold)('Headers:'));
|
|
179
|
+
Object.entries(uploadHeaders).forEach(([key, value]) => {
|
|
180
|
+
console.error(` ${key}: ${value}`);
|
|
181
|
+
});
|
|
182
|
+
console.error((0, colors_1.dim)('-------------------\n'));
|
|
183
|
+
}
|
|
184
|
+
const uploadResponse = await (0, http_1.request)(signedUrlResponse.url, {
|
|
185
|
+
method: 'PUT',
|
|
186
|
+
headers: uploadHeaders,
|
|
187
|
+
body: fileContent
|
|
188
|
+
});
|
|
189
|
+
if (uploadResponse.statusCode >= 400) {
|
|
190
|
+
throw new Error(`Failed to upload file: ${uploadResponse.statusCode} ${uploadResponse.body}`);
|
|
191
|
+
}
|
|
192
|
+
// Get a NEW signed URL for reading (the upload URL is write-only)
|
|
193
|
+
const readUrlResponse = await client.postWorkspace('/storage/sign', {
|
|
194
|
+
type: 'import',
|
|
195
|
+
filename,
|
|
196
|
+
connectionName
|
|
197
|
+
});
|
|
198
|
+
// Return the read URL for the import API
|
|
199
|
+
return readUrlResponse.url;
|
|
200
|
+
}
|
|
201
|
+
async function getUserId(client) {
|
|
202
|
+
try {
|
|
203
|
+
const userInfo = await client.getAccounts('/users/me', true);
|
|
204
|
+
return userInfo.user_id;
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
throw new Error(`Failed to get user ID: ${err.message}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function getPreviewSchema(client, url, connection) {
|
|
211
|
+
try {
|
|
212
|
+
const preview = await client.post('/v3/imports/preview', {
|
|
213
|
+
connection,
|
|
214
|
+
url,
|
|
215
|
+
client: 'workspace'
|
|
216
|
+
});
|
|
217
|
+
return preview;
|
|
218
|
+
}
|
|
219
|
+
catch (err) {
|
|
220
|
+
throw new Error(`Failed to get file schema: ${err.message}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function pollImportStatus(client, jobId, jsonOutput) {
|
|
224
|
+
let status;
|
|
225
|
+
do {
|
|
226
|
+
// Wait before polling
|
|
227
|
+
await new Promise(resolve => setTimeout(resolve, POLL_INTERVAL));
|
|
228
|
+
status = await client.get(`/v3/imports/${jobId}`);
|
|
229
|
+
if (!jsonOutput) {
|
|
230
|
+
console.log((0, colors_1.dim)(`Status: ${status.status}...`));
|
|
231
|
+
}
|
|
232
|
+
} while (status.status === 'pending' || status.status === 'running');
|
|
233
|
+
return status;
|
|
234
|
+
}
|