@rstackjs/test-utils 0.1.0 → 0.2.0

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 CHANGED
@@ -25,6 +25,28 @@ bun add @rstackjs/test-utils -D
25
25
 
26
26
  ## Usage
27
27
 
28
+ ### copyNodeModules
29
+
30
+ Replaces `node_modules` with a copy of `_node_modules` in the given directory. It uses the current working directory by default.
31
+
32
+ ```ts
33
+ import { copyNodeModules } from '@rstackjs/test-utils';
34
+
35
+ const nodeModulesPath = await copyNodeModules();
36
+ ```
37
+
38
+ ### editFile
39
+
40
+ Edits a file using a sync or async editor function.
41
+
42
+ ```ts
43
+ import { editFile } from '@rstackjs/test-utils';
44
+
45
+ await editFile('src/index.ts', (content) =>
46
+ content.replace('const value = 1', 'const value = 2'),
47
+ );
48
+ ```
49
+
28
50
  ### findFile
29
51
 
30
52
  Finds the first matching path in a file-content map. String matchers use suffix matching, and content hashes are ignored by default.
@@ -119,9 +141,31 @@ try {
119
141
  console.log(logHelper.logs);
120
142
  ```
121
143
 
144
+ ### normalizeEol
145
+
146
+ Normalizes CRLF line endings to LF.
147
+
148
+ ```ts
149
+ import { normalizeEol } from '@rstackjs/test-utils';
150
+
151
+ const content = normalizeEol('first line\r\nsecond line\r\n');
152
+ // first line\nsecond line\n
153
+ ```
154
+
155
+ ### prepareDist
156
+
157
+ Removes a dist directory and returns its absolute path. It removes `dist` from the current working directory by default.
158
+
159
+ ```ts
160
+ import { prepareDist } from '@rstackjs/test-utils';
161
+
162
+ const distPath = await prepareDist();
163
+ const customDistPath = await prepareDist('dist-custom');
164
+ ```
165
+
122
166
  ### readDirContents
123
167
 
124
- Recursively reads UTF-8 files from a directory and returns a file-content map keyed by sorted absolute paths.
168
+ Recursively reads UTF-8 files from a directory and returns a file-content map keyed by sorted POSIX absolute paths.
125
169
 
126
170
  ```ts
127
171
  import { readDirContents } from '@rstackjs/test-utils';
@@ -159,6 +203,28 @@ await waitFor(() => server.isReady(), {
159
203
 
160
204
  Condition polling uses a 100ms interval and a 5-second timeout by default.
161
205
 
206
+ ### waitForFile
207
+
208
+ Waits until a file path exists.
209
+
210
+ ```ts
211
+ import { waitForFile } from '@rstackjs/test-utils';
212
+
213
+ await waitForFile('dist/index.js');
214
+ ```
215
+
216
+ ### waitForFileContent
217
+
218
+ Waits until a UTF-8 file includes the expected content.
219
+
220
+ ```ts
221
+ import { waitForFileContent } from '@rstackjs/test-utils';
222
+
223
+ await waitForFileContent('dist/index.js', 'compiled successfully');
224
+ ```
225
+
226
+ Both helpers accept the same `interval` and `timeout` options as `waitFor`.
227
+
162
228
  ## License
163
229
 
164
230
  [MIT](./LICENSE).
@@ -0,0 +1,2 @@
1
+ /** Copy `_node_modules` to `node_modules` in a directory. */
2
+ export declare const copyNodeModules: (cwd?: string) => Promise<string>;
@@ -0,0 +1,3 @@
1
+ export type FileEditor = (content: string) => string | Promise<string>;
2
+ /** Edits a file using a sync or async editor function */
3
+ export declare const editFile: (filePath: string, editor: FileEditor) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -1,8 +1,14 @@
1
+ export { copyNodeModules } from './copyNodeModules.js';
2
+ export { editFile, type FileEditor } from './editFile.js';
1
3
  export { type FileMatcher, findFile, type FindFileOptions, } from './findFile.js';
2
4
  export { getDistFiles } from './getDistFiles.js';
3
5
  export { getFileContent } from './getFileContent.js';
4
6
  export { getRandomPort, isPortAvailable } from './getRandomPort.js';
7
+ export { normalizeEol } from './normalizeEol.js';
8
+ export { prepareDist } from './prepareDist.js';
5
9
  export { type ConsoleType, createLogHelper, type ExtendedLogHelper, type LogHelper, proxyConsole, type ProxyConsoleOptions, } from './proxyConsole.js';
6
10
  export { readDirContents } from './readDirContents.js';
7
11
  export { toPosixPath } from './toPosixPath.js';
8
12
  export { waitFor, type WaitForCondition, type WaitForOptions, } from './waitFor.js';
13
+ export { waitForFile } from './waitForFile.js';
14
+ export { waitForFileContent } from './waitForFileContent.js';
package/dist/index.js CHANGED
@@ -1,7 +1,25 @@
1
- import { readFile, readdir } from "node:fs/promises";
1
+ import { cp, readFile, readdir, rm, writeFile } from "node:fs/promises";
2
2
  import { join, resolve as external_node_path_resolve } from "node:path";
3
3
  import node_net from "node:net";
4
4
  import { stripVTControlCharacters, styleText } from "node:util";
5
+ import { existsSync } from "node:fs";
6
+ const copyNodeModules = async (cwd = process.cwd())=>{
7
+ const rootPath = external_node_path_resolve(cwd);
8
+ const sourcePath = join(rootPath, '_node_modules');
9
+ const targetPath = join(rootPath, 'node_modules');
10
+ await rm(targetPath, {
11
+ force: true,
12
+ recursive: true
13
+ });
14
+ await cp(sourcePath, targetPath, {
15
+ recursive: true
16
+ });
17
+ return targetPath;
18
+ };
19
+ const editFile = async (filePath, editor)=>{
20
+ const content = await readFile(filePath, 'utf-8');
21
+ await writeFile(filePath, await editor(content));
22
+ };
5
23
  const HASH_PATTERN = /\.[0-9a-z]{8,}(?=\.)/gi;
6
24
  const toMatcher = (matcher)=>{
7
25
  if ('function' == typeof matcher) return matcher;
@@ -21,6 +39,7 @@ const findFile = (files, matcher, options = {})=>{
21
39
  }
22
40
  throw new Error(`Unable to find file matching "${matcher.toString()}"`);
23
41
  };
42
+ const toPosixPath = (filePath)=>filePath.replaceAll('\\', '/');
24
43
  const collectFilePaths = async (directoryPath)=>{
25
44
  const entries = await readdir(directoryPath, {
26
45
  withFileTypes: true
@@ -38,7 +57,7 @@ const readDirContents = async (directoryPath)=>{
38
57
  const filePaths = await collectFilePaths(external_node_path_resolve(directoryPath));
39
58
  filePaths.sort();
40
59
  const entries = await Promise.all(filePaths.map(async (filePath)=>[
41
- filePath,
60
+ toPosixPath(filePath),
42
61
  await readFile(filePath, 'utf-8')
43
62
  ]));
44
63
  return Object.fromEntries(entries);
@@ -78,7 +97,15 @@ const getRandomPort = async (startPort = Math.ceil(30000 * Math.random()) + 1500
78
97
  }
79
98
  throw new Error('No available ports found in the valid range.');
80
99
  };
81
- const toPosixPath = (filePath)=>filePath.replaceAll('\\', '/');
100
+ const normalizeEol = (content)=>content.replaceAll('\r\n', '\n');
101
+ const prepareDist = async (distPath = 'dist')=>{
102
+ const absolutePath = external_node_path_resolve(distPath);
103
+ await rm(absolutePath, {
104
+ force: true,
105
+ recursive: true
106
+ });
107
+ return absolutePath;
108
+ };
82
109
  const matchPattern = (log, pattern, options = {})=>{
83
110
  const logToCheck = options.posix ? toPosixPath(log) : log;
84
111
  if ('string' == typeof pattern) return options.strict ? logToCheck.split('\n').some((line)=>line.trim() === pattern.trim()) : logToCheck.includes(pattern);
@@ -191,4 +218,17 @@ async function waitFor(millisecondsOrCondition, options = {}) {
191
218
  await delay(Math.min(interval, timeout - elapsedTime));
192
219
  }
193
220
  }
194
- export { createLogHelper, findFile, getDistFiles, getFileContent, getRandomPort, isPortAvailable, proxyConsole, readDirContents, toPosixPath, waitFor };
221
+ const waitForFile = async (filePath, options)=>{
222
+ await waitFor(()=>existsSync(filePath), options);
223
+ };
224
+ const waitForFileContent = async (filePath, expectedContent, options)=>{
225
+ await waitFor(async ()=>{
226
+ try {
227
+ const content = await readFile(filePath, 'utf-8');
228
+ return content.includes(expectedContent);
229
+ } catch {
230
+ return false;
231
+ }
232
+ }, options);
233
+ };
234
+ export { copyNodeModules, createLogHelper, editFile, findFile, getDistFiles, getFileContent, getRandomPort, isPortAvailable, normalizeEol, prepareDist, proxyConsole, readDirContents, toPosixPath, waitFor, waitForFile, waitForFileContent };
@@ -0,0 +1,2 @@
1
+ /** Normalize CRLF line endings to LF. */
2
+ export declare const normalizeEol: (content: string) => string;
@@ -0,0 +1,2 @@
1
+ /** Remove a dist directory and return its absolute path. */
2
+ export declare const prepareDist: (distPath?: string) => Promise<string>;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Recursively read UTF-8 files from a directory.
3
3
  *
4
- * The returned record uses sorted absolute file paths as keys.
4
+ * The returned record uses sorted POSIX absolute file paths as keys.
5
5
  */
6
6
  export declare const readDirContents: (directoryPath: string) => Promise<Record<string, string>>;
@@ -0,0 +1,3 @@
1
+ import { type WaitForOptions } from './waitFor.js';
2
+ /** Wait until a file path exists. */
3
+ export declare const waitForFile: (filePath: string, options?: WaitForOptions) => Promise<void>;
@@ -0,0 +1,3 @@
1
+ import { type WaitForOptions } from './waitFor.js';
2
+ /** Wait until a UTF-8 file includes the expected content. */
3
+ export declare const waitForFileContent: (filePath: string, expectedContent: string, options?: WaitForOptions) => Promise<void>;
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@rstackjs/test-utils",
3
- "version": "0.1.0",
4
- "repository": "https://github.com/rstackjs/test-utils",
3
+ "version": "0.2.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/rstackjs/test-utils"
7
+ },
5
8
  "license": "MIT",
6
9
  "type": "module",
7
10
  "exports": {
@@ -14,23 +17,22 @@
14
17
  "files": [
15
18
  "dist"
16
19
  ],
17
- "scripts": {
18
- "build": "rs lib",
19
- "dev": "rs lib -w",
20
- "lint": "rs lint && prettier -c .",
21
- "lint:write": "rs lint --fix && prettier -w .",
22
- "test": "rs test",
23
- "bump": "pnpx bumpp"
24
- },
25
20
  "devDependencies": {
26
21
  "@types/node": "^24.13.2",
27
22
  "prettier": "^3.9.4",
28
23
  "rstack": "0.0.2",
29
24
  "typescript": "^7.0.2"
30
25
  },
31
- "packageManager": "pnpm@11.9.0",
32
26
  "publishConfig": {
33
27
  "access": "public",
34
28
  "registry": "https://registry.npmjs.org/"
29
+ },
30
+ "scripts": {
31
+ "build": "rs lib",
32
+ "dev": "rs lib -w",
33
+ "lint": "rs lint && prettier -c .",
34
+ "lint:write": "rs lint --fix && prettier -w .",
35
+ "test": "rs test",
36
+ "bump": "pnpx bumpp"
35
37
  }
36
- }
38
+ }