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.
@@ -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
+ }