@stonyx/logs 1.0.1-alpha.16 → 1.0.1-alpha.18
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 +152 -7
- package/dist/index.d.ts +3 -2
- package/dist/index.js +83 -19
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,12 @@ const log = new Log();
|
|
|
57
57
|
|
|
58
58
|
log.info('Info: sample application has started');
|
|
59
59
|
log.warn('Warning: this is just a sample');
|
|
60
|
-
|
|
60
|
+
|
|
61
|
+
// Writing to a file can fail, and the returned promise is the only signal that it did.
|
|
62
|
+
// Always handle it -- an unhandled rejection terminates the process.
|
|
63
|
+
// See "Handling write failures" below.
|
|
64
|
+
log.error('Error: no application logic detected', true) // logs to logs/error.log file
|
|
65
|
+
.catch(err => { /* err.code is the underlying fs error code, e.g. 'EACCES' */ });
|
|
61
66
|
```
|
|
62
67
|
|
|
63
68
|
Easily define your own logging mechanism and color-coding preference:
|
|
@@ -91,8 +96,14 @@ const log = new Log({
|
|
|
91
96
|
suffix: '\n=============================================================== \n',
|
|
92
97
|
});
|
|
93
98
|
|
|
94
|
-
log.info('Info: sample application has started')
|
|
99
|
+
log.info('Info: sample application has started')
|
|
100
|
+
.catch(err => { /* see "Handling write failures" */ });
|
|
95
101
|
```
|
|
102
|
+
|
|
103
|
+
> **Caveat:** with `logToFileByDefault: true`, *every* log call becomes a file write, and therefore
|
|
104
|
+
> every log call returns a promise that can reject. A failing log directory turns an unhandled
|
|
105
|
+
> `log.info()` into a process-terminating rejection. See [handling write failures](#handling-write-failures).
|
|
106
|
+
|
|
96
107
|

|
|
97
108
|
|
|
98
109
|
|
|
@@ -107,6 +118,7 @@ const log = new Log({ additionalLogs: { question: 'green' } });
|
|
|
107
118
|
log.defineType('query', log.chalk().black.bgGreen);
|
|
108
119
|
|
|
109
120
|
log.question('What will a fully custom chalk color function look like?');
|
|
121
|
+
// second argument writes to file, so this can reject -- see "Handling write failures"
|
|
110
122
|
await log.query('This is what a custom chalk color setting looks like', true);
|
|
111
123
|
```
|
|
112
124
|

|
|
@@ -153,16 +165,27 @@ These methods can then be called in your application with [logging parameters](#
|
|
|
153
165
|
|
|
154
166
|
Color settings are handled by determining whether your input is a color name or a hex value (prefixed with **#**). For example, passing `red` as a color setting will utilize `chalk.red`, while passing `#ff0000` would use `chalk.hex('#ff0000')` instead. A [list of available colors](https://github.com/chalk/chalk#colors) can be found in chalks' documentation.
|
|
155
167
|
|
|
156
|
-
Additionally, these methods return a promise when `logToFile` is true, allowing you use them with `await` in an async method, or append `then()
|
|
168
|
+
Additionally, these methods return a promise when `logToFile` is true, allowing you to use them with `await` in an async method, or to append `then()`, `catch()` or `finally()`.
|
|
169
|
+
|
|
170
|
+
When `logToFile` is true that promise **can reject**, and handling the rejection is **required**, not
|
|
171
|
+
optional: the rejection is the only signal that the write failed, and an unhandled rejection
|
|
172
|
+
terminates your process under Node's default settings. Wrap the call in `try`/`catch` (or attach
|
|
173
|
+
`.catch()`) for every call that writes to a file.
|
|
157
174
|
|
|
158
175
|
```js
|
|
159
176
|
async method() {
|
|
160
|
-
|
|
177
|
+
try {
|
|
178
|
+
await log.error('error message', true);
|
|
161
179
|
|
|
162
|
-
|
|
180
|
+
// do something after logs/error.log (default) is created
|
|
181
|
+
} catch (err) {
|
|
182
|
+
// err.code is the underlying fs error code, e.g. 'EACCES'
|
|
183
|
+
}
|
|
163
184
|
}
|
|
164
185
|
```
|
|
165
186
|
|
|
187
|
+
See [handling write failures](#handling-write-failures) for the full contract.
|
|
188
|
+
|
|
166
189
|
### The Debug Method
|
|
167
190
|
|
|
168
191
|
**Log** allows for the `log.debug()` method to be overridden by a color setting. However, by default we do not define a color for debug and debug is handled differently. For console logging, all **debug** does is output the following:
|
|
@@ -177,6 +200,10 @@ JSON.stringify(content, null, 2);
|
|
|
177
200
|
|
|
178
201
|
We believe that when wanting to output complicated objects or debug **typescript** applications, there are better methods than utilizing this **Log** package. But for anyone who's fully incorporated **Log** into their project, this function offers some convenience.
|
|
179
202
|
|
|
203
|
+
`debug(content, logToFile)` writes to file on exactly the same contract as every other log type:
|
|
204
|
+
`log.debug(content, true)` returns a promise that rejects with the underlying `fs` error when the
|
|
205
|
+
write fails, and that rejection must be handled. See [handling write failures](#handling-write-failures).
|
|
206
|
+
|
|
180
207
|
### Logging Parameters
|
|
181
208
|
|
|
182
209
|
```js
|
|
@@ -186,10 +213,128 @@ log.error('error message', true, false); // content, logToFile, overwrite
|
|
|
186
213
|
| Parameter | Type | Default | Description |
|
|
187
214
|
| :---: | :---: | :---: | :--- |
|
|
188
215
|
| `content` | **String** | | Content of log that will output on your console. |
|
|
189
|
-
| `logToFile` | **Boolean** | *false* | Option to log content to file. |
|
|
216
|
+
| `logToFile` | **Boolean** | *false* | Option to log content to file. When true, the call returns a promise that rejects if the write fails -- see [handling write failures](#handling-write-failures). |
|
|
190
217
|
| `overwrite` | **Boolean** | *false <br> (true on debug())* | Option to overwrite log file, rather than append to it. This option is redundant if logToFile is false. |
|
|
191
218
|
|
|
192
219
|
**logToFile** will log to *<project-root>/logs* unless [configured](#configuration) differently during instantiation. <br>
|
|
220
|
+
|
|
221
|
+
### Handling write failures
|
|
222
|
+
|
|
223
|
+
Any call that writes to a file returns a promise that **rejects when the write fails**. That covers
|
|
224
|
+
`log.error(content, true)`, `log.debug(content, true)`, and *every* log call when
|
|
225
|
+
[`logToFileByDefault`](#configuration) is `true`.
|
|
226
|
+
|
|
227
|
+
That rejection is the only actionable failure signal, so every file-writing call must be awaited
|
|
228
|
+
inside `try`/`catch`, or have `.catch()` attached.
|
|
229
|
+
|
|
230
|
+
> **A rejection is terminal. Do not retry it.**
|
|
231
|
+
>
|
|
232
|
+
> **Log** has already retried internally by the time a rejection reaches you (see
|
|
233
|
+
> [self-healing retry](#self-healing-retry) below). A rejection means that retry ran and failed too,
|
|
234
|
+
> so a consumer-side retry layer races the internal self-heal and only delays the disable or back-off
|
|
235
|
+
> that your error path exists to trigger.
|
|
236
|
+
>
|
|
237
|
+
> Treat the **first** rejection as the signal to disable file logging or back off. Do not attempt the
|
|
238
|
+
> write again.
|
|
239
|
+
|
|
240
|
+
#### What the promise rejects with
|
|
241
|
+
|
|
242
|
+
The rejection value is the underlying Node `fs` error - a `NodeJS.ErrnoException` passed through
|
|
243
|
+
unmodified, with `err.code`, `err.syscall`, `err.errno` and `err.path` all preserved.
|
|
244
|
+
|
|
245
|
+
```js
|
|
246
|
+
try {
|
|
247
|
+
await log.error('error message', true);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
// err.code is the underlying fs error code, e.g. 'EACCES'
|
|
250
|
+
// err.syscall is the failing call, e.g. 'mkdir' or 'open'
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Codes you are most likely to see are `EACCES` and `EPERM` (no permission to create the log directory
|
|
255
|
+
or write the log file), `ENOENT` (the directory disappeared and could not be recreated) and `EROFS`
|
|
256
|
+
(read-only filesystem) - but any error the filesystem raises reaches the caller as-is.
|
|
257
|
+
|
|
258
|
+
**An unhandled rejection terminates your process** under Node's default `--unhandled-rejections=throw`.
|
|
259
|
+
A fire-and-forget `log.error('...', true)` pointed at an unwritable log directory exits the process
|
|
260
|
+
with `Error: EACCES: permission denied, mkdir '...'`.
|
|
261
|
+
|
|
262
|
+
The recommended consumer shape is a latch that trips on the first rejection:
|
|
263
|
+
|
|
264
|
+
```js
|
|
265
|
+
let fileLoggingDisabled = false;
|
|
266
|
+
|
|
267
|
+
async function logError(message) {
|
|
268
|
+
// once file logging has failed, stay on the console
|
|
269
|
+
if (fileLoggingDisabled) return log.error(message);
|
|
270
|
+
|
|
271
|
+
try {
|
|
272
|
+
await log.error(message, true);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
// the first rejection is terminal - disable, do not retry
|
|
275
|
+
fileLoggingDisabled = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
#### Self-healing retry
|
|
281
|
+
|
|
282
|
+
A cached log directory can outlive the directory it describes - for example, something deletes
|
|
283
|
+
`logs/` while your process is running. To cover that, a failure whose `err.code` is `ENOENT`,
|
|
284
|
+
`EACCES`, `EPERM` or `EROFS` drops the cached directory entry and retries the write **exactly once**.
|
|
285
|
+
Any other code rejects on the first attempt, with no retry.
|
|
286
|
+
|
|
287
|
+
A successful retry is silent: the promise resolves normally and no notice is emitted. A rejection
|
|
288
|
+
therefore always means the single retry has already been spent, which is why consumers must not add
|
|
289
|
+
one of their own.
|
|
290
|
+
|
|
291
|
+
#### Structured stderr notices
|
|
292
|
+
|
|
293
|
+
Alongside the rejection, **Log** emits a machine-parseable notice to `stderr` so that operators have
|
|
294
|
+
something to key on. The write-failure path must never re-enter the logger itself, so these notices
|
|
295
|
+
bypass all formatting and configuration: they are written directly to `stderr` as one JSON object per
|
|
296
|
+
line (JSONL), regardless of your color, prefix, suffix or path settings.
|
|
297
|
+
|
|
298
|
+
These notices are **informational only**. They are not the failure signal - the promise rejection is.
|
|
299
|
+
Never leave a write unhandled on the assumption that stderr covers it.
|
|
300
|
+
|
|
301
|
+
| Field | Value |
|
|
302
|
+
| :--- | :--- |
|
|
303
|
+
| `ts` | ISO-8601 timestamp of the notice |
|
|
304
|
+
| `surface` | Always `"stonyx-logs"` |
|
|
305
|
+
| `sessionKey` | Always `null` |
|
|
306
|
+
| `project` | Always `null` |
|
|
307
|
+
| `severity` | `"error"` for a failure, `"warn"` for a recovery |
|
|
308
|
+
| `event` | `"log-write-failed"` or `"log-write-recovered"` |
|
|
309
|
+
| `payload` | Event-specific, see below |
|
|
310
|
+
| `schemaVersion` | Always `1` |
|
|
311
|
+
|
|
312
|
+
`log-write-failed` payload:
|
|
313
|
+
|
|
314
|
+
| Field | Description |
|
|
315
|
+
| :--- | :--- |
|
|
316
|
+
| `targetLog` | Resolved path of the log file that could not be written |
|
|
317
|
+
| `path` | Resolved log *directory*, with trailing separator - this is the dedupe key |
|
|
318
|
+
| `syscall` | The failing syscall, e.g. `"mkdir"` or `"open"` |
|
|
319
|
+
| `code` | The same `err.code` carried by the rejection |
|
|
320
|
+
| `cached` | Whether the directory was already cached when the write started |
|
|
321
|
+
| `suppressedCount` | Always `0` on a failure notice |
|
|
322
|
+
|
|
323
|
+
`log-write-recovered` carries `targetLog`, `path` and `suppressedCount` only.
|
|
324
|
+
|
|
325
|
+
**Dedupe:** notices are one per failure episode, keyed on the resolved directory. The first failure
|
|
326
|
+
for a directory emits `log-write-failed`; every subsequent failure for that same directory is silent.
|
|
327
|
+
The next successful write to it emits a single `log-write-recovered` carrying `suppressedCount` - the
|
|
328
|
+
number of notices that were suppressed, i.e. `N - 1` for an episode of `N` consecutive failures.
|
|
329
|
+
Every failure still rejects its own promise; only the notices are deduped.
|
|
330
|
+
|
|
331
|
+
Five consecutive failures followed by one success emit exactly these two records:
|
|
332
|
+
|
|
333
|
+
```jsonl
|
|
334
|
+
{"ts":"2026-08-29T23:08:50.343Z","surface":"stonyx-logs","sessionKey":null,"project":null,"severity":"error","event":"log-write-failed","payload":{"targetLog":"/private/tmp/my-app/logs/error.log","path":"/private/tmp/my-app/logs/","syscall":"mkdir","code":"EACCES","cached":false,"suppressedCount":0},"schemaVersion":1}
|
|
335
|
+
{"ts":"2026-08-29T23:08:50.344Z","surface":"stonyx-logs","sessionKey":null,"project":null,"severity":"warn","event":"log-write-recovered","payload":{"targetLog":"/private/tmp/my-app/logs/error.log","path":"/private/tmp/my-app/logs/","suppressedCount":4},"schemaVersion":1}
|
|
336
|
+
```
|
|
337
|
+
|
|
193
338
|
### Configuration
|
|
194
339
|
|
|
195
340
|
When instantiating **Log**, you can pass an object to customize your settings. Below is the default configuration:
|
|
@@ -213,7 +358,7 @@ const log = new Log({
|
|
|
213
358
|
|
|
214
359
|
| Option | Type | Default | Description |
|
|
215
360
|
| :---: | :---: | :---: | :--- |
|
|
216
|
-
| `logToFileByDefault` | **Boolean** | *false* | Option to change default setting for `logToFile` parameter of logging functions. |
|
|
361
|
+
| `logToFileByDefault` | **Boolean** | *false* | Option to change default setting for `logToFile` parameter of logging functions. When true, every log call becomes a rejectable file write -- see [handling write failures](#handling-write-failures). |
|
|
217
362
|
| `logTimestamp` | **Boolean** | *false* | Option to include timestamp in console logging. Timestamps are automatically included in file logs. |
|
|
218
363
|
| `path` | **String** | *'logs/'* | Path in which to store log files. This setting is relative to your project's root directory. |
|
|
219
364
|
| `prefix` | **String** | *''* | Prefix string to prepend all log messages for all log types with the exception of *debug*. |
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Color, { type ColorSetting } from './color.js';
|
|
2
|
+
export type Severity = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
2
3
|
export interface LogOptions {
|
|
3
4
|
logToFileByDefault: boolean;
|
|
4
5
|
logTimestamp: boolean;
|
|
@@ -28,8 +29,8 @@ export default class Log {
|
|
|
28
29
|
log(content: string, type: string, logToFile: boolean, overwrite: boolean): Promise<void>;
|
|
29
30
|
debug(content: unknown, logToFile?: boolean, overwrite?: boolean): Promise<void>;
|
|
30
31
|
writeToFile(type: string, content: string, overwrite: boolean): Promise<void>;
|
|
31
|
-
noticeWriteResult(targetLog: string, payload: Record<string, unknown> | null): void;
|
|
32
|
-
emitNotice(severity:
|
|
32
|
+
noticeWriteResult(path: string, targetLog: string, payload: Record<string, unknown> | null): void;
|
|
33
|
+
emitNotice(severity: Severity, event: string, payload: Record<string, unknown>): void;
|
|
33
34
|
resolveFilename(template: string, type: string): string;
|
|
34
35
|
validateFileAndDirectory(path: string, targetLog: string): Promise<void>;
|
|
35
36
|
sanitizePath(path: string): string;
|
package/dist/index.js
CHANGED
|
@@ -25,7 +25,7 @@ export default class Log {
|
|
|
25
25
|
typeOptions = {};
|
|
26
26
|
// resolved directory -> in-flight or settled mkdir; instance-scoped so it cannot outlive its directory
|
|
27
27
|
directoryCache = new Map();
|
|
28
|
-
// resolved
|
|
28
|
+
// resolved directory -> consecutive write failures, used to dedupe the operator notice
|
|
29
29
|
writeFailures = new Map();
|
|
30
30
|
constructor(options = {}) {
|
|
31
31
|
const merged = {
|
|
@@ -93,7 +93,13 @@ export default class Log {
|
|
|
93
93
|
chalk() {
|
|
94
94
|
return this.color.getChalkInstance();
|
|
95
95
|
}
|
|
96
|
-
|
|
96
|
+
/*
|
|
97
|
+
* Logs to console, and conditionally to file.
|
|
98
|
+
*
|
|
99
|
+
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, so
|
|
100
|
+
* the caller receives the underlying `NodeJS.ErrnoException`. Callers must handle it: see the
|
|
101
|
+
* `writeToFile` contract below.
|
|
102
|
+
*/
|
|
97
103
|
async log(content, type, logToFile, overwrite) {
|
|
98
104
|
const logTimestamp = this.getOptionForType(type, 'logTimestamp');
|
|
99
105
|
const timestamp = `[${new Date().toLocaleString('en-US')}]`;
|
|
@@ -112,13 +118,35 @@ export default class Log {
|
|
|
112
118
|
return;
|
|
113
119
|
await this.writeToFile(type, `${timestamp} ${content}\n`, overwrite);
|
|
114
120
|
}
|
|
115
|
-
|
|
121
|
+
/*
|
|
122
|
+
* Direct hardcoded debug method (limited file logging).
|
|
123
|
+
*
|
|
124
|
+
* When `logToFile` is true this awaits `writeToFile` and propagates its rejection unchanged, on
|
|
125
|
+
* exactly the same contract as `log` - see `writeToFile` below.
|
|
126
|
+
*/
|
|
116
127
|
async debug(content, logToFile = false, overwrite = true) {
|
|
117
128
|
console.dir(content, { depth: 6 }); // eslint-disable-line no-console
|
|
118
129
|
if (!logToFile)
|
|
119
130
|
return;
|
|
120
131
|
await this.writeToFile('debug', JSON.stringify(content, null, 2), overwrite);
|
|
121
132
|
}
|
|
133
|
+
/*
|
|
134
|
+
* Writes `content` to the resolved target for `type`, creating the target's directory on first
|
|
135
|
+
* use for that directory.
|
|
136
|
+
*
|
|
137
|
+
* The returned promise rejecting with the underlying `NodeJS.ErrnoException` (with `err.code`
|
|
138
|
+
* preserved) is the sole failure signal - callers that care must handle it. The structured
|
|
139
|
+
* stderr notice emitted alongside a failure is informational only and is deduped per episode.
|
|
140
|
+
*
|
|
141
|
+
* On `ENOENT`, `EACCES`, `EPERM` or `EROFS` the cached directory entry is dropped and the write
|
|
142
|
+
* is retried exactly once; every other code rejects on the first attempt without a retry.
|
|
143
|
+
*
|
|
144
|
+
* That retry is why a rejection is terminal rather than transient: by the time one escapes, the
|
|
145
|
+
* self-heal has already run and failed. Consumers must not layer their own retry on top - doing
|
|
146
|
+
* so races the internal one and delays the disable/back-off the rejection exists to trigger. The
|
|
147
|
+
* correct response to the first rejection is to stop writing. See README "Handling write
|
|
148
|
+
* failures".
|
|
149
|
+
*/
|
|
122
150
|
async writeToFile(type, content, overwrite) {
|
|
123
151
|
const path = this.getOptionForType(type, 'path');
|
|
124
152
|
const filenameTemplate = this.getOptionForType(type, 'filename');
|
|
@@ -139,29 +167,63 @@ export default class Log {
|
|
|
139
167
|
}
|
|
140
168
|
catch (error) {
|
|
141
169
|
const { code, syscall } = error;
|
|
142
|
-
this.noticeWriteResult(
|
|
170
|
+
this.noticeWriteResult(path, targetLog, {
|
|
171
|
+
targetLog,
|
|
172
|
+
path,
|
|
173
|
+
syscall,
|
|
174
|
+
code,
|
|
175
|
+
cached,
|
|
176
|
+
});
|
|
143
177
|
throw error;
|
|
144
178
|
}
|
|
145
|
-
this.noticeWriteResult(targetLog, null);
|
|
179
|
+
this.noticeWriteResult(path, targetLog, null);
|
|
146
180
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
181
|
+
/*
|
|
182
|
+
* One structured stderr notice per failure episode: an emit on the first failure, silence while
|
|
183
|
+
* it keeps failing, one recovery emit on the next success. A null payload records a success.
|
|
184
|
+
*
|
|
185
|
+
* Keyed on the directory rather than the resolved target, matching `directoryCache`: every code
|
|
186
|
+
* in the retry allowlist is a directory-level condition, so keying on the target would strand an
|
|
187
|
+
* un-reaped entry - and silently drop its suppressed count - whenever a `{date}` template rotates
|
|
188
|
+
* away from a still-failing filename.
|
|
189
|
+
*/
|
|
190
|
+
noticeWriteResult(path, targetLog, payload) {
|
|
191
|
+
const failures = this.writeFailures.get(path) ?? 0;
|
|
151
192
|
if (payload) {
|
|
152
|
-
this.writeFailures.set(
|
|
153
|
-
if (!failures)
|
|
154
|
-
this.emitNotice('error', 'log-write-failed', {
|
|
193
|
+
this.writeFailures.set(path, failures + 1);
|
|
194
|
+
if (!failures) {
|
|
195
|
+
this.emitNotice('error', 'log-write-failed', {
|
|
196
|
+
...payload,
|
|
197
|
+
suppressedCount: 0,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
155
200
|
}
|
|
156
201
|
else if (failures) {
|
|
157
|
-
this.writeFailures.delete(
|
|
158
|
-
this.emitNotice('warn', 'log-write-recovered', {
|
|
202
|
+
this.writeFailures.delete(path);
|
|
203
|
+
this.emitNotice('warn', 'log-write-recovered', {
|
|
204
|
+
targetLog,
|
|
205
|
+
path,
|
|
206
|
+
suppressedCount: failures - 1,
|
|
207
|
+
});
|
|
159
208
|
}
|
|
160
209
|
}
|
|
161
|
-
|
|
210
|
+
/*
|
|
211
|
+
* The write-failure path must never re-enter this logger, so notices go straight to stderr as a
|
|
212
|
+
* single JSONL record. Field order is the canonical one mandated by the framework logging
|
|
213
|
+
* schema, with the optional `schemaVersion` last.
|
|
214
|
+
*/
|
|
162
215
|
emitNotice(severity, event, payload) {
|
|
163
216
|
const ts = new Date().toISOString();
|
|
164
|
-
console.error(JSON.stringify({
|
|
217
|
+
console.error(JSON.stringify({
|
|
218
|
+
ts,
|
|
219
|
+
surface: 'stonyx-logs',
|
|
220
|
+
sessionKey: null,
|
|
221
|
+
project: null,
|
|
222
|
+
severity,
|
|
223
|
+
event,
|
|
224
|
+
payload,
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
}));
|
|
165
227
|
}
|
|
166
228
|
// resolves template variables in a filename string
|
|
167
229
|
resolveFilename(template, type) {
|
|
@@ -184,9 +246,11 @@ export default class Log {
|
|
|
184
246
|
// sanitize: prevent path traversal and disallow directory separators
|
|
185
247
|
return resolved.replace(/\.\./g, '').replace(/[/\\]/g, '');
|
|
186
248
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
249
|
+
/*
|
|
250
|
+
* Ensures the target's directory exists. Both write paths auto-create the file itself, so no
|
|
251
|
+
* bootstrap write is needed. `targetLog` is unused but retained for signature compatibility:
|
|
252
|
+
* resolveFilename strips separators, so the only cached invariant is the directory.
|
|
253
|
+
*/
|
|
190
254
|
async validateFileAndDirectory(path, targetLog) {
|
|
191
255
|
let pending = this.directoryCache.get(path);
|
|
192
256
|
if (!pending) {
|