@lakutata/cli 2.17.1 → 3.0.0-beta.0

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,357 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { applyEdits, indentUnit, lineEndOf, lineIndent, NativeSyntaxError } from './TextEdits.js';
3
+ /**
4
+ * The blocks of CMake, by their opening command
5
+ */
6
+ const BLOCKS = { if: 'endif', foreach: 'endforeach', while: 'endwhile', function: 'endfunction', macro: 'endmacro', block: 'endblock' };
7
+ /**
8
+ * The parser of the files of CMake, by the grammar of cmake-language(7): the command invocations, their bracket, quoted
9
+ * and unquoted arguments (the parentheses nested), the line and the bracket comments
10
+ */
11
+ class CMakeParser {
12
+ constructor(file, text) {
13
+ this.file = file;
14
+ this.text = text;
15
+ this.offset = 0;
16
+ }
17
+ error(message, offset = this.offset) {
18
+ throw new NativeSyntaxError(this.file, this.text, offset, message);
19
+ }
20
+ /**
21
+ * The length of the bracket opening at an offset ([==[: its = counted), -1 when there is none
22
+ * @param offset
23
+ */
24
+ bracketLevel(offset) {
25
+ if (this.text[offset] !== '[')
26
+ return -1;
27
+ let end = offset + 1;
28
+ while (this.text[end] === '=')
29
+ end++;
30
+ return this.text[end] === '[' ? end - offset - 1 : -1;
31
+ }
32
+ /**
33
+ * Skip a bracket ([=[…]=]) at the offset, return its content
34
+ */
35
+ bracket() {
36
+ const start = this.offset;
37
+ const level = this.bracketLevel(this.offset);
38
+ const close = `]${'='.repeat(level)}]`;
39
+ const contentStart = this.offset + level + 2;
40
+ const end = this.text.indexOf(close, contentStart);
41
+ if (end < 0)
42
+ this.error('unterminated bracket', start);
43
+ this.offset = end + close.length;
44
+ //The newline right after the opening bracket is not part of its content
45
+ return this.text.slice(contentStart, end).replace(/^\r?\n/, '');
46
+ }
47
+ /**
48
+ * Skip a comment at the offset (#…, #[[…]])
49
+ */
50
+ comment() {
51
+ this.offset++;
52
+ if (this.bracketLevel(this.offset) >= 0) {
53
+ this.bracket();
54
+ }
55
+ else {
56
+ while (this.offset < this.text.length && this.text[this.offset] !== '\n')
57
+ this.offset++;
58
+ }
59
+ }
60
+ /**
61
+ * Skip the spaces, the newlines and the comments
62
+ */
63
+ skip() {
64
+ for (;;) {
65
+ const char = this.text[this.offset];
66
+ if (char === ' ' || char === '\t' || char === '\r' || char === '\n')
67
+ this.offset++;
68
+ else if (char === '#')
69
+ this.comment();
70
+ else
71
+ return;
72
+ }
73
+ }
74
+ parse() {
75
+ const commands = [];
76
+ let depth = 0;
77
+ for (;;) {
78
+ this.skip();
79
+ if (this.offset >= this.text.length)
80
+ return commands;
81
+ const start = this.offset;
82
+ const identifier = /^[A-Za-z_][A-Za-z0-9_]*/.exec(this.text.slice(this.offset));
83
+ if (!identifier)
84
+ this.error(`unexpected ${JSON.stringify(this.text[this.offset])}`);
85
+ this.offset += identifier[0].length;
86
+ while (this.text[this.offset] === ' ' || this.text[this.offset] === '\t')
87
+ this.offset++;
88
+ if (this.text[this.offset] !== '(')
89
+ this.error('expected "(" after the name of the command');
90
+ this.offset++;
91
+ const args = this.arguments();
92
+ const name = identifier[0].toLowerCase();
93
+ //The commands closing a block are at the depth of their opening one
94
+ if (Object.values(BLOCKS).includes(name))
95
+ depth = Math.max(0, depth - 1);
96
+ commands.push({ name: name, args: args, start: start, end: this.offset, depth: ['else', 'elseif'].includes(name) ? Math.max(0, depth - 1) : depth });
97
+ if (name in BLOCKS)
98
+ depth++;
99
+ }
100
+ }
101
+ /**
102
+ * The arguments of a command, until its closing parenthesis (the parentheses nested are unquoted arguments)
103
+ */
104
+ arguments() {
105
+ const args = [];
106
+ let nested = 0;
107
+ for (;;) {
108
+ this.skip();
109
+ if (this.offset >= this.text.length)
110
+ this.error('unterminated command');
111
+ const char = this.text[this.offset];
112
+ const start = this.offset;
113
+ if (char === ')') {
114
+ this.offset++;
115
+ if (nested === 0)
116
+ return args;
117
+ nested--;
118
+ args.push({ kind: 'unquoted', value: ')', start: start, end: this.offset });
119
+ }
120
+ else if (char === '(') {
121
+ this.offset++;
122
+ nested++;
123
+ args.push({ kind: 'unquoted', value: '(', start: start, end: this.offset });
124
+ }
125
+ else if (char === '[' && this.bracketLevel(this.offset) >= 0) {
126
+ args.push({ kind: 'bracket', value: this.bracket(), start: start, end: this.offset });
127
+ }
128
+ else if (char === '"') {
129
+ args.push({ kind: 'quoted', value: this.quoted(), start: start, end: this.offset });
130
+ }
131
+ else {
132
+ args.push({ kind: 'unquoted', value: this.unquoted(), start: start, end: this.offset });
133
+ }
134
+ }
135
+ }
136
+ quoted() {
137
+ const start = this.offset;
138
+ this.offset++;
139
+ let value = '';
140
+ for (;;) {
141
+ if (this.offset >= this.text.length)
142
+ this.error('unterminated quoted argument', start);
143
+ const char = this.text[this.offset];
144
+ if (char === '"') {
145
+ this.offset++;
146
+ return value;
147
+ }
148
+ if (char === '\\') {
149
+ const next = this.text[this.offset + 1] ?? '';
150
+ //A line continuation, an encoded escape, or the character escaped
151
+ value += next === '\n' ? '' : next === 't' ? '\t' : next === 'r' ? '\r' : next === 'n' ? '\n' : next === ';' ? '\;' : next;
152
+ this.offset += 2;
153
+ continue;
154
+ }
155
+ value += char;
156
+ this.offset++;
157
+ }
158
+ }
159
+ /**
160
+ * An unquoted argument: its characters but the whitespaces and ()#"\ (escaped by \), with the quoted parts of the
161
+ * legacy arguments (-Da="b c")
162
+ */
163
+ unquoted() {
164
+ let value = '';
165
+ for (;;) {
166
+ const char = this.text[this.offset];
167
+ if (char === undefined || /[\s()#]/.test(char))
168
+ return value;
169
+ if (char === '\\') {
170
+ value += this.text.slice(this.offset, this.offset + 2);
171
+ this.offset += 2;
172
+ }
173
+ else if (char === '"') {
174
+ //The quotes inside of a legacy unquoted argument
175
+ const end = this.text.indexOf('"', this.offset + 1);
176
+ if (end < 0)
177
+ this.error('unterminated quote');
178
+ value += this.text.slice(this.offset, end + 1);
179
+ this.offset = end + 1;
180
+ }
181
+ else {
182
+ value += char;
183
+ this.offset++;
184
+ }
185
+ }
186
+ }
187
+ }
188
+ /**
189
+ * A file of CMake (CMakeLists.txt), parsed with the places of its commands: it is changed by edits of its text, its
190
+ * comments and its layout kept
191
+ */
192
+ export class CMakeDocument {
193
+ constructor(file, text) {
194
+ this.file = file;
195
+ this.text = text;
196
+ this.edits = [];
197
+ this.commands = new CMakeParser(file, text).parse();
198
+ this.indent = indentUnit(text);
199
+ }
200
+ static async read(file) {
201
+ return new CMakeDocument(file, await readFile(file, 'utf8'));
202
+ }
203
+ get changes() {
204
+ return this.edits;
205
+ }
206
+ /**
207
+ * The document with its changes (parsed again)
208
+ */
209
+ applied() {
210
+ return new CMakeDocument(this.file, applyEdits(this.text, this.edits));
211
+ }
212
+ /**
213
+ * The value of an argument, its variables resolved (${PROJECT_NAME}, the variables set at the top level)
214
+ * @param value
215
+ */
216
+ resolve(value) {
217
+ const variables = new Map();
218
+ for (const command of this.commands) {
219
+ if (command.depth !== 0)
220
+ continue;
221
+ if (command.name === 'project' && command.args[0])
222
+ variables.set('PROJECT_NAME', command.args[0].value);
223
+ if (command.name === 'set' && command.args.length === 2)
224
+ variables.set(command.args[0].value, command.args[1].value);
225
+ }
226
+ return value.replace(/\$\{([A-Za-z0-9_]+)\}/g, (all, name) => variables.get(name) ?? all);
227
+ }
228
+ /**
229
+ * The targets of the libraries and the executables: their name, and the text naming them in their command
230
+ */
231
+ targets() {
232
+ return this.commands
233
+ .filter((command) => (command.name === 'add_library' || command.name === 'add_executable') && command.args.length > 0)
234
+ .map((command) => ({
235
+ name: this.resolve(command.args[0].value),
236
+ text: this.text.slice(command.args[0].start, command.args[0].end),
237
+ command: command
238
+ }));
239
+ }
240
+ /**
241
+ * The target changed: the one named, the only one by default
242
+ * @param name
243
+ */
244
+ target(name) {
245
+ const targets = this.targets();
246
+ if (!targets.length)
247
+ throw new Error(`${this.file} declares no target (add_library)`);
248
+ if (name !== undefined) {
249
+ const target = targets.find((candidate) => candidate.name === name);
250
+ if (!target)
251
+ throw new Error(`${this.file} has no target ${name} (${targets.map((candidate) => candidate.name).join(', ')})`);
252
+ return target;
253
+ }
254
+ if (targets.length > 1)
255
+ throw new Error(`${this.file} has several targets (${targets.map((candidate) => candidate.name).join(', ')}): --target gives the one changed`);
256
+ return targets[0];
257
+ }
258
+ /**
259
+ * Whether a command with these arguments exists (the arguments compared by their resolved values)
260
+ * @param name
261
+ * @param args
262
+ */
263
+ has(name, args) {
264
+ const expected = args.map((arg) => this.resolve(arg.replace(/^"(.*)"$/, '$1')));
265
+ return this.commands.some((command) => command.name === name.toLowerCase() &&
266
+ command.args.length === expected.length &&
267
+ command.args.every((arg, index) => this.resolve(arg.value) === expected[index]));
268
+ }
269
+ /**
270
+ * Insert commands after the last command of the top level about a target (its add_library, its target_* and
271
+ * set_target_properties commands, the add_custom_command of the target), on lines of their own
272
+ * @param target
273
+ * @param lines the commands, a line each
274
+ * @param condition the condition of the if block they are given in
275
+ */
276
+ insertAfterTarget(target, lines, condition) {
277
+ const about = this.commands.filter((command) => command.depth === 0 && (command === target.command ||
278
+ ((command.name.startsWith('target_') || command.name === 'set_target_properties') && command.args[0] !== undefined && this.resolve(command.args[0].value) === target.name) ||
279
+ (command.name === 'add_custom_command' && command.args[0]?.value.toUpperCase() === 'TARGET' && command.args[1] !== undefined && this.resolve(command.args[1].value) === target.name)));
280
+ //Inside of the block of the condition about the target already written
281
+ if (condition !== undefined) {
282
+ const block = this.conditionBlock(target, condition);
283
+ if (block) {
284
+ const last = this.commands[this.commands.indexOf(block) - 1];
285
+ const indent = this.commands[this.commands.indexOf(block) - 1].depth === 1 ? lineIndent(this.text, last.start) : lineIndent(this.text, block.start) + this.indent;
286
+ this.edits.push({ offset: lineEndOf(this.text, last.end), remove: 0, insert: `\n${lines.map((line) => `${indent}${line}`).join('\n')}` });
287
+ return;
288
+ }
289
+ }
290
+ //The block of a condition: the if blocks of the target are followed by the next one
291
+ const anchor = this.blockEnd(about.at(-1));
292
+ const indent = lineIndent(this.text, anchor.start);
293
+ const block = condition === undefined
294
+ ? lines.map((line) => `${indent}${line}`).join('\n')
295
+ : [`${indent}if(${condition})`, ...lines.map((line) => `${indent}${this.indent}${line}`), `${indent}endif()`].join('\n');
296
+ this.edits.push({ offset: lineEndOf(this.text, anchor.end), remove: 0, insert: `\n${block}` });
297
+ }
298
+ /**
299
+ * The endif of a block of the top level with a condition, after the target, holding only commands about it (its if
300
+ * has no else)
301
+ * @param target
302
+ * @param condition
303
+ */
304
+ conditionBlock(target, condition) {
305
+ const normalize = (text) => text.replace(/\s+/g, ' ').trim();
306
+ for (let index = this.commands.indexOf(target.command) + 1; index < this.commands.length; index++) {
307
+ const command = this.commands[index];
308
+ if (command.name !== 'if' || command.depth !== 0)
309
+ continue;
310
+ const open = this.text.indexOf('(', command.start);
311
+ if (normalize(this.text.slice(open + 1, command.end - 1)) !== normalize(condition))
312
+ continue;
313
+ let end = index + 1;
314
+ let about = true;
315
+ while (end < this.commands.length && !(this.commands[end].depth === 0 && this.commands[end].name === 'endif')) {
316
+ const inner = this.commands[end];
317
+ if (inner.depth === 0)
318
+ about = false;
319
+ else if (inner.depth === 1 && !(['find_package', 'pkg_check_modules'].includes(inner.name) || (['target_link_libraries', 'target_include_directories', 'target_compile_definitions', 'set_target_properties', 'add_custom_command'].includes(inner.name) &&
320
+ [inner.args[0]?.value, inner.args[1]?.value].some((arg) => arg !== undefined && this.resolve(arg) === target.name))))
321
+ about = false;
322
+ end++;
323
+ }
324
+ if (about && end > index + 1 && end < this.commands.length)
325
+ return this.commands[end];
326
+ }
327
+ return undefined;
328
+ }
329
+ /**
330
+ * The last command of the if blocks following a command (the conditions about a target written after it)
331
+ * @param command
332
+ */
333
+ blockEnd(command) {
334
+ let index = this.commands.indexOf(command);
335
+ for (;;) {
336
+ const next = this.commands[index + 1];
337
+ if (next?.name !== 'if' || next.depth !== 0)
338
+ return this.commands[index];
339
+ //The block, until its endif
340
+ let end = index + 2;
341
+ while (end < this.commands.length && !(this.commands[end].name === 'endif' && this.commands[end].depth === 0))
342
+ end++;
343
+ //Its commands are about the target only when the first one is
344
+ const first = this.commands[index + 2];
345
+ if (!first || first.depth !== 1 || !(first.name.startsWith('target_') || ['set_target_properties', 'add_custom_command', 'find_package', 'pkg_check_modules'].includes(first.name)))
346
+ return this.commands[index];
347
+ index = end;
348
+ }
349
+ }
350
+ }
351
+ /**
352
+ * An argument of CMake for a value: quoted when it has spaces, semicolons, parentheses or quotes
353
+ * @param value
354
+ */
355
+ export function cMakeArgumentText(value) {
356
+ return /[\s;()#"\\]/.test(value) && !/^\$<.*>$/.test(value) ? `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"` : value;
357
+ }