@tenonhq/dovetail-core 0.0.107 → 0.0.108
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/FileLogger.js
CHANGED
|
@@ -37,6 +37,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
exports.fileLogger = void 0;
|
|
40
|
+
exports.shouldEnableFileLogging = shouldEnableFileLogging;
|
|
40
41
|
exports.enableFileLogging = enableFileLogging;
|
|
41
42
|
const fs = __importStar(require("fs"));
|
|
42
43
|
const path = __importStar(require("path"));
|
|
@@ -45,14 +46,58 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
45
46
|
// Long-running sessions (e.g. `dove watch`) would otherwise grow a single
|
|
46
47
|
// debug log unbounded; capping at 200 writes per file keeps each one small.
|
|
47
48
|
const ROTATION_WRITE_LIMIT = 200;
|
|
49
|
+
// Truthy spellings accepted for the DOVETAIL_DEBUG env var.
|
|
50
|
+
const TRUTHY = /^(1|true|yes|on)$/i;
|
|
51
|
+
/**
|
|
52
|
+
* @description Decide whether the file logger should write `dovetail-debug-*.log`
|
|
53
|
+
* files to disk. File logging is OFF by default and only turns on when the user
|
|
54
|
+
* explicitly opts in — either the `--debug` CLI flag (`--debug` or
|
|
55
|
+
* `--debug=<truthy>`) or the `DOVETAIL_DEBUG` env var set to a truthy value.
|
|
56
|
+
* Pure and side-effect free so it can be unit-tested with injected inputs.
|
|
57
|
+
* @param {string[]} argv - CLI args after the node + script entries (process.argv.slice(2)).
|
|
58
|
+
* @param {NodeJS.ProcessEnv} env - Environment map (defaults to an empty object).
|
|
59
|
+
* @returns {boolean} true when file logging should be enabled.
|
|
60
|
+
*/
|
|
61
|
+
function shouldEnableFileLogging(argv = [], env = {}) {
|
|
62
|
+
const raw = typeof env.DOVETAIL_DEBUG === "string" ? env.DOVETAIL_DEBUG.trim() : "";
|
|
63
|
+
if (TRUTHY.test(raw))
|
|
64
|
+
return true;
|
|
65
|
+
const args = Array.isArray(argv) ? argv : [];
|
|
66
|
+
for (let i = 0; i < args.length; i++) {
|
|
67
|
+
const arg = args[i];
|
|
68
|
+
if (arg === "--debug")
|
|
69
|
+
return true;
|
|
70
|
+
const eq = typeof arg === "string" ? arg.match(/^--debug=(.*)$/) : null;
|
|
71
|
+
if (eq)
|
|
72
|
+
return TRUTHY.test(eq[1].trim());
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
48
76
|
class FileLogger {
|
|
49
77
|
logFilePath;
|
|
50
78
|
logStream = null;
|
|
51
79
|
initialized = false;
|
|
52
80
|
writeCount = 0;
|
|
81
|
+
// File logging is opt-in — nothing touches disk unless this is true. Seeded
|
|
82
|
+
// from `--debug` / DOVETAIL_DEBUG so a stray log file is never created just by
|
|
83
|
+
// running a command; can also be flipped on at runtime via enable().
|
|
84
|
+
enabled;
|
|
53
85
|
constructor() {
|
|
54
86
|
// Initialize on first use
|
|
55
87
|
this.logFilePath = "";
|
|
88
|
+
this.enabled = shouldEnableFileLogging(Array.isArray(process.argv) ? process.argv.slice(2) : [], process.env);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Turn file logging on for this process (programmatic opt-in). Idempotent.
|
|
92
|
+
*/
|
|
93
|
+
enable() {
|
|
94
|
+
this.enabled = true;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Whether file logging is currently active.
|
|
98
|
+
*/
|
|
99
|
+
isEnabled() {
|
|
100
|
+
return this.enabled;
|
|
56
101
|
}
|
|
57
102
|
/**
|
|
58
103
|
* Initialize the file logger in the current working directory
|
|
@@ -109,6 +154,11 @@ class FileLogger {
|
|
|
109
154
|
* Write a message to the log file
|
|
110
155
|
*/
|
|
111
156
|
writeToFile(message) {
|
|
157
|
+
// Opt-in gate: without --debug / DOVETAIL_DEBUG we never create or write a
|
|
158
|
+
// log file. Console output (info/warn/error/success) still happens because
|
|
159
|
+
// those methods log to the console before calling writeToFile.
|
|
160
|
+
if (!this.enabled)
|
|
161
|
+
return;
|
|
112
162
|
if (!this.initialized) {
|
|
113
163
|
this.initialize();
|
|
114
164
|
}
|
|
@@ -215,6 +265,8 @@ const fileLogger = new FileLogger();
|
|
|
215
265
|
exports.fileLogger = fileLogger;
|
|
216
266
|
// Also export a function to replace console.log globally
|
|
217
267
|
function enableFileLogging() {
|
|
268
|
+
// Opt in to file logging even if neither --debug nor DOVETAIL_DEBUG was set.
|
|
269
|
+
fileLogger.enable();
|
|
218
270
|
// Store original console.log
|
|
219
271
|
const originalConsoleLog = console.log;
|
|
220
272
|
// Override console.log to also write to file
|
package/dist/commander.js
CHANGED
|
@@ -31,6 +31,12 @@ async function initCommands() {
|
|
|
31
31
|
describe: "Path to a .env file to load for this command (default: .env in the project root). Lets one checkout target multiple instances.",
|
|
32
32
|
})
|
|
33
33
|
.global("env")
|
|
34
|
+
.option("debug", {
|
|
35
|
+
type: "boolean",
|
|
36
|
+
default: false,
|
|
37
|
+
describe: "Write a dovetail-debug-*.log file for this command (off by default; also enabled by DOVETAIL_DEBUG=1).",
|
|
38
|
+
})
|
|
39
|
+
.global("debug")
|
|
34
40
|
.command(["watch", "w", "watchAllScopes"], "Watch all scopes for file changes and sync to ServiceNow", (cmdArgs) => {
|
|
35
41
|
cmdArgs.options({
|
|
36
42
|
...sharedOptions,
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Regression: `dove` used to drop a `dovetail-debug-*.log` file into the cwd on
|
|
4
|
+
* EVERY invocation, because the file logger lazily created its file on the first
|
|
5
|
+
* write and `index.ts` logs "Starting Dovetail..." (plus scattered debug traces)
|
|
6
|
+
* on every command. The file logger is now opt-in — it only touches disk when
|
|
7
|
+
* the user asks for it via `--debug` or `DOVETAIL_DEBUG`.
|
|
8
|
+
*
|
|
9
|
+
* These tests cover:
|
|
10
|
+
* - the pure decision helper (`shouldEnableFileLogging`), and
|
|
11
|
+
* - the singleton's disk behavior: writes no file by default, writes once
|
|
12
|
+
* enabled, while console output is unaffected in both states.
|
|
13
|
+
*/
|
|
14
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
17
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
18
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
19
|
+
}
|
|
20
|
+
Object.defineProperty(o, k2, desc);
|
|
21
|
+
}) : (function(o, m, k, k2) {
|
|
22
|
+
if (k2 === undefined) k2 = k;
|
|
23
|
+
o[k2] = m[k];
|
|
24
|
+
}));
|
|
25
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
26
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
27
|
+
}) : function(o, v) {
|
|
28
|
+
o["default"] = v;
|
|
29
|
+
});
|
|
30
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
31
|
+
var ownKeys = function(o) {
|
|
32
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
33
|
+
var ar = [];
|
|
34
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
35
|
+
return ar;
|
|
36
|
+
};
|
|
37
|
+
return ownKeys(o);
|
|
38
|
+
};
|
|
39
|
+
return function (mod) {
|
|
40
|
+
if (mod && mod.__esModule) return mod;
|
|
41
|
+
var result = {};
|
|
42
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
43
|
+
__setModuleDefault(result, mod);
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
})();
|
|
47
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const path = __importStar(require("path"));
|
|
51
|
+
const FileLogger_1 = require("../FileLogger");
|
|
52
|
+
function countDebugLogs(dir) {
|
|
53
|
+
return fs.readdirSync(dir).filter((f) => /^dovetail-debug-.*\.log$/.test(f))
|
|
54
|
+
.length;
|
|
55
|
+
}
|
|
56
|
+
// The file logger opens its stream asynchronously; poll until a predicate holds.
|
|
57
|
+
async function waitFor(predicate, timeoutMs = 2000) {
|
|
58
|
+
const start = Date.now();
|
|
59
|
+
while (!predicate()) {
|
|
60
|
+
if (Date.now() - start > timeoutMs) {
|
|
61
|
+
throw new Error("waitFor: condition not met before timeout");
|
|
62
|
+
}
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
describe("shouldEnableFileLogging (pure)", () => {
|
|
67
|
+
it("is OFF by default — no flag, no env", () => {
|
|
68
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)([], {})).toBe(false);
|
|
69
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["push", "--diff", "main"], {})).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
it("turns ON with the --debug flag", () => {
|
|
72
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["push", "--debug"], {})).toBe(true);
|
|
73
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["--debug", "status"], {})).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("honors --debug=<value>", () => {
|
|
76
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["--debug=true"], {})).toBe(true);
|
|
77
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["--debug=1"], {})).toBe(true);
|
|
78
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["--debug=false"], {})).toBe(false);
|
|
79
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(["--debug=0"], {})).toBe(false);
|
|
80
|
+
});
|
|
81
|
+
it("turns ON with a truthy DOVETAIL_DEBUG env var", () => {
|
|
82
|
+
for (const v of ["1", "true", "TRUE", "yes", "on", " on "]) {
|
|
83
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)([], { DOVETAIL_DEBUG: v })).toBe(true);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
it("stays OFF for a falsy/empty DOVETAIL_DEBUG env var", () => {
|
|
87
|
+
for (const v of ["", "0", "false", "no", "off"]) {
|
|
88
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)([], { DOVETAIL_DEBUG: v })).toBe(false);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
it("tolerates non-array argv defensively", () => {
|
|
92
|
+
expect((0, FileLogger_1.shouldEnableFileLogging)(undefined, {})).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
describe("fileLogger disk gate", () => {
|
|
96
|
+
let tmpDir;
|
|
97
|
+
let cwdSpy;
|
|
98
|
+
let logSpy;
|
|
99
|
+
let errSpy;
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "dovetail-log-gate-"));
|
|
102
|
+
cwdSpy = jest.spyOn(process, "cwd").mockReturnValue(tmpDir);
|
|
103
|
+
logSpy = jest.spyOn(console, "log").mockImplementation(() => undefined);
|
|
104
|
+
errSpy = jest.spyOn(console, "error").mockImplementation(() => undefined);
|
|
105
|
+
});
|
|
106
|
+
afterEach(async () => {
|
|
107
|
+
FileLogger_1.fileLogger.close();
|
|
108
|
+
// Let any in-flight stream close settle before removing the directory.
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
110
|
+
cwdSpy.mockRestore();
|
|
111
|
+
logSpy.mockRestore();
|
|
112
|
+
errSpy.mockRestore();
|
|
113
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
114
|
+
});
|
|
115
|
+
it("writes NO log file when disabled (jest runs with no --debug / env)", () => {
|
|
116
|
+
expect(FileLogger_1.fileLogger.isEnabled()).toBe(false);
|
|
117
|
+
FileLogger_1.fileLogger.debug("should not create a file");
|
|
118
|
+
FileLogger_1.fileLogger.info("informational");
|
|
119
|
+
FileLogger_1.fileLogger.warn("warning");
|
|
120
|
+
FileLogger_1.fileLogger.error("boom");
|
|
121
|
+
expect(countDebugLogs(tmpDir)).toBe(0);
|
|
122
|
+
expect(FileLogger_1.fileLogger.getLogFilePath()).toBe("");
|
|
123
|
+
});
|
|
124
|
+
it("still prints info to the console when disabled", () => {
|
|
125
|
+
FileLogger_1.fileLogger.info("visible to the user");
|
|
126
|
+
expect(logSpy).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
it("creates and writes a log file once enabled", async () => {
|
|
129
|
+
FileLogger_1.fileLogger.enable();
|
|
130
|
+
expect(FileLogger_1.fileLogger.isEnabled()).toBe(true);
|
|
131
|
+
FileLogger_1.fileLogger.debug("now this lands on disk");
|
|
132
|
+
// Path is assigned synchronously in initialize(); the stream opens async.
|
|
133
|
+
const logPath = FileLogger_1.fileLogger.getLogFilePath();
|
|
134
|
+
expect(logPath.startsWith(tmpDir)).toBe(true);
|
|
135
|
+
expect(/dovetail-debug-.*\.log$/.test(path.basename(logPath))).toBe(true);
|
|
136
|
+
await waitFor(() => fs.existsSync(logPath) &&
|
|
137
|
+
fs.readFileSync(logPath, "utf8").includes("now this lands on disk"));
|
|
138
|
+
expect(countDebugLogs(tmpDir)).toBe(1);
|
|
139
|
+
});
|
|
140
|
+
});
|