codz-cli 0.0.0-stage → 0.1.2
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/BETA.md +91 -0
- package/LICENSE +21 -0
- package/README.md +525 -2
- package/assets/gradle-wrapper/README.txt +2 -0
- package/assets/gradle-wrapper/gradle-wrapper.jar +0 -0
- package/assets/gradle-wrapper/gradlew +251 -0
- package/assets/gradle-wrapper/gradlew.bat +94 -0
- package/bin/codz.js +33 -0
- package/dist/adapters/android.js +321 -0
- package/dist/adapters/androidSetup.js +277 -0
- package/dist/adapters/common.js +114 -0
- package/dist/adapters/dartGen.js +83 -0
- package/dist/adapters/flutter.js +291 -0
- package/dist/adapters/flutterGen.js +204 -0
- package/dist/adapters/flutterScan.js +312 -0
- package/dist/adapters/flutterSetup.js +252 -0
- package/dist/adapters/flutterStyles.js +130 -0
- package/dist/adapters/generic.js +20 -0
- package/dist/adapters/getx.js +423 -0
- package/dist/adapters/index.js +28 -0
- package/dist/adapters/ios.js +440 -0
- package/dist/adapters/kotlinGen.js +110 -0
- package/dist/adapters/models.js +116 -0
- package/dist/adapters/nextjs.js +243 -0
- package/dist/adapters/nextjsGen.js +285 -0
- package/dist/adapters/nextjsScan.js +364 -0
- package/dist/adapters/nextjsSetup.js +119 -0
- package/dist/adapters/react.js +89 -0
- package/dist/adapters/reactNative.js +109 -0
- package/dist/adapters/reactNativeSetup.js +236 -0
- package/dist/adapters/reactQuery.js +89 -0
- package/dist/adapters/reactSetup.js +150 -0
- package/dist/adapters/source.js +165 -0
- package/dist/cli.js +1175 -0
- package/dist/core/cache.js +76 -0
- package/dist/core/changeset.js +404 -0
- package/dist/core/commands.js +239 -0
- package/dist/core/config.js +45 -0
- package/dist/core/diff.js +143 -0
- package/dist/core/events.js +41 -0
- package/dist/core/fsutil.js +122 -0
- package/dist/core/git.js +36 -0
- package/dist/core/jsonEdit.js +113 -0
- package/dist/core/merge.js +159 -0
- package/dist/core/proc.js +23 -0
- package/dist/core/safe.js +75 -0
- package/dist/core/templates.js +100 -0
- package/dist/core/tsAst.js +112 -0
- package/dist/core/types.js +12 -0
- package/dist/core/validateConfig.js +172 -0
- package/dist/core/workflow.js +72 -0
- package/dist/core/workspace.js +80 -0
- package/dist/core/yaml.js +126 -0
- package/dist/core/yamlDoc.js +367 -0
- package/dist/detector.js +95 -0
- package/dist/input/apiSpec.js +395 -0
- package/dist/input/curl.js +259 -0
- package/dist/input/endpoint.js +220 -0
- package/dist/input/har.js +120 -0
- package/dist/input/infer.js +174 -0
- package/dist/input/naming.js +145 -0
- package/dist/input/paste.js +130 -0
- package/dist/skills/androidSkeleton.js +292 -0
- package/dist/skills/androidTests.js +221 -0
- package/dist/skills/androidUi.js +561 -0
- package/dist/skills/androidViews.js +536 -0
- package/dist/skills/apiAdd.js +99 -0
- package/dist/skills/apiDiff.js +271 -0
- package/dist/skills/apiEdit.js +547 -0
- package/dist/skills/apiImport.js +127 -0
- package/dist/skills/apiList.js +35 -0
- package/dist/skills/apiSync.js +221 -0
- package/dist/skills/auth.js +1304 -0
- package/dist/skills/completion.js +142 -0
- package/dist/skills/config.js +93 -0
- package/dist/skills/create.js +638 -0
- package/dist/skills/createDemo.js +1026 -0
- package/dist/skills/doctor.js +304 -0
- package/dist/skills/doctorFix.js +214 -0
- package/dist/skills/explain.js +61 -0
- package/dist/skills/feature.js +418 -0
- package/dist/skills/featureEdit.js +225 -0
- package/dist/skills/featureLinks.js +179 -0
- package/dist/skills/init.js +66 -0
- package/dist/skills/iosUi.js +468 -0
- package/dist/skills/locale.js +335 -0
- package/dist/skills/mcp.js +175 -0
- package/dist/skills/mock.js +482 -0
- package/dist/skills/module.js +23 -0
- package/dist/skills/plans.js +66 -0
- package/dist/skills/prefill.js +84 -0
- package/dist/skills/reactNativeUi.js +388 -0
- package/dist/skills/reactUi.js +352 -0
- package/dist/skills/report.js +75 -0
- package/dist/skills/route.js +230 -0
- package/dist/skills/screen.js +597 -0
- package/dist/skills/screenForm.js +410 -0
- package/dist/skills/setup.js +28 -0
- package/dist/skills/setupDoctor.js +226 -0
- package/dist/skills/templatesCmd.js +32 -0
- package/dist/skills/testgen.js +331 -0
- package/dist/skills/theme.js +388 -0
- package/dist/starters/auth.js +144 -0
- package/dist/starters/commands.js +283 -0
- package/dist/starters/engine.js +315 -0
- package/dist/starters/home.js +42 -0
- package/dist/starters/http.js +93 -0
- package/dist/starters/manifest.js +285 -0
- package/dist/starters/paths.js +70 -0
- package/dist/starters/publish.js +150 -0
- package/dist/starters/registry.js +258 -0
- package/dist/starters/sign.js +72 -0
- package/dist/starters/source.js +98 -0
- package/dist/starters/strictJson.js +148 -0
- package/dist/starters/tar.js +180 -0
- package/docs/COMMANDS.md +527 -0
- package/examples/openapi.yaml +319 -0
- package/package.json +57 -4
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// A YAML reader for OpenAPI and Swagger files: block maps and sequences,
|
|
2
|
+
// maps inside sequences, quoted and plain scalars, block scalars (| and >),
|
|
3
|
+
// single- and multi-line flow collections, anchors, aliases and `<<` merges.
|
|
4
|
+
// It returns plain JSON-like values. Tags and multiple documents are not supported.
|
|
5
|
+
import { ToolError } from "./types.js";
|
|
6
|
+
function stripComment(line) {
|
|
7
|
+
let q = null;
|
|
8
|
+
for (let i = 0; i < line.length; i++) {
|
|
9
|
+
const c = line[i];
|
|
10
|
+
if (q) {
|
|
11
|
+
if (c === q) {
|
|
12
|
+
if (q === "'" && line[i + 1] === "'")
|
|
13
|
+
i++;
|
|
14
|
+
else if (q === '"' && line[i - 1] === '\\')
|
|
15
|
+
continue;
|
|
16
|
+
else
|
|
17
|
+
q = null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
else if (c === '"' || c === "'") {
|
|
21
|
+
// Quotes only open a string at the start of a value.
|
|
22
|
+
const before = line.slice(0, i).trimEnd();
|
|
23
|
+
if (before === '' || /[:\-,[{]$/.test(before))
|
|
24
|
+
q = c;
|
|
25
|
+
}
|
|
26
|
+
else if (c === '#' && (i === 0 || /\s/.test(line[i - 1]))) {
|
|
27
|
+
return line.slice(0, i);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return line;
|
|
31
|
+
}
|
|
32
|
+
function plainScalar(s) {
|
|
33
|
+
const t = s.trim();
|
|
34
|
+
if (t === '' || t === '~' || /^null$/i.test(t))
|
|
35
|
+
return null;
|
|
36
|
+
if (/^(true|false)$/i.test(t))
|
|
37
|
+
return t.toLowerCase() === 'true';
|
|
38
|
+
if (/^[-+]?\d+$/.test(t))
|
|
39
|
+
return Number(t);
|
|
40
|
+
if (/^[-+]?(\d+\.\d*|\.\d+|\d+)([eE][-+]?\d+)?$/.test(t))
|
|
41
|
+
return Number(t);
|
|
42
|
+
return t;
|
|
43
|
+
}
|
|
44
|
+
function quoted(s) {
|
|
45
|
+
const q = s[0];
|
|
46
|
+
const body = s.slice(1, -1);
|
|
47
|
+
if (q === "'")
|
|
48
|
+
return body.replace(/''/g, "'");
|
|
49
|
+
return body.replace(/\\(["\\/bfnrt]|u[0-9a-fA-F]{4})/g, (_m, e) => {
|
|
50
|
+
const map = { '"': '"', '\\': '\\', '/': '/', b: '\b', f: '\f', n: '\n', r: '\r', t: '\t' };
|
|
51
|
+
return e.startsWith('u') ? String.fromCharCode(parseInt(e.slice(1), 16)) : map[e];
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Parses a flow collection or scalar such as [a, "b", {c: 1}]. */
|
|
55
|
+
function parseFlow(src, anchors) {
|
|
56
|
+
let i = 0;
|
|
57
|
+
const ws = () => {
|
|
58
|
+
while (i < src.length && /\s/.test(src[i]))
|
|
59
|
+
i++;
|
|
60
|
+
};
|
|
61
|
+
const value = () => {
|
|
62
|
+
ws();
|
|
63
|
+
const c = src[i];
|
|
64
|
+
if (c === '[') {
|
|
65
|
+
i++;
|
|
66
|
+
const arr = [];
|
|
67
|
+
for (;;) {
|
|
68
|
+
ws();
|
|
69
|
+
if (src[i] === ']') {
|
|
70
|
+
i++;
|
|
71
|
+
return arr;
|
|
72
|
+
}
|
|
73
|
+
arr.push(value());
|
|
74
|
+
ws();
|
|
75
|
+
if (src[i] === ',')
|
|
76
|
+
i++;
|
|
77
|
+
else if (src[i] === ']') {
|
|
78
|
+
i++;
|
|
79
|
+
return arr;
|
|
80
|
+
}
|
|
81
|
+
else
|
|
82
|
+
throw new ToolError('yaml_parse', `Bad flow sequence near "${src.slice(i, i + 20)}"`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (c === '{') {
|
|
86
|
+
i++;
|
|
87
|
+
const obj = {};
|
|
88
|
+
for (;;) {
|
|
89
|
+
ws();
|
|
90
|
+
if (src[i] === '}') {
|
|
91
|
+
i++;
|
|
92
|
+
return obj;
|
|
93
|
+
}
|
|
94
|
+
const k = String(scalarToken(true));
|
|
95
|
+
ws();
|
|
96
|
+
let v = null;
|
|
97
|
+
if (src[i] === ':') {
|
|
98
|
+
i++;
|
|
99
|
+
v = value();
|
|
100
|
+
}
|
|
101
|
+
obj[k] = v;
|
|
102
|
+
ws();
|
|
103
|
+
if (src[i] === ',')
|
|
104
|
+
i++;
|
|
105
|
+
else if (src[i] === '}') {
|
|
106
|
+
i++;
|
|
107
|
+
return obj;
|
|
108
|
+
}
|
|
109
|
+
else
|
|
110
|
+
throw new ToolError('yaml_parse', `Bad flow mapping near "${src.slice(i, i + 20)}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (c === '*') {
|
|
114
|
+
i++;
|
|
115
|
+
const m = /^[\w-]+/.exec(src.slice(i));
|
|
116
|
+
if (!m)
|
|
117
|
+
throw new ToolError('yaml_parse', `An alias (*) needs a name near "${src.slice(i - 1, i + 19)}"`);
|
|
118
|
+
i += m[0].length;
|
|
119
|
+
return structuredClone(anchors.get(m[0]) ?? null);
|
|
120
|
+
}
|
|
121
|
+
return scalarToken(false);
|
|
122
|
+
};
|
|
123
|
+
const scalarToken = (isKey) => {
|
|
124
|
+
ws();
|
|
125
|
+
const c = src[i];
|
|
126
|
+
if (c === '"' || c === "'") {
|
|
127
|
+
let j = i + 1;
|
|
128
|
+
while (j < src.length) {
|
|
129
|
+
if (src[j] === c) {
|
|
130
|
+
if (c === "'" && src[j + 1] === "'") {
|
|
131
|
+
j += 2;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (c === '"' && src[j] === '\\')
|
|
137
|
+
j++;
|
|
138
|
+
j++;
|
|
139
|
+
}
|
|
140
|
+
const s = quoted(src.slice(i, j + 1));
|
|
141
|
+
i = j + 1;
|
|
142
|
+
return s;
|
|
143
|
+
}
|
|
144
|
+
let j = i;
|
|
145
|
+
while (j < src.length && !',]}'.includes(src[j]) && !(isKey && src[j] === ':' && (src[j + 1] === ' ' || src[j + 1] === undefined))) {
|
|
146
|
+
if (!isKey && src[j] === ':' && src[j + 1] === ' ')
|
|
147
|
+
break;
|
|
148
|
+
j++;
|
|
149
|
+
}
|
|
150
|
+
const raw = src.slice(i, j);
|
|
151
|
+
i = j;
|
|
152
|
+
return isKey ? raw.trim() : plainScalar(raw);
|
|
153
|
+
};
|
|
154
|
+
const v = value();
|
|
155
|
+
ws();
|
|
156
|
+
if (i < src.length)
|
|
157
|
+
throw new ToolError('yaml_parse', `Unexpected text after a flow value: "${src.slice(i, i + 20)}"`);
|
|
158
|
+
return v;
|
|
159
|
+
}
|
|
160
|
+
/** Splits "key: value" at the first mapping colon outside quotes. */
|
|
161
|
+
function splitKey(text) {
|
|
162
|
+
if (text.startsWith('"') || text.startsWith("'")) {
|
|
163
|
+
const q = text[0];
|
|
164
|
+
let j = 1;
|
|
165
|
+
while (j < text.length && text[j] !== q)
|
|
166
|
+
j += text[j] === '\\' && q === '"' ? 2 : 1;
|
|
167
|
+
const after = text.slice(j + 1);
|
|
168
|
+
const m = /^\s*:(\s|$)/.exec(after);
|
|
169
|
+
if (!m)
|
|
170
|
+
return null;
|
|
171
|
+
return [quoted(text.slice(0, j + 1)), after.slice(m[0].length).trim()];
|
|
172
|
+
}
|
|
173
|
+
if (/^[[{]/.test(text))
|
|
174
|
+
return null;
|
|
175
|
+
const m = /^([^:#]*?[^\s:])?\s*:(\s+|$)/.exec(text);
|
|
176
|
+
if (!m || m[1] === undefined)
|
|
177
|
+
return null;
|
|
178
|
+
return [m[1].trim(), text.slice(m[0].length).trim()];
|
|
179
|
+
}
|
|
180
|
+
export function parseYamlDocument(source) {
|
|
181
|
+
const raw = source.replace(/\r\n/g, '\n').replace(/^/, '').split('\n');
|
|
182
|
+
const lines = raw.map((r, idx) => {
|
|
183
|
+
const t = stripComment(r);
|
|
184
|
+
if (t.trim() === '' || /^(---|\.\.\.)\s*$/.test(t))
|
|
185
|
+
return null;
|
|
186
|
+
if (/^\t/.test(r))
|
|
187
|
+
throw new ToolError('yaml_parse', `Line ${idx + 1}: tabs are not allowed for indentation in YAML.`);
|
|
188
|
+
return { indent: t.length - t.trimStart().length, text: t.trim(), no: idx + 1 };
|
|
189
|
+
});
|
|
190
|
+
const anchors = new Map();
|
|
191
|
+
let i = 0;
|
|
192
|
+
const peek = () => {
|
|
193
|
+
while (i < lines.length && lines[i] === null)
|
|
194
|
+
i++;
|
|
195
|
+
return i < lines.length ? lines[i] : null;
|
|
196
|
+
};
|
|
197
|
+
const blockScalar = (header, parentIndent) => {
|
|
198
|
+
const style = header[0];
|
|
199
|
+
const chomp = header.includes('-') ? 'strip' : header.includes('+') ? 'keep' : 'clip';
|
|
200
|
+
const body = [];
|
|
201
|
+
let blockIndent = -1;
|
|
202
|
+
while (i < raw.length) {
|
|
203
|
+
const r = raw[i];
|
|
204
|
+
if (r.trim() === '') {
|
|
205
|
+
body.push('');
|
|
206
|
+
i++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const ind = r.length - r.trimStart().length;
|
|
210
|
+
if (ind <= parentIndent)
|
|
211
|
+
break;
|
|
212
|
+
if (blockIndent === -1)
|
|
213
|
+
blockIndent = ind;
|
|
214
|
+
if (ind < blockIndent)
|
|
215
|
+
break;
|
|
216
|
+
body.push(r.slice(blockIndent));
|
|
217
|
+
i++;
|
|
218
|
+
}
|
|
219
|
+
while (body.length && body[body.length - 1] === '' && chomp !== 'keep')
|
|
220
|
+
body.pop();
|
|
221
|
+
let text = style === '|' ? body.join('\n') : body.reduce((acc, l, k) => (k === 0 ? l : l === '' ? acc + '\n' : acc.endsWith('\n') ? acc + l : `${acc} ${l}`), '');
|
|
222
|
+
if (chomp !== 'strip' && text)
|
|
223
|
+
text += '\n';
|
|
224
|
+
return text;
|
|
225
|
+
};
|
|
226
|
+
const flowSpanning = (start) => {
|
|
227
|
+
let text = start;
|
|
228
|
+
const balanced = (t) => {
|
|
229
|
+
let d = 0;
|
|
230
|
+
let q = null;
|
|
231
|
+
for (const c of t) {
|
|
232
|
+
if (q) {
|
|
233
|
+
if (c === q)
|
|
234
|
+
q = null;
|
|
235
|
+
}
|
|
236
|
+
else if (c === '"' || c === "'")
|
|
237
|
+
q = c;
|
|
238
|
+
else if (c === '[' || c === '{')
|
|
239
|
+
d++;
|
|
240
|
+
else if (c === ']' || c === '}')
|
|
241
|
+
d--;
|
|
242
|
+
}
|
|
243
|
+
return d <= 0;
|
|
244
|
+
};
|
|
245
|
+
while (!balanced(text)) {
|
|
246
|
+
const l = peek();
|
|
247
|
+
if (!l)
|
|
248
|
+
throw new ToolError('yaml_parse', 'A flow collection ([...] or {...}) is not closed.');
|
|
249
|
+
text += ' ' + l.text;
|
|
250
|
+
i++;
|
|
251
|
+
}
|
|
252
|
+
return text;
|
|
253
|
+
};
|
|
254
|
+
const value = (rest, parentIndent) => {
|
|
255
|
+
let r = rest;
|
|
256
|
+
let anchor;
|
|
257
|
+
const a = /^&([\w-]+)\s*/.exec(r);
|
|
258
|
+
if (a) {
|
|
259
|
+
anchor = a[1];
|
|
260
|
+
r = r.slice(a[0].length);
|
|
261
|
+
}
|
|
262
|
+
let v;
|
|
263
|
+
if (/^!\S*\s*/.test(r))
|
|
264
|
+
r = r.replace(/^!\S*\s*/, ''); // ignore tags
|
|
265
|
+
if (r === '') {
|
|
266
|
+
const next = peek();
|
|
267
|
+
if (next && next.indent > parentIndent)
|
|
268
|
+
v = node(next.indent);
|
|
269
|
+
else if (next && next.indent === parentIndent && /^-(\s|$)/.test(next.text))
|
|
270
|
+
v = seq(parentIndent);
|
|
271
|
+
else
|
|
272
|
+
v = null;
|
|
273
|
+
}
|
|
274
|
+
else if (r.startsWith('*')) {
|
|
275
|
+
v = structuredClone(anchors.get(r.slice(1).trim()) ?? null);
|
|
276
|
+
}
|
|
277
|
+
else if (/^[|>][-+0-9]*$/.test(r)) {
|
|
278
|
+
v = blockScalar(r, parentIndent);
|
|
279
|
+
}
|
|
280
|
+
else if (r.startsWith('[') || r.startsWith('{')) {
|
|
281
|
+
v = parseFlow(flowSpanning(r), anchors);
|
|
282
|
+
}
|
|
283
|
+
else if (r.startsWith('"') || r.startsWith("'")) {
|
|
284
|
+
let t = r;
|
|
285
|
+
// A double-quoted string may continue on the next lines.
|
|
286
|
+
while (!(t.length > 1 && t.endsWith(t[0]) && !(t[0] === '"' && t.endsWith('\\"')))) {
|
|
287
|
+
const l = peek();
|
|
288
|
+
if (!l)
|
|
289
|
+
break;
|
|
290
|
+
t += ' ' + l.text;
|
|
291
|
+
i++;
|
|
292
|
+
}
|
|
293
|
+
v = quoted(t);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
let t = r;
|
|
297
|
+
// Multi-line plain scalars.
|
|
298
|
+
for (let l = peek(); l && l.indent > parentIndent && !splitKey(l.text) && !/^-(\s|$)/.test(l.text); l = peek()) {
|
|
299
|
+
t += ' ' + l.text;
|
|
300
|
+
i++;
|
|
301
|
+
}
|
|
302
|
+
v = plainScalar(t);
|
|
303
|
+
}
|
|
304
|
+
if (anchor)
|
|
305
|
+
anchors.set(anchor, structuredClone(v));
|
|
306
|
+
return v;
|
|
307
|
+
};
|
|
308
|
+
const map = (indent) => {
|
|
309
|
+
const obj = {};
|
|
310
|
+
for (let l = peek(); l && l.indent === indent; l = peek()) {
|
|
311
|
+
const kv = splitKey(l.text);
|
|
312
|
+
if (!kv)
|
|
313
|
+
throw new ToolError('yaml_parse', `Line ${l.no}: expected "key: value".`);
|
|
314
|
+
i++;
|
|
315
|
+
const v = value(kv[1], indent);
|
|
316
|
+
if (kv[0] === '<<') {
|
|
317
|
+
for (const src of Array.isArray(v) ? v : [v])
|
|
318
|
+
if (src && typeof src === 'object')
|
|
319
|
+
Object.assign(obj, { ...src, ...obj });
|
|
320
|
+
}
|
|
321
|
+
else
|
|
322
|
+
obj[kv[0]] = v;
|
|
323
|
+
}
|
|
324
|
+
return obj;
|
|
325
|
+
};
|
|
326
|
+
const seq = (indent) => {
|
|
327
|
+
const arr = [];
|
|
328
|
+
for (let l = peek(); l && l.indent === indent && /^-(\s|$)/.test(l.text); l = peek()) {
|
|
329
|
+
const rest = l.text.slice(1).trimStart();
|
|
330
|
+
if (rest === '') {
|
|
331
|
+
i++;
|
|
332
|
+
const next = peek();
|
|
333
|
+
arr.push(next && next.indent > indent ? node(next.indent) : null);
|
|
334
|
+
}
|
|
335
|
+
else if (splitKey(rest) && !rest.startsWith('[') && !rest.startsWith('{')) {
|
|
336
|
+
// "- key: value" starts a map indented at the column of "key".
|
|
337
|
+
const col = indent + (l.text.length - rest.length);
|
|
338
|
+
lines[i] = { indent: col, text: rest, no: l.no };
|
|
339
|
+
arr.push(map(col));
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
i++;
|
|
343
|
+
arr.push(value(rest, indent));
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return arr;
|
|
347
|
+
};
|
|
348
|
+
const node = (indent) => {
|
|
349
|
+
const l = peek();
|
|
350
|
+
if (!l)
|
|
351
|
+
return null;
|
|
352
|
+
if (/^-(\s|$)/.test(l.text))
|
|
353
|
+
return seq(l.indent);
|
|
354
|
+
if (splitKey(l.text))
|
|
355
|
+
return map(l.indent);
|
|
356
|
+
i++;
|
|
357
|
+
return value(l.text, indent - 1);
|
|
358
|
+
};
|
|
359
|
+
const first = peek();
|
|
360
|
+
if (!first)
|
|
361
|
+
return null;
|
|
362
|
+
const result = node(first.indent);
|
|
363
|
+
const left = peek();
|
|
364
|
+
if (left)
|
|
365
|
+
throw new ToolError('yaml_parse', `Line ${left.no}: could not read this line (check its indentation).`);
|
|
366
|
+
return result;
|
|
367
|
+
}
|
package/dist/detector.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Framework detection from signature files. Only frameworks with an adapter
|
|
2
|
+
// are "supported"; others are reported by name and handled by the generic adapter.
|
|
3
|
+
import { readdirSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { IGNORED_DIRS, isDir, readJson, readText, toPosix } from "./core/fsutil.js";
|
|
6
|
+
export function detectAt(dir) {
|
|
7
|
+
const pubspec = readText(join(dir, 'pubspec.yaml'));
|
|
8
|
+
if (pubspec !== null) {
|
|
9
|
+
if (/^\s+sdk:\s*flutter\b/m.test(pubspec) || /^flutter:/m.test(pubspec))
|
|
10
|
+
return { framework: 'flutter', label: 'Flutter', supported: true };
|
|
11
|
+
return { framework: 'generic', label: 'Dart', supported: false };
|
|
12
|
+
}
|
|
13
|
+
const pkg = readJson(join(dir, 'package.json'));
|
|
14
|
+
if (pkg) {
|
|
15
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
16
|
+
if (deps.next)
|
|
17
|
+
return { framework: 'nextjs', label: 'Next.js', supported: true };
|
|
18
|
+
if (deps['react-native'] || deps.expo)
|
|
19
|
+
return { framework: 'react-native', label: deps.expo ? 'React Native (Expo)' : 'React Native', supported: true };
|
|
20
|
+
if (deps['@angular/core'])
|
|
21
|
+
return { framework: 'generic', label: 'Angular', supported: false };
|
|
22
|
+
if (deps.vue || deps.nuxt)
|
|
23
|
+
return { framework: 'generic', label: 'Vue', supported: false };
|
|
24
|
+
if (deps.react && (deps['react-dom'] || deps.vite || deps['react-scripts']))
|
|
25
|
+
return { framework: 'react', label: deps['react-scripts'] ? 'React (Create React App)' : 'React (Vite)', supported: true };
|
|
26
|
+
if (deps.react)
|
|
27
|
+
return { framework: 'generic', label: 'React', supported: false };
|
|
28
|
+
if (deps.express || deps.fastify || deps['@nestjs/core'])
|
|
29
|
+
return { framework: 'generic', label: 'Node backend', supported: false };
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const pyproject = readText(join(dir, 'pyproject.toml')) ?? readText(join(dir, 'requirements.txt'));
|
|
33
|
+
if (pyproject && /django|fastapi|flask/i.test(pyproject))
|
|
34
|
+
return { framework: 'generic', label: 'Python web', supported: false };
|
|
35
|
+
if (readText(join(dir, 'settings.gradle.kts')) !== null || readText(join(dir, 'settings.gradle')) !== null || readText(join(dir, 'build.gradle')) !== null || readText(join(dir, 'build.gradle.kts')) !== null) {
|
|
36
|
+
const app = ['app/build.gradle.kts', 'app/build.gradle'].map((f) => readText(join(dir, f))).find((t) => t !== null);
|
|
37
|
+
if (app && /com\.android\.application|android\.application/.test(app))
|
|
38
|
+
return { framework: 'android', label: 'Android (Kotlin)', supported: true };
|
|
39
|
+
return { framework: 'generic', label: 'Android or JVM', supported: false };
|
|
40
|
+
}
|
|
41
|
+
// An Xcode project: an iOS app in Swift.
|
|
42
|
+
let names = [];
|
|
43
|
+
try {
|
|
44
|
+
names = readdirSync(dir);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
names = [];
|
|
48
|
+
}
|
|
49
|
+
const xcodeproj = names.find((n) => n.endsWith('.xcodeproj'));
|
|
50
|
+
if (xcodeproj && readText(join(dir, xcodeproj, 'project.pbxproj')) !== null)
|
|
51
|
+
return { framework: 'ios', label: 'iOS (Swift)', supported: true };
|
|
52
|
+
if (readText(join(dir, 'Package.swift')) !== null)
|
|
53
|
+
return { framework: 'generic', label: 'Swift', supported: false };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Detects the project at `start`, and also looks up to 3 levels down for
|
|
58
|
+
* monorepos. Returns every match; the caller asks which root to use when
|
|
59
|
+
* there is more than one.
|
|
60
|
+
*/
|
|
61
|
+
export function detect(start) {
|
|
62
|
+
const found = [];
|
|
63
|
+
const here = detectAt(start);
|
|
64
|
+
if (here && here.supported)
|
|
65
|
+
return [{ root: '.', ...here }];
|
|
66
|
+
if (here)
|
|
67
|
+
found.push({ root: '.', ...here });
|
|
68
|
+
const visit = (dir, relDir, depth) => {
|
|
69
|
+
if (depth > 3)
|
|
70
|
+
return;
|
|
71
|
+
let entries;
|
|
72
|
+
try {
|
|
73
|
+
entries = readdirSync(dir).sort();
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
for (const name of entries) {
|
|
79
|
+
if (IGNORED_DIRS.has(name) || name.startsWith('.'))
|
|
80
|
+
continue;
|
|
81
|
+
const abs = join(dir, name);
|
|
82
|
+
if (!isDir(abs))
|
|
83
|
+
continue;
|
|
84
|
+
const rel = relDir === '.' ? name : `${relDir}/${name}`;
|
|
85
|
+
const d = detectAt(abs);
|
|
86
|
+
if (d) {
|
|
87
|
+
found.push({ root: toPosix(rel), ...d });
|
|
88
|
+
continue; // Do not look inside a detected project.
|
|
89
|
+
}
|
|
90
|
+
visit(abs, rel, depth + 1);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
visit(start, '.', 1);
|
|
94
|
+
return found;
|
|
95
|
+
}
|