@vicinae/api 0.16.8 → 0.16.9

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.
@@ -40,14 +40,15 @@ const core_1 = require("@oclif/core");
40
40
  const chokidar = __importStar(require("chokidar"));
41
41
  const esbuild = __importStar(require("esbuild"));
42
42
  const node_child_process_1 = require("node:child_process");
43
- const node_fs_1 = require("node:fs");
44
- const promises_1 = require("node:fs/promises");
45
- const node_path_1 = require("node:path");
43
+ const fs = __importStar(require("node:fs"));
44
+ const fsp = __importStar(require("node:fs/promises"));
45
+ const path = __importStar(require("node:path"));
46
46
  const logger_js_1 = require("../../utils/logger.js");
47
47
  const utils_js_1 = require("../../utils/utils.js");
48
48
  const extension_types_js_1 = require("../../utils/extension-types.js");
49
49
  const vicinae_js_1 = require("../../utils/vicinae.js");
50
50
  const manifest_js_1 = __importDefault(require("../../schemas/manifest.js"));
51
+ const tail_js_1 = require("../../utils/tail.js");
51
52
  class Develop extends core_1.Command {
52
53
  static args = {};
53
54
  static description = "Start an extension development session";
@@ -67,13 +68,13 @@ class Develop extends core_1.Command {
67
68
  async run() {
68
69
  const { flags } = await this.parse(Develop);
69
70
  const logger = new logger_js_1.Logger();
70
- const pkgPath = (0, node_path_1.join)(flags.target, "package.json");
71
+ const pkgPath = path.join(flags.target, "package.json");
71
72
  const parseManifest = () => {
72
- if (!(0, node_fs_1.existsSync)(pkgPath)) {
73
+ if (!fs.existsSync(pkgPath)) {
73
74
  logger.logError(`No package.json found at ${pkgPath}. Does this location point to a valid extension repository?`);
74
75
  process.exit(1);
75
76
  }
76
- const json = JSON.parse((0, node_fs_1.readFileSync)(pkgPath, "utf8"));
77
+ const json = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
77
78
  const e = manifest_js_1.default.safeParse(json);
78
79
  if (e.error) {
79
80
  logger.logError(`${pkgPath} is not a valid extension manifest: ${e.error}`);
@@ -107,22 +108,22 @@ class Develop extends core_1.Command {
107
108
  });
108
109
  */
109
110
  const entryPoints = manifest.commands
110
- .map((cmd) => (0, node_path_1.join)("src", `${cmd.name}.tsx`))
111
- .filter(node_fs_1.existsSync);
111
+ .map((cmd) => path.join("src", `${cmd.name}.tsx`))
112
+ .filter(fs.existsSync);
112
113
  logger.logInfo(`entrypoints [${entryPoints.join(", ")}]`);
113
114
  const promises = manifest.commands.map((cmd) => {
114
- const base = (0, node_path_1.join)(process.cwd(), "src", `${cmd.name}`);
115
+ const base = path.join(process.cwd(), "src", `${cmd.name}`);
115
116
  const tsxSource = `${base}.tsx`;
116
117
  const tsSource = `${base}.ts`;
117
118
  let source = tsxSource;
118
- if (cmd.mode === "view" && !(0, node_fs_1.existsSync)(tsxSource)) {
119
+ if (cmd.mode === "view" && !fs.existsSync(tsxSource)) {
119
120
  throw new Error(`could not find entrypoint src/${cmd.name}.tsx for command ${cmd.name}.`);
120
121
  }
121
122
  // we allow .ts or .tsx for no-view
122
123
  if (cmd.mode === "no-view") {
123
- if (!(0, node_fs_1.existsSync)(tsxSource)) {
124
+ if (!fs.existsSync(tsxSource)) {
124
125
  source = tsSource;
125
- if (!(0, node_fs_1.existsSync)(tsSource)) {
126
+ if (!fs.existsSync(tsSource)) {
126
127
  throw new Error(`could not find entrypoint src/${cmd.name}.{ts,tsx} for command ${cmd.name}.`);
127
128
  }
128
129
  }
@@ -132,19 +133,19 @@ class Develop extends core_1.Command {
132
133
  entryPoints: [source],
133
134
  external: ["react", "@vicinae/api", "@raycast/api"],
134
135
  format: "cjs",
135
- outfile: (0, node_path_1.join)(outDir, `${cmd.name}.js`),
136
+ outfile: path.join(outDir, `${cmd.name}.js`),
136
137
  platform: "node",
137
138
  });
138
139
  });
139
140
  await Promise.all(promises);
140
- const targetPkg = (0, node_path_1.join)(outDir, "package.json");
141
- const targetAssets = (0, node_path_1.join)(outDir, "assets");
142
- (0, node_fs_1.cpSync)("package.json", targetPkg, { force: true });
143
- if ((0, node_fs_1.existsSync)("assets")) {
144
- (0, node_fs_1.cpSync)("assets", targetAssets, { force: true, recursive: true });
141
+ const targetPkg = path.join(outDir, "package.json");
142
+ const targetAssets = path.join(outDir, "assets");
143
+ fs.cpSync("package.json", targetPkg, { force: true });
144
+ if (fs.existsSync("assets")) {
145
+ fs.cpSync("assets", targetAssets, { force: true, recursive: true });
145
146
  }
146
147
  else {
147
- (0, node_fs_1.mkdirSync)(targetAssets, { recursive: true });
148
+ fs.mkdirSync(targetAssets, { recursive: true });
148
149
  }
149
150
  };
150
151
  const pingError = vicinae.ping();
@@ -172,12 +173,23 @@ class Develop extends core_1.Command {
172
173
  process.chdir(flags.target);
173
174
  const dataDir = (0, utils_js_1.extensionDataDir)();
174
175
  const id = `${manifest.name}`;
175
- const extensionDir = (0, node_path_1.join)(dataDir, id);
176
- const logFile = (0, node_path_1.join)(extensionDir, "dev.log");
177
- const pidFile = (0, node_path_1.join)(extensionDir, "cli.pid");
178
- (0, node_fs_1.mkdirSync)(extensionDir, { recursive: true });
179
- (0, node_fs_1.writeFileSync)(pidFile, `${process.pid}`);
180
- (0, node_fs_1.writeFileSync)(logFile, "");
176
+ const extensionDir = path.join(dataDir, id);
177
+ const internalSupportDir = (0, utils_js_1.extensionInternalSupportDir)(id);
178
+ const stdoutPath = path.join(internalSupportDir, "stdout.txt");
179
+ const stderrPath = path.join(internalSupportDir, "stderr.txt");
180
+ const pidFile = path.join(internalSupportDir, "cli.pid");
181
+ await Promise.all([extensionDir, internalSupportDir].map((dir) => fsp.mkdir(dir, { recursive: true })));
182
+ await fsp.writeFile(pidFile, `${process.pid}`);
183
+ const outStream = new tail_js_1.Tail(stdoutPath, { forceCreate: true, nLines: 0 });
184
+ const errStream = new tail_js_1.Tail(stderrPath, { forceCreate: true, nLines: 0 });
185
+ outStream.on("line", (data) => {
186
+ logger.logExtensionOut(data.toString());
187
+ });
188
+ outStream.on("error", () => { });
189
+ errStream.on("line", (data) => {
190
+ logger.logExtensionError(data.toString());
191
+ });
192
+ errStream.on("error", () => { });
181
193
  await safeBuild(extensionDir);
182
194
  process.on("SIGINT", () => {
183
195
  logger.logInfo("Shutting down...");
@@ -203,31 +215,6 @@ class Develop extends core_1.Command {
203
215
  logger.logEvent(`changed file ${path}`);
204
216
  await safeBuild(extensionDir);
205
217
  });
206
- const logFiles = new Map();
207
- chokidar.watch(logFile).on("all", async (_, path) => {
208
- const stats = await (0, promises_1.stat)(path);
209
- if (!stats.isFile())
210
- return;
211
- if (!logFiles.has(path)) {
212
- //logger.logInfo(`Monitoring new log file at ${path}`);
213
- logFiles.set(path, { cursor: 0, path });
214
- }
215
- const info = logFiles.get(path);
216
- if (info.cursor > stats.size) {
217
- info.cursor = 0;
218
- }
219
- if (stats.size === info.cursor)
220
- return;
221
- const handle = await (0, promises_1.open)(path, "r");
222
- const buffer = Buffer.alloc(stats.size - info.cursor);
223
- (0, node_fs_1.read)(handle.fd, buffer, 0, buffer.length, info.cursor, (error, nRead) => {
224
- if (error)
225
- return;
226
- info.cursor += nRead;
227
- logger.logTimestamp(buffer.toString());
228
- handle.close();
229
- });
230
- });
231
218
  }
232
219
  }
233
220
  exports.default = Develop;
@@ -9,5 +9,7 @@ export declare class Logger {
9
9
  logEvent(message: string): void;
10
10
  logInfo(message: string): void;
11
11
  logReady(message: string): void;
12
+ logExtensionOut(s: string): void;
13
+ logExtensionError(s: string): void;
12
14
  logTimestamp(s: string): void;
13
15
  }
@@ -1,16 +1,13 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.Logger = void 0;
7
- const chalk_1 = __importDefault(require("chalk"));
4
+ const yoctocolors_1 = require("./yoctocolors");
8
5
  class Logger {
9
6
  prefixes = {
10
- error: `${chalk_1.default.red("error")}${chalk_1.default.reset()}`,
11
- event: `${chalk_1.default.magenta("event")}${chalk_1.default.reset()}`,
12
- info: `${chalk_1.default.blue("info")}${chalk_1.default.reset()}`,
13
- ready: `${chalk_1.default.green("ready")}${chalk_1.default.reset()}`,
7
+ error: `${(0, yoctocolors_1.red)("error")}${(0, yoctocolors_1.reset)()}`,
8
+ event: `${(0, yoctocolors_1.magenta)("event")}${(0, yoctocolors_1.reset)()}`,
9
+ info: `${(0, yoctocolors_1.blue)("info")}${(0, yoctocolors_1.reset)()}`,
10
+ ready: `${(0, yoctocolors_1.green)("ready")}${(0, yoctocolors_1.reset)()}`,
14
11
  };
15
12
  logError(message) {
16
13
  console.log(`${this.prefixes.error.padEnd(15)} - ${message}`);
@@ -24,6 +21,12 @@ class Logger {
24
21
  logReady(message) {
25
22
  console.log(`${this.prefixes.ready.padEnd(15)} - ${message}`);
26
23
  }
24
+ logExtensionOut(s) {
25
+ this.logTimestamp(s);
26
+ }
27
+ logExtensionError(s) {
28
+ this.logTimestamp(`${(0, yoctocolors_1.red)(s)}${(0, yoctocolors_1.reset)()}`);
29
+ }
27
30
  logTimestamp(s) {
28
31
  const ts = new Date().toJSON();
29
32
  const lines = s.split("\n");
@@ -31,7 +34,7 @@ class Logger {
31
34
  const line = lines[i];
32
35
  if (i === lines.length - 1 && line.length === 0)
33
36
  continue;
34
- console.log(`${chalk_1.default.gray(ts.padEnd(20))}${chalk_1.default.reset()} - ${line}`);
37
+ console.log(`${(0, yoctocolors_1.gray)(ts.padEnd(20))}${(0, yoctocolors_1.reset)()} - ${line}`);
35
38
  }
36
39
  }
37
40
  }
@@ -0,0 +1,66 @@
1
+ import events from "node:events";
2
+ interface FSWatchOptions {
3
+ interval: number;
4
+ }
5
+ export interface TailOptions {
6
+ separator?: string | RegExp | null;
7
+ fsWatchOptions?: FSWatchOptions;
8
+ follow?: boolean;
9
+ logger?: DevNull;
10
+ useWatchFile?: boolean;
11
+ flushAtEOF?: boolean;
12
+ encoding?: BufferEncoding;
13
+ fromBeginning?: boolean;
14
+ nLines?: number;
15
+ /**
16
+ * Create an empty file if it does not exist.
17
+ */
18
+ forceCreate?: boolean;
19
+ }
20
+ declare class DevNull {
21
+ info(...args: any): void;
22
+ error(...args: any): void;
23
+ }
24
+ export declare class Tail extends events.EventEmitter {
25
+ private filename;
26
+ private absPath;
27
+ private separator?;
28
+ private fsWatchOptions;
29
+ private follow;
30
+ private logger;
31
+ private useWatchFile;
32
+ private flushAtEOF;
33
+ private encoding;
34
+ private nLines?;
35
+ private rewatchId;
36
+ private isWatching;
37
+ private queue;
38
+ private buffer;
39
+ private watcher;
40
+ private internalDispatcher;
41
+ private currentCursorPos;
42
+ constructor(filename: string, options?: TailOptions);
43
+ /**
44
+ * Grabs the index of the last line of text in the format /.*(\n)?/.
45
+ * Returns null if a full line can not be found.
46
+ * @param {string} text
47
+ * @returns {number | null}
48
+ */
49
+ private getIndexOfLastLine;
50
+ /**
51
+ * Returns the position of the start of the `nLines`th line from the bottom.
52
+ * Returns 0 if `nLines` is greater than the total number of lines in the file.
53
+ * @param {number} nLines
54
+ * @returns {number}
55
+ */
56
+ private getPositionAtNthLine;
57
+ private latestPosition;
58
+ private readBlock;
59
+ private change;
60
+ watch(startingCursor: number, flush?: boolean): void;
61
+ private rename;
62
+ private watchEvent;
63
+ private watchFileEvent;
64
+ unwatch(): void;
65
+ }
66
+ export {};
@@ -0,0 +1,352 @@
1
+ "use strict";
2
+ // Based on https://github.com/WandersonAlves/node-tail/blob/master/src/tail.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.Tail = void 0;
8
+ const node_events_1 = __importDefault(require("node:events"));
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ class DevNull {
12
+ info(...args) { }
13
+ error(...args) { }
14
+ }
15
+ class Tail extends node_events_1.default.EventEmitter {
16
+ filename;
17
+ absPath;
18
+ separator;
19
+ fsWatchOptions;
20
+ follow;
21
+ logger;
22
+ useWatchFile;
23
+ flushAtEOF;
24
+ encoding;
25
+ nLines;
26
+ rewatchId;
27
+ isWatching;
28
+ queue = [];
29
+ // NOTE Should we rename that as it is a string instead of a Buffer?
30
+ buffer;
31
+ watcher;
32
+ internalDispatcher;
33
+ currentCursorPos = 0;
34
+ constructor(filename, options = {}) {
35
+ super();
36
+ this.filename = filename;
37
+ this.absPath = (0, node_path_1.dirname)(this.filename);
38
+ this.separator =
39
+ options.separator !== undefined ? options.separator : /[\r]{0,1}\n/; // null is a valid param
40
+ this.fsWatchOptions = options.fsWatchOptions || {};
41
+ this.follow = options.follow ?? true;
42
+ this.logger = options.logger || new DevNull();
43
+ this.useWatchFile = options.useWatchFile || false;
44
+ this.flushAtEOF = options.flushAtEOF || false;
45
+ this.encoding = options.encoding || "utf-8";
46
+ const fromBeginning = options.fromBeginning || false;
47
+ this.nLines = options.nLines ?? 0;
48
+ this.logger.info(`Tail starting...`);
49
+ this.logger.info(`filename: ${this.filename}`);
50
+ this.logger.info(`encoding: ${this.encoding}`);
51
+ try {
52
+ (0, node_fs_1.accessSync)(this.filename, node_fs_1.constants.F_OK);
53
+ }
54
+ catch (err) {
55
+ if (err.code === "ENOENT") {
56
+ if (!options.forceCreate)
57
+ throw err;
58
+ (0, node_fs_1.writeFileSync)(this.filename, "");
59
+ }
60
+ }
61
+ this.buffer = "";
62
+ this.internalDispatcher = new node_events_1.default.EventEmitter();
63
+ this.isWatching = false;
64
+ // this.internalDispatcher.on('next',this.readBlock);
65
+ this.internalDispatcher.on("next", () => {
66
+ this.readBlock();
67
+ });
68
+ let cursor = 0;
69
+ this.logger.info(`fromBeginning: ${fromBeginning}`);
70
+ if (fromBeginning) {
71
+ cursor = 0;
72
+ }
73
+ else if (this.nLines <= 0) {
74
+ cursor = 0;
75
+ }
76
+ else if (this.nLines !== undefined) {
77
+ cursor = this.getPositionAtNthLine(this.nLines);
78
+ }
79
+ else {
80
+ cursor = this.latestPosition();
81
+ }
82
+ if (cursor === undefined)
83
+ throw new Error("Tail can't initialize.");
84
+ const flush = fromBeginning || this.nLines !== undefined;
85
+ try {
86
+ this.watch(cursor, flush);
87
+ }
88
+ catch (err) {
89
+ this.logger.error(`watch for ${this.filename} failed: ${err}`);
90
+ this.emit("error", `watch for ${this.filename} failed: ${err}`);
91
+ }
92
+ }
93
+ /**
94
+ * Grabs the index of the last line of text in the format /.*(\n)?/.
95
+ * Returns null if a full line can not be found.
96
+ * @param {string} text
97
+ * @returns {number | null}
98
+ */
99
+ getIndexOfLastLine(text) {
100
+ /**
101
+ * Helper function get the last match as string
102
+ * @param {string} haystack
103
+ * @param {string | RegExp} needle
104
+ * @returns {string | undefined}
105
+ */
106
+ const getLastMatch = (haystack, needle) => {
107
+ // NOTE `as string` was used to cast the needle to string, but it can be null as well. Just making TS compiler happy
108
+ const matches = haystack.match(needle);
109
+ if (matches === null) {
110
+ return;
111
+ }
112
+ return matches[matches.length - 1];
113
+ };
114
+ // NOTE `as string` was used to cast the needle to string, but it can be null as well. Just making TS compiler happy
115
+ const endSep = getLastMatch(text, this.separator);
116
+ if (!endSep)
117
+ return null;
118
+ const endSepIndex = text.lastIndexOf(endSep);
119
+ let lastLine = "";
120
+ if (text.endsWith(endSep)) {
121
+ // If the text ends with a separator, look back further to find the next
122
+ // separator to complete the line
123
+ const trimmed = text.substring(0, endSepIndex);
124
+ // NOTE `as string` was used to cast the needle to string, but it can be null as well. Just making TS compiler happy
125
+ const startSep = getLastMatch(trimmed, this.separator);
126
+ // If there isn't another separator, the line isn't complete so
127
+ // so return null to get more data
128
+ if (!startSep) {
129
+ return null;
130
+ }
131
+ const startSepIndex = trimmed.lastIndexOf(startSep);
132
+ // Exclude the starting separator, include the ending separator
133
+ lastLine = text.substring(startSepIndex + startSep.length, endSepIndex + endSep.length);
134
+ }
135
+ else {
136
+ // If the text does not end with a separator, grab everything after
137
+ // the last separator
138
+ lastLine = text.substring(endSepIndex + endSep.length);
139
+ }
140
+ return text.lastIndexOf(lastLine);
141
+ }
142
+ /**
143
+ * Returns the position of the start of the `nLines`th line from the bottom.
144
+ * Returns 0 if `nLines` is greater than the total number of lines in the file.
145
+ * @param {number} nLines
146
+ * @returns {number}
147
+ */
148
+ getPositionAtNthLine(nLines) {
149
+ const { size } = (0, node_fs_1.statSync)(this.filename);
150
+ if (size === 0) {
151
+ return 0;
152
+ }
153
+ const fd = (0, node_fs_1.openSync)(this.filename, "r");
154
+ // Start from the end of the file and work backwards in specific chunks
155
+ let currentReadPosition = size;
156
+ const chunkSizeBytes = Math.min(1024, size);
157
+ const lineBytes = [];
158
+ let remaining = "";
159
+ while (lineBytes.length < nLines) {
160
+ // Shift the current read position backward to the amount we're about to read
161
+ currentReadPosition -= chunkSizeBytes;
162
+ // If negative, we've reached the beginning of the file and we should stop and return 0, starting the
163
+ // stream at the beginning.
164
+ if (currentReadPosition < 0) {
165
+ return 0;
166
+ }
167
+ // Read a chunk of the file and prepend it to the working buffer
168
+ const buffer = Buffer.alloc(chunkSizeBytes);
169
+ const bytesRead = (0, node_fs_1.readSync)(fd, buffer, 0, // position in buffer to write to
170
+ chunkSizeBytes, // number of bytes to read
171
+ currentReadPosition);
172
+ // .subarray returns Uint8Array in node versions < 16.x and Buffer
173
+ // in versions >= 16.x. To support both, allocate a new buffer with
174
+ // Buffer.from which accepts both types
175
+ const readArray = buffer.subarray(0, bytesRead);
176
+ remaining = Buffer.from(readArray).toString(this.encoding) + remaining;
177
+ let index = this.getIndexOfLastLine(remaining);
178
+ while (index !== null && lineBytes.length < nLines) {
179
+ const line = remaining.substring(index);
180
+ lineBytes.push(Buffer.byteLength(line));
181
+ remaining = remaining.substring(0, index);
182
+ index = this.getIndexOfLastLine(remaining);
183
+ }
184
+ }
185
+ (0, node_fs_1.closeSync)(fd);
186
+ return size - lineBytes.reduce((acc, cur) => acc + cur, 0);
187
+ }
188
+ latestPosition() {
189
+ try {
190
+ return (0, node_fs_1.statSync)(this.filename).size;
191
+ }
192
+ catch (err) {
193
+ this.logger.error(`size check for ${this.filename} failed: ${err}`);
194
+ this.emit("error", `size check for ${this.filename} failed: ${err}`);
195
+ throw err;
196
+ }
197
+ }
198
+ readBlock() {
199
+ if (this.queue.length >= 1) {
200
+ const block = this.queue[0];
201
+ if (block.end > block.start) {
202
+ const stream = (0, node_fs_1.createReadStream)(this.filename, {
203
+ start: block.start,
204
+ end: block.end - 1,
205
+ encoding: this.encoding,
206
+ });
207
+ stream.on("error", (error) => {
208
+ this.logger.error(`Tail error: ${error}`);
209
+ this.emit("error", error);
210
+ });
211
+ stream.on("end", () => {
212
+ this.queue.shift();
213
+ if (this.queue.length > 0) {
214
+ this.internalDispatcher.emit("next");
215
+ }
216
+ if (this.flushAtEOF && this.buffer.length > 0) {
217
+ this.emit("line", this.buffer);
218
+ this.buffer = "";
219
+ }
220
+ });
221
+ stream.on("data", (d) => {
222
+ if (this.separator === null) {
223
+ this.emit("line", d);
224
+ }
225
+ else {
226
+ this.buffer += d;
227
+ // NOTE `as string` was used to cast the needle to string, but it can be null as well. Just making TS compiler happy
228
+ let parts = this.buffer.split(this.separator);
229
+ // NOTE Since parts.pop could return undefined, i'm returning a empty string when that happens
230
+ this.buffer = parts.pop() ?? "";
231
+ for (const chunk of parts) {
232
+ this.emit("line", chunk);
233
+ }
234
+ }
235
+ });
236
+ }
237
+ }
238
+ }
239
+ change() {
240
+ const p = this.latestPosition();
241
+ if (p < this.currentCursorPos) {
242
+ //scenario where text is not appended but it's actually a w+
243
+ this.currentCursorPos = p;
244
+ }
245
+ else if (p > this.currentCursorPos) {
246
+ this.queue.push({ start: this.currentCursorPos, end: p });
247
+ this.currentCursorPos = p;
248
+ if (this.queue.length === 1) {
249
+ this.internalDispatcher.emit("next");
250
+ }
251
+ }
252
+ }
253
+ watch(startingCursor, flush) {
254
+ if (this.isWatching)
255
+ return;
256
+ this.logger.info(`filesystem.watch present? ${node_fs_1.watch !== undefined}`);
257
+ this.logger.info(`useWatchFile: ${this.useWatchFile}`);
258
+ this.isWatching = true;
259
+ this.currentCursorPos = startingCursor;
260
+ //force a file flush is either fromBegining or nLines flags were passed.
261
+ if (flush)
262
+ this.change();
263
+ if (!this.useWatchFile) {
264
+ this.logger.info(`watch strategy: watch`);
265
+ this.watcher = (0, node_fs_1.watch)(this.filename, this.fsWatchOptions, (e, filename) => {
266
+ // NOTE Filename here is a `Buffer`, how it's used as a string?
267
+ // NOTE Test if filename.toString changes the behavior
268
+ if (filename)
269
+ this.watchEvent(e, filename.toString());
270
+ });
271
+ }
272
+ else {
273
+ this.logger.info(`watch strategy: watchFile`);
274
+ (0, node_fs_1.watchFile)(this.filename, this.fsWatchOptions, (curr, prev) => {
275
+ this.watchFileEvent(curr, prev);
276
+ });
277
+ }
278
+ }
279
+ rename(filename) {
280
+ //TODO
281
+ //MacOS sometimes throws a rename event for no reason.
282
+ //Different platforms might behave differently.
283
+ //see https://nodejs.org/api/fs.html#fs_fs_watch_filename_options_listener
284
+ //filename might not be present.
285
+ //https://nodejs.org/api/fs.html#fs_filename_argument
286
+ //Better solution would be check inode but it will require a timeout and
287
+ // a sync file read.
288
+ if (filename === undefined || filename !== this.filename) {
289
+ this.unwatch();
290
+ if (this.follow) {
291
+ this.filename = (0, node_path_1.join)(this.absPath, filename);
292
+ this.rewatchId = setTimeout(() => {
293
+ try {
294
+ this.watch(this.currentCursorPos);
295
+ }
296
+ catch (ex) {
297
+ this.logger.error(`'rename' event for ${this.filename}. File not available anymore.`);
298
+ this.emit("error", ex);
299
+ }
300
+ }, 1000);
301
+ }
302
+ else {
303
+ this.logger.error(`'rename' event for ${this.filename}. File not available anymore.`);
304
+ this.emit("error", `'rename' event for ${this.filename}. File not available anymore.`);
305
+ }
306
+ }
307
+ else {
308
+ // this.logger.info("rename event but same filename")
309
+ }
310
+ }
311
+ watchEvent(evtName, evtFilename) {
312
+ try {
313
+ if (evtName === "change") {
314
+ this.change();
315
+ }
316
+ else if (evtName === "rename") {
317
+ this.rename(evtFilename);
318
+ }
319
+ }
320
+ catch (err) {
321
+ this.logger.error(`watchEvent for ${this.filename} failed: ${err}`);
322
+ this.emit("error", `watchEvent for ${this.filename} failed: ${err}`);
323
+ }
324
+ }
325
+ watchFileEvent(curr, prev) {
326
+ if (curr.size > prev.size) {
327
+ this.currentCursorPos = curr.size; //Update this.currentCursorPos so that a consumer can determine if entire file has been handled
328
+ this.queue.push({ start: prev.size, end: curr.size });
329
+ if (this.queue.length === 1) {
330
+ this.internalDispatcher.emit("next");
331
+ }
332
+ }
333
+ }
334
+ unwatch() {
335
+ if (this.watcher) {
336
+ this.watcher.close();
337
+ }
338
+ else {
339
+ (0, node_fs_1.unwatchFile)(this.filename);
340
+ }
341
+ if (this.rewatchId) {
342
+ clearTimeout(this.rewatchId);
343
+ this.rewatchId = undefined;
344
+ }
345
+ this.isWatching = false;
346
+ this.queue = []; // TODO: is this correct behaviour?
347
+ if (this.logger) {
348
+ this.logger.info(`Unwatch ${this.filename}`);
349
+ }
350
+ }
351
+ }
352
+ exports.Tail = Tail;
@@ -1,2 +1,4 @@
1
1
  export declare const dataDir: () => string;
2
2
  export declare const extensionDataDir: () => string;
3
+ export declare const supportDir: string;
4
+ export declare const extensionInternalSupportDir: (id: string) => string;