@lakutata/cli 2.17.1 → 3.0.0-beta.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.
Files changed (101) hide show
  1. package/README.md +455 -28
  2. package/dist/esm/CLIApp.js +6 -0
  3. package/dist/esm/Config.js +53 -0
  4. package/dist/esm/controllers/CommandLineController.js +180 -0
  5. package/dist/esm/controllers/I18nController.js +67 -0
  6. package/dist/esm/controllers/MigrationController.js +66 -0
  7. package/dist/esm/controllers/NativeController.js +41 -0
  8. package/dist/esm/controllers/PackController.js +88 -0
  9. package/dist/esm/controllers/WorkspaceController.js +91 -0
  10. package/dist/esm/dtos/AddPackageDTO.js +25 -0
  11. package/dist/esm/dtos/BundleDTO.js +29 -0
  12. package/dist/esm/dtos/CleanDTO.js +17 -0
  13. package/dist/esm/dtos/CreateProjectDTO.js +61 -0
  14. package/dist/esm/dtos/ExecutableDTO.js +37 -0
  15. package/dist/esm/dtos/I18nDTO.js +41 -0
  16. package/dist/esm/dtos/InstallerDTO.js +33 -0
  17. package/dist/{options/ListTemplatesOptions.js → esm/dtos/ListTemplatesDTO.js} +5 -14
  18. package/dist/esm/dtos/MigrationDTO.js +45 -0
  19. package/dist/esm/dtos/NativeDTO.js +41 -0
  20. package/dist/esm/dtos/RunScriptDTO.js +33 -0
  21. package/dist/esm/dtos/VersionDTO.js +29 -0
  22. package/dist/esm/lib/AddPackage.js +487 -0
  23. package/dist/esm/lib/Builder.js +21 -0
  24. package/dist/esm/lib/Clean.js +146 -0
  25. package/dist/esm/lib/Environment.js +11 -0
  26. package/dist/esm/lib/I18n.js +67 -0
  27. package/dist/esm/lib/Language.js +24 -0
  28. package/dist/esm/lib/Migration.js +94 -0
  29. package/dist/esm/lib/Output.js +40 -0
  30. package/dist/esm/lib/Processes.js +54 -0
  31. package/dist/esm/lib/ProjectCreator.js +191 -0
  32. package/dist/esm/lib/Registry.js +152 -0
  33. package/dist/esm/lib/Settings.js +25 -0
  34. package/dist/esm/lib/Tar.js +145 -0
  35. package/dist/esm/lib/TemplateCatalog.js +195 -0
  36. package/dist/esm/lib/TemplateFeatures.js +141 -0
  37. package/dist/esm/lib/Versions.js +58 -0
  38. package/dist/esm/lib/native/CMake.js +357 -0
  39. package/dist/esm/lib/native/Gyp.js +398 -0
  40. package/dist/esm/lib/native/NativeProject.js +370 -0
  41. package/dist/esm/lib/native/TextEdits.js +68 -0
  42. package/dist/esm/lib/native/Toml.js +325 -0
  43. package/dist/esm/lib/workspace/Run.js +111 -0
  44. package/dist/esm/lib/workspace/Version.js +144 -0
  45. package/dist/esm/lib/workspace/Workspace.js +231 -0
  46. package/dist/esm/locales/zh-CN.json +194 -0
  47. package/dist/esm/locales/zh-TW.json +194 -0
  48. package/dist/esm/types/ProcessResult.js +1 -0
  49. package/package.json +32 -43
  50. package/CHANGELOG.md +0 -460
  51. package/dist/CLIApp.d.ts +0 -3
  52. package/dist/CLIApp.d.ts.map +0 -1
  53. package/dist/CLIApp.js +0 -45
  54. package/dist/config/Config.d.ts +0 -3
  55. package/dist/config/Config.d.ts.map +0 -1
  56. package/dist/config/Config.js +0 -63
  57. package/dist/config/SetupCLIEntrypoint.d.ts +0 -3
  58. package/dist/config/SetupCLIEntrypoint.d.ts.map +0 -1
  59. package/dist/config/SetupCLIEntrypoint.js +0 -31
  60. package/dist/controllers/CommandLineController.d.ts +0 -25
  61. package/dist/controllers/CommandLineController.d.ts.map +0 -1
  62. package/dist/controllers/CommandLineController.js +0 -78
  63. package/dist/lib/ConvertDTO2Inquirer.d.ts +0 -3
  64. package/dist/lib/ConvertDTO2Inquirer.d.ts.map +0 -1
  65. package/dist/lib/ConvertDTO2Inquirer.js +0 -61
  66. package/dist/lib/GetDataDirectory.d.ts +0 -2
  67. package/dist/lib/GetDataDirectory.d.ts.map +0 -1
  68. package/dist/lib/GetDataDirectory.js +0 -11
  69. package/dist/lib/GetLocalDataFilename.d.ts +0 -2
  70. package/dist/lib/GetLocalDataFilename.d.ts.map +0 -1
  71. package/dist/lib/GetLocalDataFilename.js +0 -12
  72. package/dist/lib/ListTemplateNames.d.ts +0 -2
  73. package/dist/lib/ListTemplateNames.d.ts.map +0 -1
  74. package/dist/lib/ListTemplateNames.js +0 -44
  75. package/dist/lib/SpecialChar.d.ts +0 -9
  76. package/dist/lib/SpecialChar.d.ts.map +0 -1
  77. package/dist/lib/SpecialChar.js +0 -11
  78. package/dist/lib/components/DeGitPuller.d.ts +0 -19
  79. package/dist/lib/components/DeGitPuller.d.ts.map +0 -1
  80. package/dist/lib/components/DeGitPuller.js +0 -65
  81. package/dist/lib/components/Spinner.d.ts +0 -23
  82. package/dist/lib/components/Spinner.d.ts.map +0 -1
  83. package/dist/lib/components/Spinner.js +0 -61
  84. package/dist/lib/providers/Creator.d.ts +0 -46
  85. package/dist/lib/providers/Creator.d.ts.map +0 -1
  86. package/dist/lib/providers/Creator.js +0 -179
  87. package/dist/lib/providers/Information.d.ts +0 -91
  88. package/dist/lib/providers/Information.d.ts.map +0 -1
  89. package/dist/lib/providers/Information.js +0 -225
  90. package/dist/lib/providers/OnlineLatestVersion.d.ts +0 -14
  91. package/dist/lib/providers/OnlineLatestVersion.d.ts.map +0 -1
  92. package/dist/lib/providers/OnlineLatestVersion.js +0 -47
  93. package/dist/lib/providers/TemplateManager.d.ts +0 -36
  94. package/dist/lib/providers/TemplateManager.d.ts.map +0 -1
  95. package/dist/lib/providers/TemplateManager.js +0 -164
  96. package/dist/options/CreateProjectOptions.d.ts +0 -15
  97. package/dist/options/CreateProjectOptions.d.ts.map +0 -1
  98. package/dist/options/CreateProjectOptions.js +0 -94
  99. package/dist/options/ListTemplatesOptions.d.ts +0 -5
  100. package/dist/options/ListTemplatesOptions.d.ts.map +0 -1
  101. package/tsconfig.json +0 -7
@@ -0,0 +1,398 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { applyEdits, indentUnit, lineEndOf, lineIndent, NativeSyntaxError } from './TextEdits.js';
4
+ /**
5
+ * The parser of the Python literals of the gyp files: comments (#), strings ('', "", ''' ''', """ """, the prefixes r,
6
+ * u, b, the adjacent strings concatenated as Python does), numbers, names (True, False, None), lists, tuples and
7
+ * dictionaries, their trailing commas
8
+ */
9
+ class GypParser {
10
+ constructor(file, text) {
11
+ this.file = file;
12
+ this.text = text;
13
+ this.offset = 0;
14
+ }
15
+ error(message, offset = this.offset) {
16
+ throw new NativeSyntaxError(this.file, this.text, offset, message);
17
+ }
18
+ /**
19
+ * Skip the whitespaces, the line continuations and the comments
20
+ */
21
+ skip() {
22
+ for (;;) {
23
+ const char = this.text[this.offset];
24
+ if (char === ' ' || char === '\t' || char === '\r' || char === '\n' || char === '\f') {
25
+ this.offset++;
26
+ }
27
+ else if (char === '\\' && (this.text[this.offset + 1] === '\n' || this.text.startsWith('\r\n', this.offset + 1))) {
28
+ this.offset += this.text[this.offset + 1] === '\n' ? 2 : 3;
29
+ }
30
+ else if (char === '#') {
31
+ while (this.offset < this.text.length && this.text[this.offset] !== '\n')
32
+ this.offset++;
33
+ }
34
+ else {
35
+ return;
36
+ }
37
+ }
38
+ }
39
+ parse() {
40
+ this.skip();
41
+ const node = this.value();
42
+ this.skip();
43
+ if (this.offset < this.text.length)
44
+ this.error('unexpected text after the value');
45
+ return node;
46
+ }
47
+ value() {
48
+ const char = this.text[this.offset];
49
+ if (char === '{')
50
+ return this.dict();
51
+ if (char === '[')
52
+ return this.list(']');
53
+ if (char === '(')
54
+ return this.list(')');
55
+ if (char === '"' || char === '\'' || (/[rRuUbB]/.test(char ?? '') && /^[rRuUbB]{1,2}['"]/.test(this.text.slice(this.offset, this.offset + 3))))
56
+ return this.strings();
57
+ const other = /^(?:[+-]?(?:0[xX][0-9a-fA-F_]+|0[oO][0-7_]+|0[bB][01_]+|\d[\d_]*(?:\.\d*)?(?:[eE][+-]?\d+)?)|True|False|None)/.exec(this.text.slice(this.offset));
58
+ if (!other)
59
+ this.error(char === undefined ? 'unexpected end of the file' : `unexpected ${JSON.stringify(char)}`);
60
+ const start = this.offset;
61
+ this.offset += other[0].length;
62
+ return { kind: 'other', text: other[0], start: start, end: this.offset };
63
+ }
64
+ /**
65
+ * A string, and the strings adjacent to it (concatenated)
66
+ */
67
+ strings() {
68
+ const start = this.offset;
69
+ let value = this.string();
70
+ for (;;) {
71
+ const before = this.offset;
72
+ this.skip();
73
+ const char = this.text[this.offset];
74
+ if (char === '"' || char === '\'' || /^[rRuUbB]{1,2}['"]/.test(this.text.slice(this.offset, this.offset + 3))) {
75
+ value += this.string();
76
+ }
77
+ else {
78
+ this.offset = before;
79
+ break;
80
+ }
81
+ }
82
+ return { kind: 'string', value: value, start: start, end: this.offset };
83
+ }
84
+ /**
85
+ * One string literal
86
+ */
87
+ string() {
88
+ const prefix = /^[rRuUbB]{0,2}/.exec(this.text.slice(this.offset))[0];
89
+ const raw = /[rR]/.test(prefix);
90
+ this.offset += prefix.length;
91
+ const quote = this.text[this.offset];
92
+ const triple = this.text.startsWith(quote.repeat(3), this.offset);
93
+ const delimiter = triple ? quote.repeat(3) : quote;
94
+ const start = this.offset;
95
+ this.offset += delimiter.length;
96
+ let value = '';
97
+ for (;;) {
98
+ if (this.offset >= this.text.length)
99
+ this.error('unterminated string', start);
100
+ if (this.text.startsWith(delimiter, this.offset)) {
101
+ this.offset += delimiter.length;
102
+ return value;
103
+ }
104
+ const char = this.text[this.offset];
105
+ if (char === '\n' && !triple)
106
+ this.error('unterminated string', start);
107
+ if (char === '\\') {
108
+ const next = this.text[this.offset + 1] ?? '';
109
+ if (raw) {
110
+ value += char + next;
111
+ }
112
+ else {
113
+ const escapes = { 'n': '\n', 't': '\t', 'r': '\r', '\\': '\\', '\'': '\'', '"': '"', '\n': '', 'a': '\x07', 'b': '\b', 'f': '\f', 'v': '\v', '0': '\0' };
114
+ value += next in escapes ? escapes[next] : char + next;
115
+ }
116
+ this.offset += 2;
117
+ continue;
118
+ }
119
+ value += char;
120
+ this.offset++;
121
+ }
122
+ }
123
+ list(close) {
124
+ const start = this.offset;
125
+ this.offset++;
126
+ const items = [];
127
+ let trailingComma = false;
128
+ for (;;) {
129
+ this.skip();
130
+ if (this.text[this.offset] === close)
131
+ break;
132
+ items.push(this.value());
133
+ trailingComma = false;
134
+ this.skip();
135
+ if (this.text[this.offset] === ',') {
136
+ this.offset++;
137
+ trailingComma = true;
138
+ }
139
+ else if (this.text[this.offset] !== close) {
140
+ this.error(`expected "," or "${close}"`);
141
+ }
142
+ }
143
+ const closeOffset = this.offset;
144
+ this.offset++;
145
+ return { kind: 'list', items: items, start: start, end: this.offset, close: closeOffset, trailingComma: trailingComma };
146
+ }
147
+ dict() {
148
+ const start = this.offset;
149
+ this.offset++;
150
+ const entries = [];
151
+ let trailingComma = false;
152
+ for (;;) {
153
+ this.skip();
154
+ if (this.text[this.offset] === '}')
155
+ break;
156
+ const key = this.value();
157
+ if (key.kind !== 'string')
158
+ this.error('the keys of gyp are strings', key.start);
159
+ this.skip();
160
+ if (this.text[this.offset] !== ':')
161
+ this.error('expected ":"');
162
+ this.offset++;
163
+ this.skip();
164
+ const value = this.value();
165
+ entries.push({ key: key, value: value });
166
+ trailingComma = false;
167
+ this.skip();
168
+ if (this.text[this.offset] === ',') {
169
+ this.offset++;
170
+ trailingComma = true;
171
+ }
172
+ else if (this.text[this.offset] !== '}') {
173
+ this.error('expected "," or "}"');
174
+ }
175
+ }
176
+ const closeOffset = this.offset;
177
+ this.offset++;
178
+ return { kind: 'dict', entries: entries, start: start, end: this.offset, close: closeOffset, trailingComma: trailingComma };
179
+ }
180
+ }
181
+ /**
182
+ * A gyp file (binding.gyp, a .gypi), parsed with the places of its values: it is changed by edits of its text, its
183
+ * comments and its layout kept
184
+ */
185
+ export class GypDocument {
186
+ constructor(file, text) {
187
+ this.file = file;
188
+ this.text = text;
189
+ this.edits = [];
190
+ const root = new GypParser(file, text).parse();
191
+ if (root.kind !== 'dict')
192
+ throw new NativeSyntaxError(file, text, root.start, 'a gyp file is a dictionary');
193
+ this.root = root;
194
+ this.indent = indentUnit(text);
195
+ const strings = text.match(/(?<![\w\\])['"]/g) ?? [];
196
+ this.quote = strings.filter((quote) => quote === '"').length > strings.filter((quote) => quote === '\'').length ? '"' : '\'';
197
+ }
198
+ static async read(file) {
199
+ return new GypDocument(file, await readFile(file, 'utf8'));
200
+ }
201
+ /**
202
+ * The edits of the changes made
203
+ */
204
+ get changes() {
205
+ return this.edits;
206
+ }
207
+ /**
208
+ * A string of the file, in its quote
209
+ * @param value
210
+ */
211
+ literal(value) {
212
+ const escaped = value.replace(/\\/g, '\\\\').replace(/\n/g, '\\n');
213
+ //The other quote when the value has the quote of the file only (OS=="mac": 'OS=="mac"')
214
+ const quote = escaped.includes(this.quote) && !escaped.includes(this.quote === '"' ? '\'' : '"') ? (this.quote === '"' ? '\'' : '"') : this.quote;
215
+ return quote === '"' ? `"${escaped.replace(/"/g, '\\"')}"` : `'${escaped.replace(/'/g, '\\\'')}'`;
216
+ }
217
+ /**
218
+ * The value of a key of a dictionary
219
+ * @param dict
220
+ * @param key
221
+ */
222
+ static get(dict, key) {
223
+ //A key repeated is the last one (as the dictionaries of Python, gyp evaluating its files)
224
+ return [...dict.entries].reverse().find((entry) => entry.key.value === key)?.value;
225
+ }
226
+ /**
227
+ * The text of a value, indented for its place (the lines after the first one)
228
+ * @param value
229
+ * @param indent the indentation of its line
230
+ */
231
+ render(value, indent) {
232
+ if (typeof value === 'string')
233
+ return this.literal(value);
234
+ const inner = indent + this.indent;
235
+ if (Array.isArray(value)) {
236
+ if (!value.length)
237
+ return '[]';
238
+ //A condition as gyp writes it: ['OS=="mac"', {, its entries indented once, }] (on lines of its own when empty)
239
+ if (value.length === 2 && typeof value[0] === 'string' && value[1] !== null && typeof value[1] === 'object' && !Array.isArray(value[1])) {
240
+ const entries = Object.entries(value[1]);
241
+ return `[${this.literal(value[0])}, {\n${entries.map(([key, item]) => `${inner}${this.literal(key)}: ${this.render(item, inner)},\n`).join('').replace(/,\n$/, '\n')}${indent}}]`;
242
+ }
243
+ //The lists of strings on one line, the other ones a value per line
244
+ if (value.every((item) => typeof item === 'string'))
245
+ return `[${value.map((item) => this.render(item, inner)).join(', ')}]`;
246
+ return `[\n${value.map((item) => `${inner}${this.render(item, inner)}`).join(',\n')}\n${indent}]`;
247
+ }
248
+ const entries = Object.entries(value);
249
+ if (!entries.length)
250
+ return '{}';
251
+ return `{\n${entries.map(([key, item]) => `${inner}${this.literal(key)}: ${this.render(item, inner)}`).join(',\n')}\n${indent}}`;
252
+ }
253
+ /**
254
+ * Insert items at the end of a list or of a dictionary (their texts): after its last item, its comma added, or
255
+ * before its closing bracket when it is empty
256
+ * @param container
257
+ * @param texts the items (the entries of a dictionary: 'key': value), by their indentation
258
+ */
259
+ append(container, texts) {
260
+ const items = container.kind === 'list' ? container.items : container.entries.map((entry) => ({ start: entry.key.start, end: entry.value.end }));
261
+ const last = items.at(-1);
262
+ const multiline = this.text.slice(container.start, container.close).includes('\n');
263
+ if (!last) {
264
+ if (!multiline) {
265
+ this.edits.push({ offset: container.close, remove: 0, insert: texts.map((text) => text('')).join(', ') });
266
+ return;
267
+ }
268
+ //Before the closing bracket, on lines of their own, after the comments of the empty container
269
+ const indent = lineIndent(this.text, container.close) + this.indent;
270
+ const lineStart = this.text.lastIndexOf('\n', container.close - 1) + 1;
271
+ const lines = texts.map((text) => `${indent}${text(indent)}`).join(',\n');
272
+ this.edits.push(/^[ \t]*$/.test(this.text.slice(lineStart, container.close))
273
+ ? { offset: lineStart, remove: 0, insert: `${lines}\n` }
274
+ : { offset: container.close, remove: 0, insert: `\n${lines}\n${lineIndent(this.text, container.close)}` });
275
+ return;
276
+ }
277
+ //After the last item and its comma
278
+ const comma = this.text.indexOf(',', last.end);
279
+ const hasComma = container.trailingComma && comma >= 0 && comma < container.close;
280
+ const after = hasComma ? comma + 1 : last.end;
281
+ if (!multiline) {
282
+ this.edits.push({ offset: after, remove: 0, insert: `${hasComma ? '' : ','} ${texts.map((text) => text('')).join(', ')}${hasComma ? ',' : ''}` });
283
+ return;
284
+ }
285
+ const indent = lineIndent(this.text, last.start);
286
+ const lines = texts.map((text) => `${indent}${text(indent)}`).join(',\n');
287
+ //After the line of the last item, its comment kept on it (its comma added after the item)
288
+ const lineEnd = lineEndOf(this.text, after);
289
+ if (lineEnd < container.close && /^[ \t]*(#.*)?$/.test(this.text.slice(after, lineEnd))) {
290
+ if (!hasComma)
291
+ this.edits.push({ offset: last.end, remove: 0, insert: ',' });
292
+ this.edits.push({ offset: lineEnd, remove: 0, insert: `\n${lines}${hasComma ? ',' : ''}` });
293
+ return;
294
+ }
295
+ this.edits.push({ offset: after, remove: 0, insert: `${hasComma ? '' : ','}\n${lines}${hasComma ? ',' : ''}` });
296
+ }
297
+ /**
298
+ * The document with its changes (parsed again)
299
+ */
300
+ applied() {
301
+ return new GypDocument(this.file, applyEdits(this.text, this.edits));
302
+ }
303
+ /**
304
+ * Add values to the list of a key of a dictionary (created when missing), the values it has skipped
305
+ * @param dict
306
+ * @param key
307
+ * @param values
308
+ * @return whether the file is changed
309
+ */
310
+ addToList(dict, key, values) {
311
+ const existing = GypDocument.get(dict, key);
312
+ if (existing && existing.kind !== 'list')
313
+ throw new NativeSyntaxError(this.file, this.text, existing.start, `"${key}" is not a list`);
314
+ const present = new Set(existing ? existing.items.filter((item) => item.kind === 'string').map((item) => item.value) : []);
315
+ const missing = values.filter((value) => !present.has(value));
316
+ if (!missing.length)
317
+ return false;
318
+ if (existing) {
319
+ this.append(existing, missing.map((value) => () => this.literal(value)));
320
+ }
321
+ else {
322
+ this.append(dict, [(indent) => `${this.literal(key)}: ${this.render(missing, indent)}`]);
323
+ }
324
+ return true;
325
+ }
326
+ /**
327
+ * Add an entry to a dictionary (when its key is missing)
328
+ * @param dict
329
+ * @param key
330
+ * @param value
331
+ * @return whether the file is changed
332
+ */
333
+ addEntry(dict, key, value) {
334
+ if (GypDocument.get(dict, key))
335
+ return false;
336
+ this.append(dict, [(indent) => `${this.literal(key)}: ${this.render(value, indent)}`]);
337
+ return true;
338
+ }
339
+ /**
340
+ * Add an item to a list
341
+ * @param list
342
+ * @param value
343
+ */
344
+ addItem(list, value) {
345
+ this.append(list, [(indent) => this.render(value, indent)]);
346
+ }
347
+ }
348
+ /**
349
+ * The normalized text of a condition of gyp (OS=="mac" and OS == 'mac' are the same condition)
350
+ * @param condition
351
+ */
352
+ export function normalizeCondition(condition) {
353
+ return condition.replace(/\s+/g, '').replace(/'/g, '"');
354
+ }
355
+ /**
356
+ * The target of a gyp file, and the file it is declared in: the targets of the file, or of the files it includes
357
+ * (the includes of its root, relative to it)
358
+ * @param file
359
+ * @param name the name of the target, the only one by default
360
+ */
361
+ export async function findGypTarget(file, name) {
362
+ const found = [];
363
+ const visited = new Set();
364
+ const visit = async (current) => {
365
+ if (visited.has(current))
366
+ return;
367
+ visited.add(current);
368
+ const document = await GypDocument.read(current);
369
+ const targets = GypDocument.get(document.root, 'targets');
370
+ if (targets?.kind === 'list') {
371
+ for (const target of targets.items)
372
+ if (target.kind === 'dict')
373
+ found.push({ document: document, target: target });
374
+ }
375
+ const includes = GypDocument.get(document.root, 'includes');
376
+ if (includes?.kind === 'list') {
377
+ for (const include of includes.items)
378
+ if (include.kind === 'string')
379
+ await visit(path.resolve(path.dirname(current), include.value));
380
+ }
381
+ };
382
+ await visit(file);
383
+ const named = (entry) => {
384
+ const value = GypDocument.get(entry.target, 'target_name');
385
+ return value?.kind === 'string' ? value.value : undefined;
386
+ };
387
+ if (!found.length)
388
+ throw new Error(`${file} declares no target (in its targets, or in the files it includes)`);
389
+ if (name !== undefined) {
390
+ const target = found.find((entry) => named(entry) === name);
391
+ if (!target)
392
+ throw new Error(`${file} has no target ${name} (${found.map(named).join(', ')})`);
393
+ return target;
394
+ }
395
+ if (found.length > 1)
396
+ throw new Error(`${file} has several targets (${found.map(named).join(', ')}): --target gives the one changed`);
397
+ return found[0];
398
+ }