engineering-memory 1.11.14 → 1.11.16

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.
@@ -0,0 +1,184 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { constants } from 'node:fs';
3
+ import { access, open, realpath, stat } from 'node:fs/promises';
4
+ import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
5
+ export var WorktreeEditorStatus;
6
+ (function (WorktreeEditorStatus) {
7
+ WorktreeEditorStatus["Requested"] = "requested";
8
+ WorktreeEditorStatus["Unavailable"] = "unavailable";
9
+ WorktreeEditorStatus["Failed"] = "failed";
10
+ WorktreeEditorStatus["Skipped"] = "skipped";
11
+ })(WorktreeEditorStatus || (WorktreeEditorStatus = {}));
12
+ export async function openWorktreeInEditor(repoRoot) {
13
+ if (!['win32', 'darwin', 'linux'].includes(process.platform) ||
14
+ (process.env.CI && !/^(false|0)$/i.test(process.env.CI)) ||
15
+ process.env.SSH_CONNECTION ||
16
+ process.env.SSH_CLIENT ||
17
+ process.env.SSH_TTY ||
18
+ process.env.VSCODE_AGENT_FOLDER ||
19
+ process.env.VSCODE_REMOTE_NAME ||
20
+ process.env.WSL_DISTRO_NAME ||
21
+ process.env.CODESPACES === 'true' ||
22
+ process.env.SESSIONNAME?.toLowerCase() === 'services' ||
23
+ (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY)) {
24
+ return {
25
+ status: WorktreeEditorStatus.Skipped,
26
+ detail: 'A local desktop session is not available.',
27
+ };
28
+ }
29
+ let folder;
30
+ try {
31
+ if (!repoRoot.trim())
32
+ throw new Error();
33
+ folder = await realpath(resolve(repoRoot));
34
+ if (!(await stat(folder)).isDirectory())
35
+ throw new Error();
36
+ }
37
+ catch {
38
+ return {
39
+ status: WorktreeEditorStatus.Failed,
40
+ detail: 'The worktree folder is not accessible.',
41
+ };
42
+ }
43
+ const command = await findEditor().catch(() => null);
44
+ if (!command) {
45
+ return {
46
+ status: WorktreeEditorStatus.Unavailable,
47
+ detail: 'An installed local VS Code CLI was not found.',
48
+ };
49
+ }
50
+ if (/[\\/](?:\.vscode-server(?:-insiders)?|remote-cli)[\\/]/i.test(command.executable)) {
51
+ return {
52
+ status: WorktreeEditorStatus.Skipped,
53
+ detail: 'The available VS Code CLI belongs to a remote session.',
54
+ };
55
+ }
56
+ const env = { ...process.env };
57
+ delete env.VSCODE_DEV;
58
+ if (process.platform === 'win32')
59
+ env.ELECTRON_RUN_AS_NODE = '1';
60
+ try {
61
+ return await new Promise((resolveResult) => {
62
+ const child = spawn(command.executable, [...command.args, '--new-window', folder], {
63
+ cwd: folder,
64
+ env,
65
+ shell: false,
66
+ windowsHide: true,
67
+ stdio: ['ignore', 'pipe', 'pipe'],
68
+ });
69
+ let settled = false;
70
+ let outputBytes = 0;
71
+ const timer = setTimeout(() => stop('The VS Code request timed out; window state is unknown.'), 10_000);
72
+ const finish = (status, detail) => {
73
+ if (settled)
74
+ return;
75
+ settled = true;
76
+ clearTimeout(timer);
77
+ child.stdout.destroy();
78
+ child.stderr.destroy();
79
+ child.unref();
80
+ resolveResult({ status, detail });
81
+ };
82
+ const stop = (detail) => {
83
+ if (settled)
84
+ return;
85
+ try {
86
+ child.kill('SIGKILL');
87
+ }
88
+ finally {
89
+ finish(WorktreeEditorStatus.Failed, detail);
90
+ }
91
+ };
92
+ const discardOutput = (chunk) => {
93
+ outputBytes += chunk.length;
94
+ if (outputBytes > 65_536) {
95
+ stop('The VS Code CLI exceeded the output limit; window state is unknown.');
96
+ }
97
+ };
98
+ child.stdout.on('data', discardOutput);
99
+ child.stderr.on('data', discardOutput);
100
+ child.once('error', () => finish(WorktreeEditorStatus.Failed, 'The VS Code CLI could not start.'));
101
+ child.once('close', (code) => finish(code === 0 ? WorktreeEditorStatus.Requested : WorktreeEditorStatus.Failed, code === 0
102
+ ? 'VS Code launch requested; window state was not verified.'
103
+ : 'The VS Code CLI failed; window state is unknown.'));
104
+ });
105
+ }
106
+ catch {
107
+ return { status: WorktreeEditorStatus.Failed, detail: 'The VS Code CLI could not start.' };
108
+ }
109
+ }
110
+ async function findEditor() {
111
+ const directories = (process.env.PATH ?? '')
112
+ .split(delimiter)
113
+ .map((directory) => directory.replace(/^"(.+)"$/, '$1'))
114
+ .filter(isAbsolute)
115
+ .slice(0, 128);
116
+ if (process.platform === 'win32') {
117
+ for (const root of [
118
+ process.env.LOCALAPPDATA && join(process.env.LOCALAPPDATA, 'Programs'),
119
+ process.env.ProgramFiles,
120
+ process.env['ProgramFiles(x86)'],
121
+ ]) {
122
+ if (root && isAbsolute(root))
123
+ directories.push(join(root, 'Microsoft VS Code', 'bin'));
124
+ }
125
+ }
126
+ for (const directory of new Set(directories)) {
127
+ if (process.platform === 'win32') {
128
+ const command = await windowsEditor(directory);
129
+ if (command)
130
+ return command;
131
+ }
132
+ else {
133
+ const executable = join(directory, 'code');
134
+ if (await executableFile(executable)) {
135
+ return { executable: await realpath(executable), args: [] };
136
+ }
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ async function windowsEditor(bin) {
142
+ let handle;
143
+ try {
144
+ const launcher = await realpath(join(bin, 'code.cmd'));
145
+ handle = await open(launcher, 'r');
146
+ const info = await handle.stat();
147
+ if (!info.isFile() || info.size > 16_384)
148
+ return null;
149
+ const bytes = Buffer.alloc(16_385);
150
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
151
+ if (bytesRead > 16_384)
152
+ return null;
153
+ const match = bytes
154
+ .subarray(0, bytesRead)
155
+ .toString('utf8')
156
+ .match(/^[ \t]*"%~dp0\.\.\\Code\.exe"[ \t]+"%~dp0\.\.\\((?:[a-f0-9]{7,40}\\)?resources\\app\\out\\cli\.js)"[ \t]+%\*[ \t]*\r?$/im);
157
+ if (!match)
158
+ return null;
159
+ const installation = resolve(dirname(launcher), '..');
160
+ const executable = join(installation, 'Code.exe');
161
+ const cli = join(installation, match[1]);
162
+ if (!(await executableFile(executable)) || !(await stat(cli)).isFile())
163
+ return null;
164
+ return { executable, args: [cli] };
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ finally {
170
+ await handle?.close();
171
+ }
172
+ }
173
+ async function executableFile(path) {
174
+ try {
175
+ if (!(await stat(path)).isFile())
176
+ return false;
177
+ await access(path, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
178
+ return true;
179
+ }
180
+ catch {
181
+ return false;
182
+ }
183
+ }
184
+ //# sourceMappingURL=worktree-editor.js.map
@@ -0,0 +1,318 @@
1
+ function unsupported() {
2
+ throw new Error('Unsupported or ambiguous Gradle signing configuration');
3
+ }
4
+ function tokenize(text) {
5
+ if (text.length > 256 * 1024)
6
+ unsupported();
7
+ const tokens = [];
8
+ let cursor = 0;
9
+ while (cursor < text.length) {
10
+ if (/\s/.test(text[cursor])) {
11
+ cursor++;
12
+ continue;
13
+ }
14
+ if (text.startsWith('//', cursor)) {
15
+ while (cursor < text.length && !/[\r\n]/.test(text[cursor]))
16
+ cursor++;
17
+ continue;
18
+ }
19
+ if (text.startsWith('/*', cursor)) {
20
+ let depth = 1;
21
+ cursor += 2;
22
+ while (cursor < text.length && depth) {
23
+ if (text.startsWith('/*', cursor)) {
24
+ depth++;
25
+ cursor += 2;
26
+ }
27
+ else if (text.startsWith('*/', cursor)) {
28
+ depth--;
29
+ cursor += 2;
30
+ }
31
+ else
32
+ cursor++;
33
+ }
34
+ if (depth)
35
+ unsupported();
36
+ continue;
37
+ }
38
+ const start = cursor;
39
+ const quote = text[cursor];
40
+ if (quote === '"' || quote === "'") {
41
+ if (text.startsWith(quote.repeat(3), cursor)) {
42
+ const end = text.indexOf(quote.repeat(3), cursor + 3);
43
+ if (end < 0)
44
+ unsupported();
45
+ cursor = end + 3;
46
+ tokens.push({ kind: 'string', value: '', literal: false, start, end: cursor });
47
+ continue;
48
+ }
49
+ cursor++;
50
+ let value = '';
51
+ let literal = true;
52
+ while (cursor < text.length && text[cursor] !== quote) {
53
+ let character = text[cursor++];
54
+ if (/[\r\n]/.test(character))
55
+ unsupported();
56
+ if (character === '\\') {
57
+ character = text[cursor++] ?? '';
58
+ if (character === 'u') {
59
+ const digits = text.slice(cursor, cursor + 4);
60
+ if (!/^[a-fA-F0-9]{4}$/.test(digits))
61
+ unsupported();
62
+ value += String.fromCharCode(parseInt(digits, 16));
63
+ cursor += 4;
64
+ }
65
+ else if ('\\\'"'.includes(character) && character)
66
+ value += character;
67
+ else {
68
+ literal = false;
69
+ value += character;
70
+ }
71
+ }
72
+ else {
73
+ if (character === '$')
74
+ literal = false;
75
+ value += character;
76
+ }
77
+ }
78
+ if (text[cursor++] !== quote)
79
+ unsupported();
80
+ tokens.push({ kind: 'string', value, literal, start, end: cursor });
81
+ continue;
82
+ }
83
+ const word = /^[A-Za-z_$][A-Za-z0-9_$]*/.exec(text.slice(cursor))?.[0];
84
+ cursor += word?.length ?? 1;
85
+ tokens.push({ kind: word ? 'word' : 'symbol', value: word ?? quote, start, end: cursor });
86
+ }
87
+ return tokens;
88
+ }
89
+ export function parseGradleSigning(text) {
90
+ const tokens = tokenize(text);
91
+ const signing = tokens.flatMap((token, index) => token.kind === 'word' && ['storeFile', 'setStoreFile'].includes(token.value) ? [index] : []);
92
+ if (!signing.length)
93
+ return null;
94
+ const value = (index) => tokens[index]?.value;
95
+ const expect = (index, expected) => {
96
+ if (value(index) !== expected || tokens[index]?.kind === 'string')
97
+ unsupported();
98
+ return index + 1;
99
+ };
100
+ const word = (index) => {
101
+ if (tokens[index]?.kind !== 'word')
102
+ unsupported();
103
+ return tokens[index].value;
104
+ };
105
+ const literal = (index) => {
106
+ const token = tokens[index];
107
+ if (token?.kind !== 'string' || !token.literal || !token.value || token.value.includes('$'))
108
+ unsupported();
109
+ return token.value;
110
+ };
111
+ const scopes = [];
112
+ const braces = [];
113
+ for (const [index, token] of tokens.entries()) {
114
+ scopes.push(braces.at(-1) ?? -1);
115
+ if (token.kind !== 'symbol')
116
+ continue;
117
+ if (token.value === '{')
118
+ braces.push(index);
119
+ if (token.value === '}' && braces.pop() === undefined)
120
+ unsupported();
121
+ }
122
+ if (braces.length)
123
+ unsupported();
124
+ const startStatement = (index) => {
125
+ if (!index ||
126
+ (tokens[index - 1]?.kind === 'symbol' && [';', '{', '}'].includes(value(index - 1))))
127
+ return;
128
+ const previous = value(index - 1);
129
+ if (!/[\r\n]/.test(text.slice(tokens[index - 1].end, tokens[index].start)) ||
130
+ ['=', '(', '[', ',', '.', '?', ':', '+', '-', '*', '/', 'else', 'do'].includes(previous))
131
+ unsupported();
132
+ if (previous === ')') {
133
+ let cursor = index - 1;
134
+ let depth = 0;
135
+ do {
136
+ if (tokens[cursor]?.kind !== 'string') {
137
+ if (value(cursor) === ')')
138
+ depth++;
139
+ if (value(cursor) === '(')
140
+ depth--;
141
+ }
142
+ cursor--;
143
+ } while (cursor >= 0 && depth);
144
+ if (['if', 'for', 'while', 'when', 'catch'].includes(value(cursor)))
145
+ unsupported();
146
+ }
147
+ };
148
+ const endStatement = (index) => {
149
+ if (index === tokens.length ||
150
+ (tokens[index]?.kind === 'symbol' && [';', '}'].includes(value(index))))
151
+ return;
152
+ const gap = text.slice(tokens[index - 1].end, tokens[index].start);
153
+ if (!/[\r\n]/.test(gap) ||
154
+ [
155
+ '.',
156
+ '?',
157
+ '+',
158
+ '-',
159
+ '*',
160
+ '/',
161
+ '%',
162
+ '[',
163
+ '(',
164
+ '=',
165
+ '&',
166
+ '|',
167
+ '^',
168
+ '<',
169
+ '>',
170
+ '!',
171
+ '{',
172
+ ':',
173
+ ',',
174
+ 'as',
175
+ 'in',
176
+ 'instanceof',
177
+ ].includes(value(index)))
178
+ unsupported();
179
+ };
180
+ const fileCall = (index) => {
181
+ word(index);
182
+ const root = value(index) === 'rootProject';
183
+ if (root || value(index) === 'project')
184
+ index = expect(index + 1, '.');
185
+ index = expect(index, 'file');
186
+ return { index: expect(index, '('), root };
187
+ };
188
+ const access = (index) => {
189
+ if (value(index) === '[') {
190
+ const key = literal(index + 1);
191
+ return { key, next: expect(index + 2, ']') };
192
+ }
193
+ index = expect(index, '.');
194
+ index = expect(index, 'getProperty');
195
+ index = expect(index, '(');
196
+ const key = literal(index++);
197
+ return { key, next: expect(index, ')') };
198
+ };
199
+ const declaration = (name, type) => {
200
+ const matches = tokens.flatMap((token, index) => {
201
+ if (token.kind !== 'word' ||
202
+ token.value !== name ||
203
+ tokens[index - 1]?.kind !== 'word' ||
204
+ !['def', 'val', 'var', type].includes(value(index - 1)))
205
+ return [];
206
+ if (scopes[index] !== -1)
207
+ unsupported();
208
+ startStatement(index - 1);
209
+ let next = index + 1;
210
+ if (value(next) === ':')
211
+ next = expect(next + 1, type);
212
+ return [{ index, next: expect(next, '=') }];
213
+ });
214
+ if (matches.length !== 1)
215
+ unsupported();
216
+ return matches[0];
217
+ };
218
+ let propertiesName;
219
+ let keyRoot = false;
220
+ const signingReads = new Set();
221
+ for (const start of signing) {
222
+ startStatement(start);
223
+ if (value(start) !== 'storeFile' || ['.', 'def', 'val', 'var'].includes(value(start - 1)))
224
+ unsupported();
225
+ let index = start + 1;
226
+ if (value(index) === '=')
227
+ index++;
228
+ const call = fileCall(index);
229
+ index = call.index;
230
+ const name = word(index);
231
+ signingReads.add(index++);
232
+ if (propertiesName && (propertiesName !== name || keyRoot !== call.root))
233
+ unsupported();
234
+ propertiesName = name;
235
+ keyRoot = call.root;
236
+ const read = access(index);
237
+ if (read.key !== 'storeFile')
238
+ unsupported();
239
+ index = read.next;
240
+ if (value(index) === 'as')
241
+ index = expect(index + 1, 'String');
242
+ endStatement(expect(index, ')'));
243
+ }
244
+ const properties = declaration(propertiesName, 'Properties');
245
+ let index = properties.next;
246
+ if (value(index) === 'new')
247
+ index++;
248
+ index = expect(index, 'Properties');
249
+ index = expect(index, '(');
250
+ endStatement(expect(index, ')'));
251
+ const loads = [];
252
+ for (const [position, token] of tokens.entries()) {
253
+ if (token.kind !== 'word' ||
254
+ token.value !== propertiesName ||
255
+ position === properties.index ||
256
+ signingReads.has(position))
257
+ continue;
258
+ if (value(position - 1) === '.')
259
+ unsupported();
260
+ if (value(position + 1) === '.' && value(position + 2) === 'load') {
261
+ let next = expect(position + 3, '(');
262
+ if (value(next) === 'new')
263
+ next++;
264
+ next = expect(next, 'FileInputStream');
265
+ next = expect(next, '(');
266
+ const fileIndex = next;
267
+ const fileName = word(next++);
268
+ next = expect(next, ')');
269
+ endStatement(expect(next, ')'));
270
+ loads.push({ index: position, fileIndex, fileName });
271
+ }
272
+ else {
273
+ const read = access(position + 1);
274
+ if (tokens[read.next]?.kind === 'symbol' &&
275
+ ![')', ']', '}', ',', ';'].includes(value(read.next)))
276
+ unsupported();
277
+ }
278
+ }
279
+ if (loads.length !== 1)
280
+ unsupported();
281
+ const load = loads[0];
282
+ startStatement(load.index);
283
+ const scope = scopes[load.index];
284
+ if (scope !== -1) {
285
+ if (scopes[scope] !== -1)
286
+ unsupported();
287
+ let guard = scope - 8;
288
+ if (guard < 0)
289
+ unsupported();
290
+ startStatement(guard);
291
+ for (const expected of ['if', '(', load.fileName, '.', 'exists', '(', ')', ')']) {
292
+ guard = expect(guard, expected);
293
+ }
294
+ }
295
+ if (properties.index >= load.index || signing.some((position) => position <= load.index))
296
+ unsupported();
297
+ const source = declaration(load.fileName, 'File');
298
+ if (source.index >= load.index)
299
+ unsupported();
300
+ const call = fileCall(source.next);
301
+ const propertiesPath = literal(call.index);
302
+ endStatement(expect(call.index + 1, ')'));
303
+ for (const [position, token] of tokens.entries()) {
304
+ if (token.kind !== 'word' ||
305
+ token.value !== load.fileName ||
306
+ position === source.index ||
307
+ position === load.fileIndex)
308
+ continue;
309
+ if (value(position - 1) === '.')
310
+ unsupported();
311
+ let next = expect(position + 1, '.');
312
+ next = expect(next, 'exists');
313
+ next = expect(next, '(');
314
+ expect(next, ')');
315
+ }
316
+ return { propertiesPath, propertiesRoot: call.root, keyRoot };
317
+ }
318
+ //# sourceMappingURL=worktree-gradle.js.map