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/dist/logo.js ADDED
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CARTO_LOGO = void 0;
4
+ exports.CARTO_LOGO = `
5
+ ██████╗ █████╗ ██████╗ ████████╗ ██████╗ ██████╗██╗ ██╗
6
+ ██╔════╝██╔══██╗██╔══██╗╚══██╔══╝██╔═══██╗ ██╔════╝██║ ██║
7
+ ██║ ███████║██████╔╝ ██║ ██║ ██║ ██║ ██║ ██║
8
+ ██║ ██╔══██║██╔══██╗ ██║ ██║ ██║ ██║ ██║ ██║
9
+ ╚██████╗██║ ██║██║ ██║ ██║ ╚██████╔╝ ╚██████╗███████╗██║
10
+ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝╚══════╝╚═╝
11
+ `;
package/dist/prompt.js ADDED
@@ -0,0 +1,67 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.promptForConfirmation = promptForConfirmation;
37
+ const readline = __importStar(require("readline"));
38
+ /**
39
+ * Prompt the user for confirmation by typing a specific word
40
+ * @param message The message to display before the prompt
41
+ * @param expectedInput The exact string the user must type (case-sensitive)
42
+ * @returns Promise that resolves to true if user entered correct input, false otherwise
43
+ */
44
+ async function promptForConfirmation(message, expectedInput) {
45
+ // Check if we're in an interactive terminal
46
+ if (!process.stdin.isTTY) {
47
+ // Non-interactive mode - cannot prompt
48
+ return false;
49
+ }
50
+ return new Promise((resolve) => {
51
+ const rl = readline.createInterface({
52
+ input: process.stdin,
53
+ output: process.stdout
54
+ });
55
+ // Handle Ctrl+C gracefully
56
+ rl.on('SIGINT', () => {
57
+ console.log('\n'); // New line after ^C
58
+ rl.close();
59
+ resolve(false);
60
+ });
61
+ rl.question(message, (answer) => {
62
+ rl.close();
63
+ const trimmedAnswer = answer.trim();
64
+ resolve(trimmedAnswer === expectedInput);
65
+ });
66
+ });
67
+ }
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ /**
3
+ * Schedule expression parser for different data warehouse providers
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseScheduleExpression = parseScheduleExpression;
7
+ const DAYS_OF_WEEK_MAP = {
8
+ 'sunday': 0,
9
+ 'sun': 0,
10
+ 'monday': 1,
11
+ 'mon': 1,
12
+ 'tuesday': 2,
13
+ 'tue': 2,
14
+ 'wednesday': 3,
15
+ 'wed': 3,
16
+ 'thursday': 4,
17
+ 'thu': 4,
18
+ 'friday': 5,
19
+ 'fri': 5,
20
+ 'saturday': 6,
21
+ 'sat': 6,
22
+ };
23
+ /**
24
+ * Parse schedule expression based on provider type
25
+ */
26
+ function parseScheduleExpression(expression, provider) {
27
+ const normalizedProvider = provider.toLowerCase();
28
+ if (normalizedProvider === 'bigquery' || normalizedProvider === 'bq' || normalizedProvider.includes('carto')) {
29
+ return parseNaturalLanguage(expression);
30
+ }
31
+ else if (normalizedProvider === 'snowflake' || normalizedProvider === 'sf' ||
32
+ normalizedProvider === 'postgres' || normalizedProvider === 'postgresql' ||
33
+ normalizedProvider === 'redshift' || normalizedProvider === 'rs') {
34
+ return parseCron(expression);
35
+ }
36
+ else if (normalizedProvider === 'databricks' || normalizedProvider === 'databricksrest' || normalizedProvider === 'db') {
37
+ return parseQuartzCron(expression);
38
+ }
39
+ throw new Error(`Unsupported provider: ${provider}`);
40
+ }
41
+ /**
42
+ * Parse natural language schedule expressions (BigQuery/CARTO DW)
43
+ */
44
+ function parseNaturalLanguage(expr) {
45
+ const normalized = expr.toLowerCase().trim();
46
+ // Pattern: "every day HH:MM"
47
+ const dailyPattern = /^every\s+day\s+(\d{1,2}):(\d{2})$/;
48
+ const dailyMatch = normalized.match(dailyPattern);
49
+ if (dailyMatch) {
50
+ const hour = parseInt(dailyMatch[1], 10);
51
+ const minute = parseInt(dailyMatch[2], 10);
52
+ return {
53
+ time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
54
+ frequency: 4,
55
+ daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
56
+ daysOfMonth: [],
57
+ expression: expr,
58
+ repeatInterval: 1
59
+ };
60
+ }
61
+ // Pattern: "every (monday|tuesday|...) HH:MM"
62
+ const weeklyPattern = /^every\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun)\s+(\d{1,2}):(\d{2})$/;
63
+ const weeklyMatch = normalized.match(weeklyPattern);
64
+ if (weeklyMatch) {
65
+ const dayName = weeklyMatch[1];
66
+ const dayOfWeek = DAYS_OF_WEEK_MAP[dayName];
67
+ const hour = parseInt(weeklyMatch[2], 10);
68
+ const minute = parseInt(weeklyMatch[3], 10);
69
+ return {
70
+ time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
71
+ frequency: 4,
72
+ daysOfWeek: [dayOfWeek],
73
+ daysOfMonth: [],
74
+ expression: expr,
75
+ repeatInterval: 1
76
+ };
77
+ }
78
+ // Pattern: "every N hours" or "every N hour"
79
+ const hourlyPattern = /^every\s+(\d+)\s+hours?$/;
80
+ const hourlyMatch = normalized.match(hourlyPattern);
81
+ if (hourlyMatch) {
82
+ const interval = parseInt(hourlyMatch[1], 10);
83
+ return {
84
+ time: "00:00",
85
+ frequency: 4,
86
+ daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
87
+ daysOfMonth: [],
88
+ expression: expr,
89
+ repeatInterval: interval
90
+ };
91
+ }
92
+ // Pattern: "every hour"
93
+ if (normalized === 'every hour') {
94
+ return {
95
+ time: "00:00",
96
+ frequency: 4,
97
+ daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
98
+ daysOfMonth: [],
99
+ expression: expr,
100
+ repeatInterval: 1
101
+ };
102
+ }
103
+ // Pattern: "N of month HH:MM" or "N,M,O of month HH:MM"
104
+ const monthlyPattern = /^(\d+(?:,\d+)*)\s+of\s+month\s+(\d{1,2}):(\d{2})$/;
105
+ const monthlyMatch = normalized.match(monthlyPattern);
106
+ if (monthlyMatch) {
107
+ const days = monthlyMatch[1].split(',').map(d => parseInt(d, 10));
108
+ const hour = parseInt(monthlyMatch[2], 10);
109
+ const minute = parseInt(monthlyMatch[3], 10);
110
+ return {
111
+ time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
112
+ frequency: 4,
113
+ daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
114
+ daysOfMonth: days,
115
+ expression: expr,
116
+ repeatInterval: 1
117
+ };
118
+ }
119
+ // Pattern: "Nth weekday of month HH:MM" (e.g., "1st monday of month 09:00")
120
+ const ordinalWeekdayPattern = /^(\d+(?:st|nd|rd|th)?)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|mon|tue|wed|thu|fri|sat|sun)\s+of\s+month\s+(\d{1,2}):(\d{2})$/;
121
+ const ordinalWeekdayMatch = normalized.match(ordinalWeekdayPattern);
122
+ if (ordinalWeekdayMatch) {
123
+ const dayName = ordinalWeekdayMatch[2];
124
+ const dayOfWeek = DAYS_OF_WEEK_MAP[dayName];
125
+ const hour = parseInt(ordinalWeekdayMatch[3], 10);
126
+ const minute = parseInt(ordinalWeekdayMatch[4], 10);
127
+ return {
128
+ time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
129
+ frequency: 4,
130
+ daysOfWeek: [dayOfWeek],
131
+ daysOfMonth: [],
132
+ expression: expr,
133
+ repeatInterval: 1
134
+ };
135
+ }
136
+ // For any other complex BigQuery format, return a default structure
137
+ // Extract time if present
138
+ const timeMatch = normalized.match(/(\d{1,2}):(\d{2})/);
139
+ if (timeMatch) {
140
+ const hour = parseInt(timeMatch[1], 10);
141
+ const minute = parseInt(timeMatch[2], 10);
142
+ return {
143
+ time: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`,
144
+ frequency: 4,
145
+ daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
146
+ daysOfMonth: [],
147
+ expression: expr,
148
+ repeatInterval: 1
149
+ };
150
+ }
151
+ throw new Error(`Invalid BigQuery/CARTO schedule expression: ${expr}. Expected formats: "every day HH:MM", "every monday HH:MM", "every N hours", "N of month HH:MM"`);
152
+ }
153
+ /**
154
+ * Parse standard cron expression (Snowflake/PostgreSQL/Redshift)
155
+ * Format: minute hour day-of-month month day-of-week
156
+ */
157
+ function parseCron(expr) {
158
+ const parts = expr.trim().split(/\s+/);
159
+ if (parts.length < 5) {
160
+ throw new Error(`Invalid cron expression: ${expr}. Expected format: "M H D M W"`);
161
+ }
162
+ const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
163
+ // Parse time
164
+ const hourNum = parseInt(hour, 10);
165
+ const minuteNum = parseInt(minute, 10);
166
+ if (isNaN(hourNum) || isNaN(minuteNum)) {
167
+ throw new Error(`Invalid time in cron expression: ${expr}`);
168
+ }
169
+ const time = `${hourNum.toString().padStart(2, '0')}:${minuteNum.toString().padStart(2, '0')}`;
170
+ // Parse days of week (0=Sunday, 7=Sunday)
171
+ let daysOfWeek = [];
172
+ if (dayOfWeek === '*') {
173
+ daysOfWeek = [0, 1, 2, 3, 4, 5, 6];
174
+ }
175
+ else if (dayOfWeek.includes(',')) {
176
+ daysOfWeek = dayOfWeek.split(',').map(d => {
177
+ const num = parseInt(d, 10);
178
+ return num === 7 ? 0 : num; // Convert 7 to 0 (Sunday)
179
+ });
180
+ }
181
+ else if (dayOfWeek.includes('*/')) {
182
+ // Every N days - treat as all days
183
+ daysOfWeek = [0, 1, 2, 3, 4, 5, 6];
184
+ }
185
+ else {
186
+ const num = parseInt(dayOfWeek, 10);
187
+ daysOfWeek = [num === 7 ? 0 : num];
188
+ }
189
+ // Parse days of month
190
+ let daysOfMonth = [];
191
+ if (dayOfMonth !== '*' && !dayOfMonth.includes('/')) {
192
+ if (dayOfMonth.includes(',')) {
193
+ daysOfMonth = dayOfMonth.split(',').map(d => parseInt(d, 10));
194
+ }
195
+ else {
196
+ daysOfMonth = [parseInt(dayOfMonth, 10)];
197
+ }
198
+ }
199
+ // Detect interval schedules (every N hours)
200
+ let repeatInterval = 1;
201
+ if (hour.includes('*/')) {
202
+ const match = hour.match(/\*\/(\d+)/);
203
+ if (match) {
204
+ repeatInterval = parseInt(match[1], 10);
205
+ }
206
+ }
207
+ return {
208
+ time,
209
+ frequency: 4,
210
+ daysOfWeek,
211
+ daysOfMonth,
212
+ expression: expr,
213
+ repeatInterval
214
+ };
215
+ }
216
+ /**
217
+ * Parse Quartz cron expression (Databricks)
218
+ * Format: second minute hour day-of-month month day-of-week [year]
219
+ */
220
+ function parseQuartzCron(expr) {
221
+ const parts = expr.trim().split(/\s+/);
222
+ if (parts.length < 6) {
223
+ throw new Error(`Invalid Quartz cron expression: ${expr}. Expected format: "S M H D M W [Y]"`);
224
+ }
225
+ const [second, minute, hour, dayOfMonth, month, dayOfWeek] = parts;
226
+ // Parse time
227
+ const hourNum = parseInt(hour, 10);
228
+ const minuteNum = parseInt(minute, 10);
229
+ if (isNaN(hourNum) || isNaN(minuteNum)) {
230
+ throw new Error(`Invalid time in Quartz cron expression: ${expr}`);
231
+ }
232
+ const time = `${hourNum.toString().padStart(2, '0')}:${minuteNum.toString().padStart(2, '0')}`;
233
+ // Parse days of week (1=Sunday in Quartz, or MON/TUE/etc)
234
+ let daysOfWeek = [];
235
+ if (dayOfWeek === '?' || dayOfWeek === '*') {
236
+ daysOfWeek = [0, 1, 2, 3, 4, 5, 6];
237
+ }
238
+ else if (/^[A-Z]{3}$/.test(dayOfWeek.toUpperCase())) {
239
+ // Named day (MON, TUE, etc)
240
+ const dayName = dayOfWeek.toLowerCase();
241
+ const dayNum = DAYS_OF_WEEK_MAP[dayName];
242
+ if (dayNum !== undefined) {
243
+ daysOfWeek = [dayNum];
244
+ }
245
+ }
246
+ else if (dayOfWeek.includes(',')) {
247
+ daysOfWeek = dayOfWeek.split(',').map(d => {
248
+ if (/^[A-Z]{3}$/i.test(d)) {
249
+ return DAYS_OF_WEEK_MAP[d.toLowerCase()];
250
+ }
251
+ const num = parseInt(d, 10);
252
+ return num === 1 ? 0 : num - 1; // Quartz: 1=Sunday, convert to 0=Sunday
253
+ });
254
+ }
255
+ else {
256
+ const num = parseInt(dayOfWeek, 10);
257
+ if (!isNaN(num)) {
258
+ daysOfWeek = [num === 1 ? 0 : num - 1];
259
+ }
260
+ }
261
+ // Parse days of month
262
+ let daysOfMonth = [];
263
+ if (dayOfMonth !== '?' && dayOfMonth !== '*' && !dayOfMonth.includes('/')) {
264
+ if (dayOfMonth.includes(',')) {
265
+ daysOfMonth = dayOfMonth.split(',').map(d => parseInt(d, 10));
266
+ }
267
+ else {
268
+ daysOfMonth = [parseInt(dayOfMonth, 10)];
269
+ }
270
+ }
271
+ // Detect interval schedules
272
+ let repeatInterval = 1;
273
+ if (hour.includes('*/')) {
274
+ const match = hour.match(/\*\/(\d+)/);
275
+ if (match) {
276
+ repeatInterval = parseInt(match[1], 10);
277
+ }
278
+ }
279
+ return {
280
+ time,
281
+ frequency: 4,
282
+ daysOfWeek,
283
+ daysOfMonth,
284
+ expression: expr,
285
+ repeatInterval
286
+ };
287
+ }
package/jest.config.ts ADDED
@@ -0,0 +1,43 @@
1
+ import type { Config } from '@jest/types';
2
+
3
+ const config: Config.InitialOptions = {
4
+ preset: 'ts-jest',
5
+ testEnvironment: 'node',
6
+ roots: ['<rootDir>/__tests__'],
7
+ testRegex: '/__tests__/.*\\.(spec|test)\\.ts$',
8
+ moduleFileExtensions: ['ts', 'js', 'json'],
9
+ collectCoverage: true,
10
+ coverageDirectory: 'coverage',
11
+ coverageReporters: ['text', 'text-summary', 'lcov'],
12
+ collectCoverageFrom: [
13
+ 'src/**/*.ts',
14
+ '!src/logo.ts',
15
+ '!src/index.ts',
16
+ '!src/commands/**/*.ts', // Commands tested via integration tests with mocks
17
+ '!src/prompt.ts' // Interactive prompts not unit-testable
18
+ ],
19
+ coverageThreshold: {
20
+ global: {
21
+ // Raised thresholds after comprehensive unit test coverage
22
+ statements: 75,
23
+ branches: 70,
24
+ functions: 70,
25
+ lines: 75
26
+ }
27
+ },
28
+ setupFilesAfterEnv: ['<rootDir>/__tests__/setup.ts'],
29
+ testPathIgnorePatterns: [
30
+ '<rootDir>/node_modules/',
31
+ '<rootDir>/__tests__/e2e/',
32
+ '<rootDir>/__tests__/fixtures/',
33
+ '<rootDir>/__tests__/mocks/'
34
+ ],
35
+ transform: {
36
+ '\\.ts$': 'ts-jest'
37
+ },
38
+ clearMocks: true,
39
+ // Increase timeout for integration tests
40
+ testTimeout: 10000
41
+ };
42
+
43
+ export default config;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "carto-cli",
3
+ "version": "0.1.0-rc.1",
4
+ "description": "CARTO CLI - Geospatial Cloud Native Platform Admin Tool",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "carto": "./dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "bundle": "ncc build dist/index.js -o bundle && cp bundle/index.js bundle/carto",
12
+ "watch": "ncc build src/index.ts -o bundle --watch && mv bundle/index.js bundle/carto",
13
+ "pkg": "pkg bundle/carto -t node18-macos-x64,node18-linux-x64,node18-win-x64 -o bin/carto",
14
+ "start": "node dist/index.js",
15
+ "dev": "npm run build && node dist/index.js",
16
+ "build:all": "npm run build && npm run bundle && npm run pkg",
17
+ "test": "jest --config jest.config.ts",
18
+ "test:watch": "jest --config jest.config.ts --watch",
19
+ "test:unit": "jest --config jest.config.ts __tests__/unit",
20
+ "test:integration": "jest --config jest.config.ts __tests__/integration",
21
+ "test:coverage": "jest --config jest.config.ts --coverage",
22
+ "bun:dev": "bun run src/index.ts",
23
+ "bun:bundle": "bun build src/index.ts --outfile bun-bundle/carto.js --target node",
24
+ "bun:compile": "bun build src/index.ts --compile --outfile bun-bin/carto",
25
+ "bun:compile:all": "bun build src/index.ts --compile --outfile bun-bin/carto-macos && bun build src/index.ts --compile --target=bun-linux-x64 --outfile bun-bin/carto-linux && bun build src/index.ts --compile --target=bun-windows-x64 --outfile bun-bin/carto-windows.exe"
26
+ },
27
+ "keywords": [
28
+ "carto",
29
+ "cli",
30
+ "geospatial",
31
+ "admin"
32
+ ],
33
+ "author": "",
34
+ "license": "ISC",
35
+ "dependencies": {
36
+ "@types/js-yaml": "^4.0.9",
37
+ "js-yaml": "^4.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/jest": "^30.0.0",
41
+ "@types/node": "^24.7.0",
42
+ "@vercel/ncc": "^0.38.4",
43
+ "jest": "^30.2.0",
44
+ "memfs": "^4.51.1",
45
+ "pkg": "^5.8.1",
46
+ "ts-jest": "^29.4.6",
47
+ "ts-node": "^10.9.2",
48
+ "typescript": "^5.9.3"
49
+ },
50
+ "overrides": {
51
+ "node-gyp": "^11.0.0"
52
+ }
53
+ }