carto-md 1.0.17 → 1.0.18
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/README.md +3 -1
- package/package.json +1 -1
- package/src/cli/init.js +2 -0
- package/src/detector/files.js +15 -0
- package/src/detector/framework.js +58 -13
- package/src/extractors/languages/r.js +236 -0
- package/src/extractors/routes.js +1 -0
package/README.md
CHANGED
|
@@ -154,6 +154,7 @@ Leave `carto watch` running in a background terminal. Every file save updates AG
|
|
|
154
154
|
| `carto watch` | Watch files, update AGENTS.md on every save |
|
|
155
155
|
| `carto sync` | One-time refresh, no watcher |
|
|
156
156
|
| `carto impact <file>` | Show blast radius before touching a file |
|
|
157
|
+
| `carto remove` | Remove AGENTS.md and .carto/ from this project |
|
|
157
158
|
| `carto --version` | Show version |
|
|
158
159
|
|
|
159
160
|
**When to use each:**
|
|
@@ -171,6 +172,7 @@ Leave `carto watch` running in a background terminal. Every file save updates AG
|
|
|
171
172
|
| Python | FastAPI, Pydantic |
|
|
172
173
|
| JavaScript | Express, Next.js |
|
|
173
174
|
| TypeScript | Express, Next.js, Prisma |
|
|
175
|
+
| R | Plumber, Shiny |
|
|
174
176
|
| HTML | fetch() calls |
|
|
175
177
|
|
|
176
178
|
More languages via community — open an issue or see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -248,7 +250,7 @@ Carto never writes secrets into AGENTS.md. `.cartoignore` blocks `.env` files, s
|
|
|
248
250
|
|
|
249
251
|
## Contributing
|
|
250
252
|
|
|
251
|
-
Python
|
|
253
|
+
Python, JS/TS, and R today. Want Go, Ruby, Django, Rails? Open an issue — or read [CONTRIBUTING.md](CONTRIBUTING.md) to add it yourself.
|
|
252
254
|
|
|
253
255
|
---
|
|
254
256
|
|
package/package.json
CHANGED
package/src/cli/init.js
CHANGED
|
@@ -17,11 +17,13 @@ async function run(projectRoot) {
|
|
|
17
17
|
// Count files for reporting
|
|
18
18
|
const pyCount = files.routeFiles.filter(f => f.endsWith('.py')).length;
|
|
19
19
|
const jsCount = files.routeFiles.filter(f => /\.(js|ts|jsx|tsx)$/.test(f)).length;
|
|
20
|
+
const rCount = files.routeFiles.filter(f => /\.[rR]$/.test(f)).length;
|
|
20
21
|
const htmlCount = files.frontendFiles.length;
|
|
21
22
|
|
|
22
23
|
const parts = [];
|
|
23
24
|
if (pyCount > 0) parts.push(`${pyCount} Python files`);
|
|
24
25
|
if (jsCount > 0) parts.push(`${jsCount} JS/TS files`);
|
|
26
|
+
if (rCount > 0) parts.push(`${rCount} R files`);
|
|
25
27
|
if (htmlCount > 0) parts.push(`${htmlCount} HTML files`);
|
|
26
28
|
console.log(`[CARTO] Found ${parts.join(', ') || '0 files'}`);
|
|
27
29
|
|
package/src/detector/files.js
CHANGED
|
@@ -9,6 +9,7 @@ const BASE_UTILITY_BUDGET = 20;
|
|
|
9
9
|
const PYTHON_IGNORE = new Set(['__pycache__', '.venv', 'venv', 'migrations', 'node_modules', '.git', '.carto']);
|
|
10
10
|
const JS_IGNORE = new Set(['node_modules', '.git', 'dist', 'build', '.carto', '.next', '.turbo', 'coverage', 'out', '.cache', 'generated', '__generated__', 'storybook-static', 'public', 'static']);
|
|
11
11
|
const HTML_IGNORE = new Set(['node_modules', '.git', '.carto']);
|
|
12
|
+
const R_IGNORE = new Set(['.Rhistory', '.RData', 'packrat', 'renv', 'node_modules', '.git', '__pycache__', '.carto']);
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* discoverFiles(projectRoot, framework, isIgnored, secondaryFramework) → { routeFiles, modelFiles, frontendFiles }
|
|
@@ -60,6 +61,20 @@ function discoverForFramework(projectRoot, framework, ignoreFn) {
|
|
|
60
61
|
return smartSelect(jsFiles, htmlFiles, projectRoot);
|
|
61
62
|
}
|
|
62
63
|
|
|
64
|
+
if (['plumber', 'shiny', 'r-generic'].includes(framework)) {
|
|
65
|
+
const rFiles = findFilesRecursive(projectRoot, ['.r'], R_IGNORE, ignoreFn)
|
|
66
|
+
.filter(f => {
|
|
67
|
+
const base = path.basename(f);
|
|
68
|
+
return !base.startsWith('test_') && !base.startsWith('test-') && !base.endsWith('_test.R');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (rFiles.length <= MAX_FILES_TOTAL) {
|
|
72
|
+
return { routeFiles: rFiles, modelFiles: rFiles, frontendFiles: [] };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return smartSelect(rFiles, [], projectRoot);
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
// Unknown framework
|
|
64
79
|
const allCode = findFilesRecursive(projectRoot, ['.py', '.js', '.ts'], new Set(['node_modules', '.git', '__pycache__', '.venv', 'venv', '.carto']), ignoreFn);
|
|
65
80
|
const htmlFiles = findFilesRecursive(projectRoot, ['.html'], HTML_IGNORE, ignoreFn);
|
|
@@ -6,6 +6,7 @@ const IGNORE_DIRS = new Set(['node_modules', '.git', '__pycache__', '.venv', 've
|
|
|
6
6
|
// Priority order: lower index = higher priority
|
|
7
7
|
const PYTHON_PRIORITY = ['fastapi', 'django', 'flask', 'python-generic'];
|
|
8
8
|
const JS_PRIORITY = ['nextjs', 'express', 'react', 'node-generic'];
|
|
9
|
+
const R_PRIORITY = ['plumber', 'shiny', 'r-generic'];
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* detectFramework(projectRoot) → { framework, language, confidence, secondaryFramework?, secondaryLanguage? }
|
|
@@ -16,36 +17,37 @@ const JS_PRIORITY = ['nextjs', 'express', 'react', 'node-generic'];
|
|
|
16
17
|
* (primary = highest priority overall, secondary = the other language).
|
|
17
18
|
*/
|
|
18
19
|
function detectFramework(projectRoot) {
|
|
19
|
-
const candidates = findFile(projectRoot, ['requirements.txt', 'package.json', 'pyproject.toml'], 3);
|
|
20
|
+
const candidates = findFile(projectRoot, ['requirements.txt', 'package.json', 'pyproject.toml', 'DESCRIPTION'], 3);
|
|
20
21
|
|
|
21
22
|
const pythonDetections = new Set();
|
|
22
23
|
const jsDetections = new Set();
|
|
24
|
+
const rDetections = new Set();
|
|
23
25
|
|
|
24
|
-
// Check requirements.txt
|
|
25
26
|
for (const f of candidates.filter(f => path.basename(f) === 'requirements.txt')) {
|
|
26
|
-
const
|
|
27
|
-
for (const r of results) pythonDetections.add(r);
|
|
27
|
+
for (const r of detectAllFromPythonDeps(f)) pythonDetections.add(r);
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
// Check pyproject.toml
|
|
31
30
|
for (const f of candidates.filter(f => path.basename(f) === 'pyproject.toml')) {
|
|
32
|
-
const
|
|
33
|
-
for (const r of results) pythonDetections.add(r);
|
|
31
|
+
for (const r of detectAllFromPythonDeps(f)) pythonDetections.add(r);
|
|
34
32
|
}
|
|
35
33
|
|
|
36
|
-
// Check package.json
|
|
37
34
|
for (const f of candidates.filter(f => path.basename(f) === 'package.json')) {
|
|
38
|
-
const
|
|
39
|
-
|
|
35
|
+
for (const r of detectAllFromPackageJson(f)) jsDetections.add(r);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for (const f of candidates.filter(f => path.basename(f) === 'DESCRIPTION')) {
|
|
39
|
+
for (const r of detectAllFromRDescription(f)) rDetections.add(r);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (rDetections.size === 0) {
|
|
43
|
+
for (const r of detectAllFromRFiles(projectRoot)) rDetections.add(r);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
|
-
// Pick best Python framework by priority
|
|
43
46
|
const bestPython = PYTHON_PRIORITY.find(fw => pythonDetections.has(fw)) || null;
|
|
44
|
-
// Pick best JS framework by priority
|
|
45
47
|
const bestJS = JS_PRIORITY.find(fw => jsDetections.has(fw)) || null;
|
|
48
|
+
const bestR = R_PRIORITY.find(fw => rDetections.has(fw)) || null;
|
|
46
49
|
|
|
47
50
|
if (bestPython && bestJS) {
|
|
48
|
-
// Both detected — Python is primary (higher priority in the global list)
|
|
49
51
|
return {
|
|
50
52
|
framework: bestPython,
|
|
51
53
|
language: 'python',
|
|
@@ -63,6 +65,10 @@ function detectFramework(projectRoot) {
|
|
|
63
65
|
return { framework: bestJS, language: 'javascript', confidence: 'high' };
|
|
64
66
|
}
|
|
65
67
|
|
|
68
|
+
if (bestR) {
|
|
69
|
+
return { framework: bestR, language: 'r', confidence: 'high' };
|
|
70
|
+
}
|
|
71
|
+
|
|
66
72
|
return { framework: 'unknown', language: 'unknown', confidence: 'none' };
|
|
67
73
|
}
|
|
68
74
|
|
|
@@ -135,4 +141,43 @@ function detectAllFromPackageJson(filePath) {
|
|
|
135
141
|
return detected;
|
|
136
142
|
}
|
|
137
143
|
|
|
144
|
+
function detectAllFromRDescription(filePath) {
|
|
145
|
+
const detected = [];
|
|
146
|
+
let content;
|
|
147
|
+
try {
|
|
148
|
+
content = fs.readFileSync(filePath, 'utf-8').toLowerCase();
|
|
149
|
+
} catch {
|
|
150
|
+
return detected;
|
|
151
|
+
}
|
|
152
|
+
if (content.includes('plumber')) detected.push('plumber');
|
|
153
|
+
if (content.includes('shiny')) detected.push('shiny');
|
|
154
|
+
if (!detected.length) detected.push('r-generic');
|
|
155
|
+
return detected;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function detectAllFromRFiles(projectRoot) {
|
|
159
|
+
const detected = [];
|
|
160
|
+
let files;
|
|
161
|
+
try {
|
|
162
|
+
files = fs.readdirSync(projectRoot).filter(f => f.endsWith('.R') || f.endsWith('.r'));
|
|
163
|
+
} catch {
|
|
164
|
+
return detected;
|
|
165
|
+
}
|
|
166
|
+
if (!files.length) return detected;
|
|
167
|
+
|
|
168
|
+
for (const file of files.slice(0, 5)) {
|
|
169
|
+
let content;
|
|
170
|
+
try {
|
|
171
|
+
content = fs.readFileSync(path.join(projectRoot, file), 'utf-8').toLowerCase();
|
|
172
|
+
} catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (/library\s*\(\s*["']?plumber["']?\s*\)/.test(content)) detected.push('plumber');
|
|
176
|
+
if (/library\s*\(\s*["']?shiny["']?\s*\)/.test(content)) detected.push('shiny');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!detected.length) detected.push('r-generic');
|
|
180
|
+
return detected;
|
|
181
|
+
}
|
|
182
|
+
|
|
138
183
|
module.exports = { detectFramework };
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
function pathToIdentifier(routePath) {
|
|
2
|
+
const segments = routePath.split('/').filter(Boolean);
|
|
3
|
+
const last = segments[segments.length - 1] || 'handler';
|
|
4
|
+
return last.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^(\d)/, '_$1') || 'handler';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function descToIdentifier(desc) {
|
|
8
|
+
const words = desc.trim().split(/\s+/);
|
|
9
|
+
if (words.length === 1 && /^[a-zA-Z_]\w*$/.test(words[0])) return words[0];
|
|
10
|
+
return words
|
|
11
|
+
.map((w, i) => i === 0 ? w.toLowerCase() : w[0].toUpperCase() + w.slice(1).toLowerCase())
|
|
12
|
+
.join('')
|
|
13
|
+
.replace(/[^a-zA-Z0-9_]/g, '')
|
|
14
|
+
.replace(/^(\d)/, '_$1') || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function extractRoutes(content) {
|
|
18
|
+
const routes = [];
|
|
19
|
+
const lines = content.split('\n');
|
|
20
|
+
const routePattern = /^#\*\s+@(get|post|put|delete|patch|options|head)\s+(\S+)/i;
|
|
21
|
+
const descPattern = /^#\*\s+(?!@)(.+)/;
|
|
22
|
+
|
|
23
|
+
for (let i = 0; i < lines.length; i++) {
|
|
24
|
+
const routeMatch = lines[i].match(routePattern);
|
|
25
|
+
if (!routeMatch) continue;
|
|
26
|
+
|
|
27
|
+
const method = routeMatch[1].toUpperCase();
|
|
28
|
+
const routePath = routeMatch[2].trim();
|
|
29
|
+
|
|
30
|
+
let functionName = pathToIdentifier(routePath);
|
|
31
|
+
for (let j = i - 1; j >= Math.max(0, i - 10); j--) {
|
|
32
|
+
const line = lines[j].trim();
|
|
33
|
+
if (!line.startsWith('#')) break;
|
|
34
|
+
const descMatch = line.match(descPattern);
|
|
35
|
+
if (descMatch) {
|
|
36
|
+
const derived = descToIdentifier(descMatch[1]);
|
|
37
|
+
if (derived) functionName = derived;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
routes.push({ method, path: routePath, functionName });
|
|
43
|
+
}
|
|
44
|
+
return routes;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function extractFunctions(content) {
|
|
48
|
+
const functions = [];
|
|
49
|
+
const lines = content.split('\n');
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < lines.length; i++) {
|
|
52
|
+
const line = lines[i];
|
|
53
|
+
if (!/^\w+\s*<-\s*function\s*\(/.test(line)) continue;
|
|
54
|
+
|
|
55
|
+
const nameMatch = line.match(/^(\w+)\s*<-\s*function\s*\(/);
|
|
56
|
+
if (!nameMatch) continue;
|
|
57
|
+
|
|
58
|
+
const name = nameMatch[1];
|
|
59
|
+
if (name.startsWith('.')) continue;
|
|
60
|
+
|
|
61
|
+
let combined = line;
|
|
62
|
+
let depth = 0;
|
|
63
|
+
for (const ch of line) {
|
|
64
|
+
if (ch === '(') depth++;
|
|
65
|
+
else if (ch === ')') depth--;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let safety = 0;
|
|
69
|
+
while (depth > 0 && safety < 15 && i + 1 < lines.length) {
|
|
70
|
+
i++;
|
|
71
|
+
safety++;
|
|
72
|
+
const next = lines[i].trim();
|
|
73
|
+
combined += ' ' + next;
|
|
74
|
+
for (const ch of next) {
|
|
75
|
+
if (ch === '(') depth++;
|
|
76
|
+
else if (ch === ')') depth--;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const paramMatch = combined.match(/function\s*\(([^)]*)\)/);
|
|
81
|
+
if (!paramMatch) continue;
|
|
82
|
+
|
|
83
|
+
const params = paramMatch[1]
|
|
84
|
+
.split(',')
|
|
85
|
+
.map(p => p.split('=')[0].trim())
|
|
86
|
+
.filter(p => p.length > 0)
|
|
87
|
+
.join(', ');
|
|
88
|
+
|
|
89
|
+
functions.push({ name, params: params || '—', returnType: '—' });
|
|
90
|
+
}
|
|
91
|
+
return functions;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function collapseParens(content) {
|
|
95
|
+
const lines = content.split('\n');
|
|
96
|
+
const result = [];
|
|
97
|
+
let i = 0;
|
|
98
|
+
while (i < lines.length) {
|
|
99
|
+
let depth = 0;
|
|
100
|
+
for (const ch of lines[i]) {
|
|
101
|
+
if (ch === '(') depth++;
|
|
102
|
+
else if (ch === ')') depth--;
|
|
103
|
+
}
|
|
104
|
+
if (depth <= 0) {
|
|
105
|
+
result.push(lines[i]);
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
let combined = lines[i];
|
|
110
|
+
let safety = 0;
|
|
111
|
+
while (depth > 0 && safety < 20 && i + 1 < lines.length) {
|
|
112
|
+
i++;
|
|
113
|
+
safety++;
|
|
114
|
+
const next = lines[i].trim();
|
|
115
|
+
combined += ' ' + next;
|
|
116
|
+
for (const ch of next) {
|
|
117
|
+
if (ch === '(') depth++;
|
|
118
|
+
else if (ch === ')') depth--;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
result.push(combined);
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
return result.join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function findBalancedEnd(str, openPos) {
|
|
128
|
+
let depth = 1;
|
|
129
|
+
let pos = openPos + 1;
|
|
130
|
+
while (depth > 0 && pos < str.length) {
|
|
131
|
+
if (str[pos] === '(') depth++;
|
|
132
|
+
else if (str[pos] === ')') depth--;
|
|
133
|
+
pos++;
|
|
134
|
+
}
|
|
135
|
+
return pos - 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function extractModels(content) {
|
|
139
|
+
const models = [];
|
|
140
|
+
const collapsed = collapseParens(content);
|
|
141
|
+
|
|
142
|
+
const setClassRe = /setClass\s*\(\s*["'](\w+)["']/g;
|
|
143
|
+
let m;
|
|
144
|
+
while ((m = setClassRe.exec(collapsed)) !== null) {
|
|
145
|
+
const className = m[1];
|
|
146
|
+
const slotsIdx = collapsed.indexOf('slots', m.index);
|
|
147
|
+
if (slotsIdx === -1 || slotsIdx > m.index + 300) continue;
|
|
148
|
+
const listIdx = collapsed.indexOf('list(', slotsIdx);
|
|
149
|
+
if (listIdx === -1) continue;
|
|
150
|
+
const openPos = listIdx + 4;
|
|
151
|
+
const closePos = findBalancedEnd(collapsed, openPos);
|
|
152
|
+
const slotsContent = collapsed.slice(openPos + 1, closePos);
|
|
153
|
+
const fields = [];
|
|
154
|
+
const slotRe = /(\w+)\s*=\s*["']([^"']+)["']/g;
|
|
155
|
+
let sm;
|
|
156
|
+
while ((sm = slotRe.exec(slotsContent)) !== null) {
|
|
157
|
+
fields.push({ name: sm[1], type: sm[2] });
|
|
158
|
+
}
|
|
159
|
+
models.push({ className, fields });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const dfRe = /^(\w+)\s*<-\s*data\.frame\s*\(/gm;
|
|
163
|
+
while ((m = dfRe.exec(collapsed)) !== null) {
|
|
164
|
+
const className = m[1];
|
|
165
|
+
const openPos = m.index + m[0].length - 1;
|
|
166
|
+
const closePos = findBalancedEnd(collapsed, openPos);
|
|
167
|
+
const innerContent = collapsed.slice(openPos + 1, closePos);
|
|
168
|
+
const fields = [];
|
|
169
|
+
const colRe = /\b(\w+)\s*=\s*(\w+)\s*\(/g;
|
|
170
|
+
let cm;
|
|
171
|
+
while ((cm = colRe.exec(innerContent)) !== null) {
|
|
172
|
+
fields.push({ name: cm[1], type: cm[2] });
|
|
173
|
+
}
|
|
174
|
+
if (fields.length > 0) {
|
|
175
|
+
models.push({ className, fields });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const r6Re = /^(\w+)\s*<-\s*(?:R6::)?R6Class\s*\(\s*["'](\w+)["']/gm;
|
|
180
|
+
while ((m = r6Re.exec(collapsed)) !== null) {
|
|
181
|
+
const className = m[2];
|
|
182
|
+
const publicIdx = collapsed.indexOf('public', m.index);
|
|
183
|
+
if (publicIdx === -1 || publicIdx > m.index + 600) continue;
|
|
184
|
+
const listIdx = collapsed.indexOf('list(', publicIdx);
|
|
185
|
+
if (listIdx === -1) continue;
|
|
186
|
+
const openPos = listIdx + 4;
|
|
187
|
+
const closePos = findBalancedEnd(collapsed, openPos);
|
|
188
|
+
const publicContent = collapsed.slice(openPos + 1, closePos);
|
|
189
|
+
const fields = [];
|
|
190
|
+
const fieldRe = /\b(\w+)\s*=\s*(?!function\s*\()(\w+|["'][^"']*["'])/g;
|
|
191
|
+
let fm;
|
|
192
|
+
while ((fm = fieldRe.exec(publicContent)) !== null) {
|
|
193
|
+
const name = fm[1];
|
|
194
|
+
const rawVal = fm[2].trim();
|
|
195
|
+
let type = 'any';
|
|
196
|
+
if (/^["']/.test(rawVal)) type = 'character';
|
|
197
|
+
else if (/^\d/.test(rawVal)) type = 'numeric';
|
|
198
|
+
else if (rawVal === 'TRUE' || rawVal === 'FALSE') type = 'logical';
|
|
199
|
+
fields.push({ name, type });
|
|
200
|
+
}
|
|
201
|
+
models.push({ className, fields });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return models;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function extractEnvVars(content) {
|
|
208
|
+
const vars = new Set();
|
|
209
|
+
const re = /Sys\.getenv\s*\(\s*["']([A-Z_][A-Z0-9_]*)["']\s*\)/g;
|
|
210
|
+
let m;
|
|
211
|
+
while ((m = re.exec(content)) !== null) {
|
|
212
|
+
vars.add(m[1]);
|
|
213
|
+
}
|
|
214
|
+
return [...vars].sort();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
name: 'r',
|
|
219
|
+
extensions: ['.r', '.R'],
|
|
220
|
+
extract(content, filename) {
|
|
221
|
+
try {
|
|
222
|
+
return {
|
|
223
|
+
routes: extractRoutes(content),
|
|
224
|
+
models: extractModels(content),
|
|
225
|
+
functions: extractFunctions(content),
|
|
226
|
+
envVars: extractEnvVars(content),
|
|
227
|
+
dbTables: [],
|
|
228
|
+
fetches: [],
|
|
229
|
+
storageKeys: [],
|
|
230
|
+
};
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.warn(`[CARTO] r plugin error on ${filename}: ${err.message}`);
|
|
233
|
+
return { routes: [], models: [], functions: [], envVars: [], dbTables: [], fetches: [], storageKeys: [] };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
package/src/extractors/routes.js
CHANGED
|
@@ -43,6 +43,7 @@ function extractRoutes(content) {
|
|
|
43
43
|
const lines = collapsed.split('\n');
|
|
44
44
|
|
|
45
45
|
for (let i = 0; i < lines.length; i++) {
|
|
46
|
+
if (/^\s*#/.test(lines[i])) { decoratorPattern.lastIndex = 0; continue; }
|
|
46
47
|
const match = decoratorPattern.exec(lines[i]);
|
|
47
48
|
if (match) {
|
|
48
49
|
// Look ahead up to 5 lines for the function definition
|