@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.
- package/README.md +455 -28
- package/dist/esm/CLIApp.js +6 -0
- package/dist/esm/Config.js +53 -0
- package/dist/esm/controllers/CommandLineController.js +180 -0
- package/dist/esm/controllers/I18nController.js +67 -0
- package/dist/esm/controllers/MigrationController.js +66 -0
- package/dist/esm/controllers/NativeController.js +41 -0
- package/dist/esm/controllers/PackController.js +88 -0
- package/dist/esm/controllers/WorkspaceController.js +91 -0
- package/dist/esm/dtos/AddPackageDTO.js +25 -0
- package/dist/esm/dtos/BundleDTO.js +29 -0
- package/dist/esm/dtos/CleanDTO.js +17 -0
- package/dist/esm/dtos/CreateProjectDTO.js +61 -0
- package/dist/esm/dtos/ExecutableDTO.js +37 -0
- package/dist/esm/dtos/I18nDTO.js +41 -0
- package/dist/esm/dtos/InstallerDTO.js +33 -0
- package/dist/{options/ListTemplatesOptions.js → esm/dtos/ListTemplatesDTO.js} +5 -14
- package/dist/esm/dtos/MigrationDTO.js +45 -0
- package/dist/esm/dtos/NativeDTO.js +41 -0
- package/dist/esm/dtos/RunScriptDTO.js +33 -0
- package/dist/esm/dtos/VersionDTO.js +29 -0
- package/dist/esm/lib/AddPackage.js +487 -0
- package/dist/esm/lib/Builder.js +21 -0
- package/dist/esm/lib/Clean.js +146 -0
- package/dist/esm/lib/Environment.js +11 -0
- package/dist/esm/lib/I18n.js +67 -0
- package/dist/esm/lib/Language.js +24 -0
- package/dist/esm/lib/Migration.js +94 -0
- package/dist/esm/lib/Output.js +40 -0
- package/dist/esm/lib/Processes.js +54 -0
- package/dist/esm/lib/ProjectCreator.js +191 -0
- package/dist/esm/lib/Registry.js +152 -0
- package/dist/esm/lib/Settings.js +25 -0
- package/dist/esm/lib/Tar.js +145 -0
- package/dist/esm/lib/TemplateCatalog.js +195 -0
- package/dist/esm/lib/TemplateFeatures.js +141 -0
- package/dist/esm/lib/Versions.js +58 -0
- package/dist/esm/lib/native/CMake.js +357 -0
- package/dist/esm/lib/native/Gyp.js +398 -0
- package/dist/esm/lib/native/NativeProject.js +370 -0
- package/dist/esm/lib/native/TextEdits.js +68 -0
- package/dist/esm/lib/native/Toml.js +325 -0
- package/dist/esm/lib/workspace/Run.js +111 -0
- package/dist/esm/lib/workspace/Version.js +144 -0
- package/dist/esm/lib/workspace/Workspace.js +231 -0
- package/dist/esm/locales/zh-CN.json +194 -0
- package/dist/esm/locales/zh-TW.json +194 -0
- package/dist/esm/types/ProcessResult.js +1 -0
- package/package.json +32 -43
- package/CHANGELOG.md +0 -460
- package/dist/CLIApp.d.ts +0 -3
- package/dist/CLIApp.d.ts.map +0 -1
- package/dist/CLIApp.js +0 -45
- package/dist/config/Config.d.ts +0 -3
- package/dist/config/Config.d.ts.map +0 -1
- package/dist/config/Config.js +0 -63
- package/dist/config/SetupCLIEntrypoint.d.ts +0 -3
- package/dist/config/SetupCLIEntrypoint.d.ts.map +0 -1
- package/dist/config/SetupCLIEntrypoint.js +0 -31
- package/dist/controllers/CommandLineController.d.ts +0 -25
- package/dist/controllers/CommandLineController.d.ts.map +0 -1
- package/dist/controllers/CommandLineController.js +0 -78
- package/dist/lib/ConvertDTO2Inquirer.d.ts +0 -3
- package/dist/lib/ConvertDTO2Inquirer.d.ts.map +0 -1
- package/dist/lib/ConvertDTO2Inquirer.js +0 -61
- package/dist/lib/GetDataDirectory.d.ts +0 -2
- package/dist/lib/GetDataDirectory.d.ts.map +0 -1
- package/dist/lib/GetDataDirectory.js +0 -11
- package/dist/lib/GetLocalDataFilename.d.ts +0 -2
- package/dist/lib/GetLocalDataFilename.d.ts.map +0 -1
- package/dist/lib/GetLocalDataFilename.js +0 -12
- package/dist/lib/ListTemplateNames.d.ts +0 -2
- package/dist/lib/ListTemplateNames.d.ts.map +0 -1
- package/dist/lib/ListTemplateNames.js +0 -44
- package/dist/lib/SpecialChar.d.ts +0 -9
- package/dist/lib/SpecialChar.d.ts.map +0 -1
- package/dist/lib/SpecialChar.js +0 -11
- package/dist/lib/components/DeGitPuller.d.ts +0 -19
- package/dist/lib/components/DeGitPuller.d.ts.map +0 -1
- package/dist/lib/components/DeGitPuller.js +0 -65
- package/dist/lib/components/Spinner.d.ts +0 -23
- package/dist/lib/components/Spinner.d.ts.map +0 -1
- package/dist/lib/components/Spinner.js +0 -61
- package/dist/lib/providers/Creator.d.ts +0 -46
- package/dist/lib/providers/Creator.d.ts.map +0 -1
- package/dist/lib/providers/Creator.js +0 -179
- package/dist/lib/providers/Information.d.ts +0 -91
- package/dist/lib/providers/Information.d.ts.map +0 -1
- package/dist/lib/providers/Information.js +0 -225
- package/dist/lib/providers/OnlineLatestVersion.d.ts +0 -14
- package/dist/lib/providers/OnlineLatestVersion.d.ts.map +0 -1
- package/dist/lib/providers/OnlineLatestVersion.js +0 -47
- package/dist/lib/providers/TemplateManager.d.ts +0 -36
- package/dist/lib/providers/TemplateManager.d.ts.map +0 -1
- package/dist/lib/providers/TemplateManager.js +0 -164
- package/dist/options/CreateProjectOptions.d.ts +0 -15
- package/dist/options/CreateProjectOptions.d.ts.map +0 -1
- package/dist/options/CreateProjectOptions.js +0 -94
- package/dist/options/ListTemplatesOptions.d.ts +0 -5
- package/dist/options/ListTemplatesOptions.d.ts.map +0 -1
- 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
|
+
}
|