codify-plugin-lib 1.0.182-beta4 → 1.0.182-beta6
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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pty/seqeuntial-pty.js +1 -1
- package/dist/utils/file-utils.d.ts +16 -0
- package/dist/utils/file-utils.js +172 -0
- package/package.json +1 -1
- package/src/index.ts +1 -1
- package/src/pty/seqeuntial-pty.ts +1 -1
- package/src/pty/sequential-pty.test.ts +2 -2
- package/src/utils/file-utils.ts +216 -0
package/dist/index.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export * from './resource/parsed-resource-settings.js';
|
|
|
10
10
|
export * from './resource/resource.js';
|
|
11
11
|
export * from './resource/resource-settings.js';
|
|
12
12
|
export * from './stateful-parameter/stateful-parameter.js';
|
|
13
|
+
export * from './utils/functions.js';
|
|
13
14
|
export * from './utils/index.js';
|
|
14
15
|
export * from './utils/verbosity-level.js';
|
|
15
|
-
export * from './utils/functions.js';
|
|
16
16
|
export declare function runPlugin(plugin: Plugin): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -10,9 +10,9 @@ export * from './resource/parsed-resource-settings.js';
|
|
|
10
10
|
export * from './resource/resource.js';
|
|
11
11
|
export * from './resource/resource-settings.js';
|
|
12
12
|
export * from './stateful-parameter/stateful-parameter.js';
|
|
13
|
+
export * from './utils/functions.js';
|
|
13
14
|
export * from './utils/index.js';
|
|
14
15
|
export * from './utils/verbosity-level.js';
|
|
15
|
-
export * from './utils/functions.js';
|
|
16
16
|
export async function runPlugin(plugin) {
|
|
17
17
|
const messageHandler = new MessageHandler(plugin);
|
|
18
18
|
process.on('message', (message) => messageHandler.onMessage(message));
|
|
@@ -35,7 +35,7 @@ export class SequentialPty {
|
|
|
35
35
|
// Initial terminal dimensions
|
|
36
36
|
const initialCols = process.stdout.columns ?? 80;
|
|
37
37
|
const initialRows = process.stdout.rows ?? 24;
|
|
38
|
-
const args = (options?.interactive ?? false) ? ['-i', '-c',
|
|
38
|
+
const args = (options?.interactive ?? false) ? ['-i', '-c', cmd] : ['-c', cmd];
|
|
39
39
|
// Run the command in a pty for interactivity
|
|
40
40
|
const mPty = pty.spawn(this.getDefaultShell(), args, {
|
|
41
41
|
...options,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class FileUtils {
|
|
2
|
+
static downloadFile(url: string, destination: string): Promise<void>;
|
|
3
|
+
static addToStartupFile(line: string): Promise<void>;
|
|
4
|
+
static addAllToStartupFile(lines: string[]): Promise<void>;
|
|
5
|
+
static addPathToPrimaryShellRc(value: string, prepend: boolean): Promise<void>;
|
|
6
|
+
static dirExists(path: string): Promise<boolean>;
|
|
7
|
+
static fileExists(path: string): Promise<boolean>;
|
|
8
|
+
static exists(path: string): Promise<boolean>;
|
|
9
|
+
static checkDirExistsOrThrowIfFile(path: string): Promise<boolean>;
|
|
10
|
+
static createDirIfNotExists(path: string): Promise<void>;
|
|
11
|
+
static removeFromFile(filePath: string, search: string): Promise<void>;
|
|
12
|
+
static removeLineFromFile(filePath: string, search: RegExp | string): Promise<void>;
|
|
13
|
+
static removeLineFromPrimaryShellRc(search: RegExp | string): Promise<void>;
|
|
14
|
+
static appendToFileWithSpacing(file: string, textToInsert: string): string;
|
|
15
|
+
private static calculateEndingNewLines;
|
|
16
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as fsSync from 'node:fs';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { finished } from 'node:stream/promises';
|
|
6
|
+
import { Utils } from './index.js';
|
|
7
|
+
const SPACE_REGEX = /^\s*$/;
|
|
8
|
+
export class FileUtils {
|
|
9
|
+
static async downloadFile(url, destination) {
|
|
10
|
+
console.log(`Downloading file from ${url} to ${destination}`);
|
|
11
|
+
const { body } = await fetch(url);
|
|
12
|
+
const dirname = path.dirname(destination);
|
|
13
|
+
if (!await fs.stat(dirname).then((s) => s.isDirectory()).catch(() => false)) {
|
|
14
|
+
await fs.mkdir(dirname, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
const ws = fsSync.createWriteStream(destination);
|
|
17
|
+
// Different type definitions here for readable stream (NodeJS vs DOM). Small hack to fix that
|
|
18
|
+
await finished(Readable.fromWeb(body).pipe(ws));
|
|
19
|
+
console.log(`Finished downloading to ${destination}`);
|
|
20
|
+
}
|
|
21
|
+
static async addToStartupFile(line) {
|
|
22
|
+
const lineToInsert = addLeadingSpacer(addTrailingSpacer(line));
|
|
23
|
+
await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert);
|
|
24
|
+
function addLeadingSpacer(line) {
|
|
25
|
+
return line.startsWith('\n')
|
|
26
|
+
? line
|
|
27
|
+
: '\n' + line;
|
|
28
|
+
}
|
|
29
|
+
function addTrailingSpacer(line) {
|
|
30
|
+
return line.endsWith('\n')
|
|
31
|
+
? line
|
|
32
|
+
: line + '\n';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
static async addAllToStartupFile(lines) {
|
|
36
|
+
const formattedLines = '\n' + lines.join('\n') + '\n';
|
|
37
|
+
const shellRc = Utils.getPrimaryShellRc();
|
|
38
|
+
console.log(`Adding to ${path.basename(shellRc)}:
|
|
39
|
+
${lines.join('\n')}`);
|
|
40
|
+
await fs.appendFile(shellRc, formattedLines);
|
|
41
|
+
}
|
|
42
|
+
static async addPathToPrimaryShellRc(value, prepend) {
|
|
43
|
+
const shellRc = Utils.getPrimaryShellRc();
|
|
44
|
+
console.log(`Saving path: ${value} to ${shellRc}`);
|
|
45
|
+
if (prepend) {
|
|
46
|
+
await fs.appendFile(shellRc, `\nexport PATH=$PATH:${value};`, { encoding: 'utf8' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
|
|
50
|
+
}
|
|
51
|
+
static async dirExists(path) {
|
|
52
|
+
let stat;
|
|
53
|
+
try {
|
|
54
|
+
stat = await fs.stat(path);
|
|
55
|
+
return stat.isDirectory();
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
static async fileExists(path) {
|
|
62
|
+
let stat;
|
|
63
|
+
try {
|
|
64
|
+
stat = await fs.stat(path);
|
|
65
|
+
return stat.isFile();
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
static async exists(path) {
|
|
72
|
+
try {
|
|
73
|
+
await fs.stat(path);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
static async checkDirExistsOrThrowIfFile(path) {
|
|
81
|
+
let stat;
|
|
82
|
+
try {
|
|
83
|
+
stat = await fs.stat(path);
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (stat.isDirectory()) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
throw new Error(`Directory ${path} already exists and is a file`);
|
|
92
|
+
}
|
|
93
|
+
static async createDirIfNotExists(path) {
|
|
94
|
+
if (!fsSync.existsSync(path)) {
|
|
95
|
+
await fs.mkdir(path, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
static async removeFromFile(filePath, search) {
|
|
99
|
+
const contents = await fs.readFile(filePath, 'utf8');
|
|
100
|
+
const newContents = contents.replaceAll(search, '');
|
|
101
|
+
await fs.writeFile(filePath, newContents, 'utf8');
|
|
102
|
+
}
|
|
103
|
+
static async removeLineFromFile(filePath, search) {
|
|
104
|
+
const file = await fs.readFile(filePath, 'utf8');
|
|
105
|
+
const lines = file.split('\n');
|
|
106
|
+
let searchRegex;
|
|
107
|
+
let searchString;
|
|
108
|
+
if (typeof search === 'object') {
|
|
109
|
+
const startRegex = /^([\t ]*)?/;
|
|
110
|
+
const endRegex = /([\t ]*)?/;
|
|
111
|
+
// Augment regex with spaces criteria to make sure this function is not deleting lines that are comments or has other content.
|
|
112
|
+
searchRegex = search
|
|
113
|
+
? new RegExp(startRegex.source + search.source + endRegex.source, search.flags)
|
|
114
|
+
: search;
|
|
115
|
+
}
|
|
116
|
+
if (typeof search === 'string') {
|
|
117
|
+
searchString = search;
|
|
118
|
+
}
|
|
119
|
+
for (let counter = lines.length; counter >= 0; counter--) {
|
|
120
|
+
if (!lines[counter]) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (searchString && lines[counter].includes(searchString)) {
|
|
124
|
+
lines.splice(counter, 1);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (searchRegex && lines[counter].search(searchRegex) !== -1) {
|
|
128
|
+
lines.splice(counter, 1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
await fs.writeFile(filePath, lines.join('\n'));
|
|
132
|
+
console.log(`Removed line: ${search} from ${filePath}`);
|
|
133
|
+
}
|
|
134
|
+
static async removeLineFromPrimaryShellRc(search) {
|
|
135
|
+
return FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
|
|
136
|
+
}
|
|
137
|
+
// Append the string to the end of a file ensuring at least 1 lines of space between.
|
|
138
|
+
// Ex result:
|
|
139
|
+
// something something;
|
|
140
|
+
//
|
|
141
|
+
// newline;
|
|
142
|
+
static appendToFileWithSpacing(file, textToInsert) {
|
|
143
|
+
const lines = file.trimEnd().split(/\n/);
|
|
144
|
+
if (lines.length === 0) {
|
|
145
|
+
return textToInsert;
|
|
146
|
+
}
|
|
147
|
+
const endingNewLines = FileUtils.calculateEndingNewLines(lines);
|
|
148
|
+
const numNewLines = endingNewLines === -1
|
|
149
|
+
? 0
|
|
150
|
+
: Math.max(0, 2 - endingNewLines);
|
|
151
|
+
return lines.join('\n') + '\n'.repeat(numNewLines) + textToInsert;
|
|
152
|
+
}
|
|
153
|
+
// This is overly complicated but it can be used to insert into any
|
|
154
|
+
// position in the future
|
|
155
|
+
static calculateEndingNewLines(lines) {
|
|
156
|
+
let counter = 0;
|
|
157
|
+
while (true) {
|
|
158
|
+
const line = lines.at(-counter - 1);
|
|
159
|
+
if (!line) {
|
|
160
|
+
return -1;
|
|
161
|
+
}
|
|
162
|
+
if (!SPACE_REGEX.test(line)) {
|
|
163
|
+
return counter;
|
|
164
|
+
}
|
|
165
|
+
counter++;
|
|
166
|
+
// Short circuit here because we don't need to check over 2;
|
|
167
|
+
if (counter > 2) {
|
|
168
|
+
return counter;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,9 +12,9 @@ export * from './resource/parsed-resource-settings.js';
|
|
|
12
12
|
export * from './resource/resource.js'
|
|
13
13
|
export * from './resource/resource-settings.js'
|
|
14
14
|
export * from './stateful-parameter/stateful-parameter.js'
|
|
15
|
+
export * from './utils/functions.js'
|
|
15
16
|
export * from './utils/index.js'
|
|
16
17
|
export * from './utils/verbosity-level.js'
|
|
17
|
-
export * from './utils/functions.js'
|
|
18
18
|
|
|
19
19
|
export async function runPlugin(plugin: Plugin) {
|
|
20
20
|
const messageHandler = new MessageHandler(plugin);
|
|
@@ -46,7 +46,7 @@ export class SequentialPty implements IPty {
|
|
|
46
46
|
const initialCols = process.stdout.columns ?? 80;
|
|
47
47
|
const initialRows = process.stdout.rows ?? 24;
|
|
48
48
|
|
|
49
|
-
const args = (options?.interactive ?? false) ? ['-i', '-c',
|
|
49
|
+
const args = (options?.interactive ?? false) ? ['-i', '-c', cmd] : ['-c', cmd]
|
|
50
50
|
|
|
51
51
|
// Run the command in a pty for interactivity
|
|
52
52
|
const mPty = pty.spawn(this.getDefaultShell(), args, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { SequentialPty } from './seqeuntial-pty.js';
|
|
3
|
-
import { VerbosityLevel } from '../utils/
|
|
3
|
+
import { VerbosityLevel } from '../utils/verbosity-level.js';
|
|
4
4
|
|
|
5
5
|
describe('SequentialPty tests', () => {
|
|
6
6
|
it('Can launch a simple command', async () => {
|
|
@@ -52,7 +52,7 @@ describe('SequentialPty tests', () => {
|
|
|
52
52
|
it('It can launch a command in interactive mode', async () => {
|
|
53
53
|
const pty = new SequentialPty();
|
|
54
54
|
|
|
55
|
-
const resultSuccess = await pty.spawnSafe('ls', { interactive:
|
|
55
|
+
const resultSuccess = await pty.spawnSafe('ls', { interactive: true });
|
|
56
56
|
expect(resultSuccess).toMatchObject({
|
|
57
57
|
status: 'success',
|
|
58
58
|
exitCode: 0,
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as fsSync from 'node:fs';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { Readable } from 'node:stream';
|
|
5
|
+
import { finished } from 'node:stream/promises';
|
|
6
|
+
|
|
7
|
+
import { Utils } from './index.js';
|
|
8
|
+
|
|
9
|
+
const SPACE_REGEX = /^\s*$/
|
|
10
|
+
|
|
11
|
+
export class FileUtils {
|
|
12
|
+
static async downloadFile(url: string, destination: string): Promise<void> {
|
|
13
|
+
console.log(`Downloading file from ${url} to ${destination}`);
|
|
14
|
+
const { body } = await fetch(url)
|
|
15
|
+
|
|
16
|
+
const dirname = path.dirname(destination);
|
|
17
|
+
if (!await fs.stat(dirname).then((s) => s.isDirectory()).catch(() => false)) {
|
|
18
|
+
await fs.mkdir(dirname, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const ws = fsSync.createWriteStream(destination)
|
|
22
|
+
// Different type definitions here for readable stream (NodeJS vs DOM). Small hack to fix that
|
|
23
|
+
await finished(Readable.fromWeb(body as never).pipe(ws));
|
|
24
|
+
|
|
25
|
+
console.log(`Finished downloading to ${destination}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static async addToStartupFile(line: string): Promise<void> {
|
|
29
|
+
const lineToInsert = addLeadingSpacer(
|
|
30
|
+
addTrailingSpacer(line)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
await fs.appendFile(Utils.getPrimaryShellRc(), lineToInsert)
|
|
34
|
+
|
|
35
|
+
function addLeadingSpacer(line: string): string {
|
|
36
|
+
return line.startsWith('\n')
|
|
37
|
+
? line
|
|
38
|
+
: '\n' + line;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function addTrailingSpacer(line: string): string {
|
|
42
|
+
return line.endsWith('\n')
|
|
43
|
+
? line
|
|
44
|
+
: line + '\n';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
static async addAllToStartupFile(lines: string[]): Promise<void> {
|
|
49
|
+
const formattedLines = '\n' + lines.join('\n') + '\n';
|
|
50
|
+
const shellRc = Utils.getPrimaryShellRc();
|
|
51
|
+
|
|
52
|
+
console.log(`Adding to ${path.basename(shellRc)}:
|
|
53
|
+
${lines.join('\n')}`)
|
|
54
|
+
|
|
55
|
+
await fs.appendFile(shellRc, formattedLines)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static async addPathToPrimaryShellRc(value: string, prepend: boolean): Promise<void> {
|
|
59
|
+
const shellRc = Utils.getPrimaryShellRc();
|
|
60
|
+
console.log(`Saving path: ${value} to ${shellRc}`);
|
|
61
|
+
|
|
62
|
+
if (prepend) {
|
|
63
|
+
await fs.appendFile(shellRc, `\nexport PATH=$PATH:${value};`, { encoding: 'utf8' });
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
await fs.appendFile(shellRc, `\nexport PATH=${value}:$PATH;`, { encoding: 'utf8' });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
static async dirExists(path: string): Promise<boolean> {
|
|
71
|
+
let stat;
|
|
72
|
+
try {
|
|
73
|
+
stat = await fs.stat(path);
|
|
74
|
+
return stat.isDirectory();
|
|
75
|
+
} catch {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static async fileExists(path: string): Promise<boolean> {
|
|
81
|
+
let stat;
|
|
82
|
+
try {
|
|
83
|
+
stat = await fs.stat(path);
|
|
84
|
+
return stat.isFile();
|
|
85
|
+
} catch {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
static async exists(path: string): Promise<boolean> {
|
|
91
|
+
try {
|
|
92
|
+
await fs.stat(path);
|
|
93
|
+
return true;
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static async checkDirExistsOrThrowIfFile(path: string): Promise<boolean> {
|
|
100
|
+
let stat;
|
|
101
|
+
try {
|
|
102
|
+
stat = await fs.stat(path);
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (stat.isDirectory()) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
throw new Error(`Directory ${path} already exists and is a file`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
static async createDirIfNotExists(path: string): Promise<void> {
|
|
115
|
+
if (!fsSync.existsSync(path)) {
|
|
116
|
+
await fs.mkdir(path, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
static async removeFromFile(filePath: string, search: string): Promise<void> {
|
|
121
|
+
const contents = await fs.readFile(filePath, 'utf8');
|
|
122
|
+
const newContents = contents.replaceAll(search, '');
|
|
123
|
+
|
|
124
|
+
await fs.writeFile(filePath, newContents, 'utf8');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
static async removeLineFromFile(filePath: string, search: RegExp | string): Promise<void> {
|
|
129
|
+
const file = await fs.readFile(filePath, 'utf8')
|
|
130
|
+
const lines = file.split('\n');
|
|
131
|
+
|
|
132
|
+
let searchRegex;
|
|
133
|
+
let searchString;
|
|
134
|
+
|
|
135
|
+
if (typeof search === 'object') {
|
|
136
|
+
const startRegex = /^([\t ]*)?/;
|
|
137
|
+
const endRegex = /([\t ]*)?/;
|
|
138
|
+
|
|
139
|
+
// Augment regex with spaces criteria to make sure this function is not deleting lines that are comments or has other content.
|
|
140
|
+
searchRegex = search
|
|
141
|
+
? new RegExp(
|
|
142
|
+
startRegex.source + search.source + endRegex.source,
|
|
143
|
+
search.flags
|
|
144
|
+
)
|
|
145
|
+
: search;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (typeof search === 'string') {
|
|
149
|
+
searchString = search;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (let counter = lines.length; counter >= 0; counter--) {
|
|
153
|
+
if (!lines[counter]) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (searchString && lines[counter].includes(searchString)) {
|
|
158
|
+
lines.splice(counter, 1);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (searchRegex && lines[counter].search(searchRegex) !== -1) {
|
|
163
|
+
lines.splice(counter, 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await fs.writeFile(filePath, lines.join('\n'));
|
|
168
|
+
console.log(`Removed line: ${search} from ${filePath}`)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
static async removeLineFromPrimaryShellRc(search: RegExp | string): Promise<void> {
|
|
172
|
+
return FileUtils.removeLineFromFile(Utils.getPrimaryShellRc(), search);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Append the string to the end of a file ensuring at least 1 lines of space between.
|
|
176
|
+
// Ex result:
|
|
177
|
+
// something something;
|
|
178
|
+
//
|
|
179
|
+
// newline;
|
|
180
|
+
static appendToFileWithSpacing(file: string, textToInsert: string): string {
|
|
181
|
+
const lines = file.trimEnd().split(/\n/);
|
|
182
|
+
if (lines.length === 0) {
|
|
183
|
+
return textToInsert;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const endingNewLines = FileUtils.calculateEndingNewLines(lines);
|
|
187
|
+
const numNewLines = endingNewLines === -1
|
|
188
|
+
? 0
|
|
189
|
+
: Math.max(0, 2 - endingNewLines);
|
|
190
|
+
return lines.join('\n') + '\n'.repeat(numNewLines) + textToInsert
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// This is overly complicated but it can be used to insert into any
|
|
194
|
+
// position in the future
|
|
195
|
+
private static calculateEndingNewLines(lines: string[]): number {
|
|
196
|
+
let counter = 0;
|
|
197
|
+
while (true) {
|
|
198
|
+
const line = lines.at(-counter - 1);
|
|
199
|
+
|
|
200
|
+
if (!line) {
|
|
201
|
+
return -1
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!SPACE_REGEX.test(line)) {
|
|
205
|
+
return counter;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
counter++;
|
|
209
|
+
|
|
210
|
+
// Short circuit here because we don't need to check over 2;
|
|
211
|
+
if (counter > 2) {
|
|
212
|
+
return counter;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|