@gjsify/fs 0.4.0 → 0.4.4

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.
Files changed (50) hide show
  1. package/package.json +51 -48
  2. package/src/callback.spec.ts +0 -296
  3. package/src/callback.ts +0 -684
  4. package/src/cp.spec.ts +0 -181
  5. package/src/cp.ts +0 -328
  6. package/src/dir.spec.ts +0 -204
  7. package/src/dir.ts +0 -199
  8. package/src/dirent.ts +0 -165
  9. package/src/encoding.ts +0 -45
  10. package/src/errors.spec.ts +0 -389
  11. package/src/errors.ts +0 -19
  12. package/src/extended.spec.ts +0 -706
  13. package/src/fd-ops.spec.ts +0 -234
  14. package/src/fd-ops.ts +0 -251
  15. package/src/file-handle.spec.ts +0 -115
  16. package/src/file-handle.ts +0 -856
  17. package/src/fs-watcher.ts +0 -198
  18. package/src/glob.spec.ts +0 -201
  19. package/src/glob.ts +0 -205
  20. package/src/index.ts +0 -313
  21. package/src/new-apis.spec.ts +0 -505
  22. package/src/promises.spec.ts +0 -812
  23. package/src/promises.ts +0 -686
  24. package/src/read-stream.ts +0 -128
  25. package/src/stat-watcher.ts +0 -116
  26. package/src/stat.spec.ts +0 -87
  27. package/src/statfs.spec.ts +0 -67
  28. package/src/statfs.ts +0 -92
  29. package/src/stats.ts +0 -207
  30. package/src/streams.spec.ts +0 -513
  31. package/src/symlink.spec.ts +0 -188
  32. package/src/sync.spec.ts +0 -377
  33. package/src/sync.ts +0 -568
  34. package/src/test.mts +0 -27
  35. package/src/types/encoding-option.ts +0 -3
  36. package/src/types/file-read-options.ts +0 -15
  37. package/src/types/file-read-result.ts +0 -4
  38. package/src/types/flag-and-open-mode.ts +0 -6
  39. package/src/types/index.ts +0 -6
  40. package/src/types/open-flags.ts +0 -14
  41. package/src/types/read-options.ts +0 -9
  42. package/src/utils.ts +0 -31
  43. package/src/utimes.spec.ts +0 -113
  44. package/src/utimes.ts +0 -97
  45. package/src/watch.spec.ts +0 -171
  46. package/src/watchfile.spec.ts +0 -185
  47. package/src/write-stream.ts +0 -142
  48. package/test/file.txt +0 -1
  49. package/tsconfig.json +0 -29
  50. package/tsconfig.tsbuildinfo +0 -1
package/src/cp.spec.ts DELETED
@@ -1,181 +0,0 @@
1
- // Ported from refs/bun/test/js/node/fs/cp.test.ts and
2
- // refs/node-test/parallel/test-fs-cp-sync-*.mjs
3
- // Original: MIT, Oven & contributors / Node.js contributors.
4
- // Rewritten for @gjsify/unit — behavior preserved, assertion dialect adapted.
5
-
6
- import { describe, it, expect } from '@gjsify/unit';
7
- import { cpSync, promises, existsSync, mkdirSync, writeFileSync, readFileSync, mkdtempSync, rmSync } from 'node:fs';
8
- import { join } from 'node:path';
9
- import { tmpdir } from 'node:os';
10
-
11
- function makeTmp(): string {
12
- return mkdtempSync(join(tmpdir(), 'gjsify-cp-'));
13
- }
14
-
15
- export default async () => {
16
- await describe('fs.cpSync', async () => {
17
- await it('copies a single file', async () => {
18
- const tmp = makeTmp();
19
- writeFileSync(join(tmp, 'a.txt'), 'hello');
20
-
21
- cpSync(join(tmp, 'a.txt'), join(tmp, 'b.txt'));
22
-
23
- expect(readFileSync(join(tmp, 'b.txt'), 'utf8')).toBe('hello');
24
- rmSync(tmp, { recursive: true, force: true });
25
- });
26
-
27
- await it('throws EISDIR when src is directory and recursive is false', async () => {
28
- const tmp = makeTmp();
29
- mkdirSync(join(tmp, 'src'));
30
-
31
- let threw = false;
32
- try {
33
- cpSync(join(tmp, 'src'), join(tmp, 'dest'));
34
- } catch (e: any) {
35
- threw = true;
36
- expect(e.code).toBe('ERR_FS_EISDIR');
37
- }
38
- expect(threw).toBe(true);
39
- rmSync(tmp, { recursive: true, force: true });
40
- });
41
-
42
- await it('recursively copies a directory tree', async () => {
43
- const tmp = makeTmp();
44
- mkdirSync(join(tmp, 'src', 'sub'), { recursive: true });
45
- writeFileSync(join(tmp, 'src', 'a.txt'), 'a');
46
- writeFileSync(join(tmp, 'src', 'sub', 'b.txt'), 'b');
47
-
48
- cpSync(join(tmp, 'src'), join(tmp, 'dst'), { recursive: true });
49
-
50
- expect(readFileSync(join(tmp, 'dst', 'a.txt'), 'utf8')).toBe('a');
51
- expect(readFileSync(join(tmp, 'dst', 'sub', 'b.txt'), 'utf8')).toBe('b');
52
- rmSync(tmp, { recursive: true, force: true });
53
- });
54
-
55
- await it('overwrites existing file by default (force=true)', async () => {
56
- const tmp = makeTmp();
57
- writeFileSync(join(tmp, 'src.txt'), 'new');
58
- writeFileSync(join(tmp, 'dst.txt'), 'old');
59
-
60
- cpSync(join(tmp, 'src.txt'), join(tmp, 'dst.txt'));
61
-
62
- expect(readFileSync(join(tmp, 'dst.txt'), 'utf8')).toBe('new');
63
- rmSync(tmp, { recursive: true, force: true });
64
- });
65
-
66
- await it('does not overwrite when force=false', async () => {
67
- const tmp = makeTmp();
68
- writeFileSync(join(tmp, 'src.txt'), 'new');
69
- writeFileSync(join(tmp, 'dst.txt'), 'old');
70
-
71
- cpSync(join(tmp, 'src.txt'), join(tmp, 'dst.txt'), { force: false });
72
-
73
- expect(readFileSync(join(tmp, 'dst.txt'), 'utf8')).toBe('old');
74
- rmSync(tmp, { recursive: true, force: true });
75
- });
76
-
77
- await it('throws EEXIST when force=false and errorOnExist=true', async () => {
78
- const tmp = makeTmp();
79
- writeFileSync(join(tmp, 'src.txt'), 'new');
80
- writeFileSync(join(tmp, 'dst.txt'), 'old');
81
-
82
- let threw = false;
83
- try {
84
- cpSync(join(tmp, 'src.txt'), join(tmp, 'dst.txt'), { force: false, errorOnExist: true });
85
- } catch (e: any) {
86
- threw = true;
87
- expect(e.code).toBe('ERR_FS_CP_EEXIST');
88
- }
89
- expect(threw).toBe(true);
90
- rmSync(tmp, { recursive: true, force: true });
91
- });
92
-
93
- await it('applies filter function — skips excluded entries', async () => {
94
- const tmp = makeTmp();
95
- mkdirSync(join(tmp, 'src'));
96
- writeFileSync(join(tmp, 'src', 'keep.txt'), 'keep');
97
- writeFileSync(join(tmp, 'src', 'skip.log'), 'skip');
98
-
99
- cpSync(join(tmp, 'src'), join(tmp, 'dst'), {
100
- recursive: true,
101
- filter: (_src, _dst) => !_src.endsWith('.log'),
102
- });
103
-
104
- expect(existsSync(join(tmp, 'dst', 'keep.txt'))).toBe(true);
105
- expect(existsSync(join(tmp, 'dst', 'skip.log'))).toBe(false);
106
- rmSync(tmp, { recursive: true, force: true });
107
- });
108
-
109
- await it('throws ENOENT when src does not exist', async () => {
110
- const tmp = makeTmp();
111
-
112
- let threw = false;
113
- try {
114
- cpSync(join(tmp, 'nonexistent.txt'), join(tmp, 'dst.txt'));
115
- } catch (e: any) {
116
- threw = true;
117
- expect(e.code).toBe('ENOENT');
118
- }
119
- expect(threw).toBe(true);
120
- rmSync(tmp, { recursive: true, force: true });
121
- });
122
- });
123
-
124
- await describe('fs.promises.cp', async () => {
125
- await it('copies a single file', async () => {
126
- const tmp = makeTmp();
127
- writeFileSync(join(tmp, 'a.txt'), 'hello');
128
-
129
- await promises.cp(join(tmp, 'a.txt'), join(tmp, 'b.txt'));
130
-
131
- expect(readFileSync(join(tmp, 'b.txt'), 'utf8')).toBe('hello');
132
- rmSync(tmp, { recursive: true, force: true });
133
- });
134
-
135
- await it('throws EISDIR when src is directory and recursive is false', async () => {
136
- const tmp = makeTmp();
137
- mkdirSync(join(tmp, 'src'));
138
-
139
- let threw = false;
140
- try {
141
- await promises.cp(join(tmp, 'src'), join(tmp, 'dest'));
142
- } catch (e: any) {
143
- threw = true;
144
- expect(e.code).toBe('ERR_FS_EISDIR');
145
- }
146
- expect(threw).toBe(true);
147
- rmSync(tmp, { recursive: true, force: true });
148
- });
149
-
150
- await it('recursively copies a directory tree', async () => {
151
- const tmp = makeTmp();
152
- mkdirSync(join(tmp, 'src', 'sub'), { recursive: true });
153
- writeFileSync(join(tmp, 'src', 'a.txt'), 'a');
154
- writeFileSync(join(tmp, 'src', 'sub', 'b.txt'), 'b');
155
-
156
- await promises.cp(join(tmp, 'src'), join(tmp, 'dst'), { recursive: true });
157
-
158
- expect(readFileSync(join(tmp, 'dst', 'a.txt'), 'utf8')).toBe('a');
159
- expect(readFileSync(join(tmp, 'dst', 'sub', 'b.txt'), 'utf8')).toBe('b');
160
- rmSync(tmp, { recursive: true, force: true });
161
- });
162
-
163
- await it('applies async filter function', async () => {
164
- const tmp = makeTmp();
165
- mkdirSync(join(tmp, 'src'));
166
- writeFileSync(join(tmp, 'src', 'keep.ts'), 'ts');
167
- writeFileSync(join(tmp, 'src', 'skip.js'), 'js');
168
-
169
- await promises.cp(join(tmp, 'src'), join(tmp, 'dst'), {
170
- recursive: true,
171
- filter: async (_src, _dst) => {
172
- return !_src.endsWith('.js');
173
- },
174
- });
175
-
176
- expect(existsSync(join(tmp, 'dst', 'keep.ts'))).toBe(true);
177
- expect(existsSync(join(tmp, 'dst', 'skip.js'))).toBe(false);
178
- rmSync(tmp, { recursive: true, force: true });
179
- });
180
- });
181
- };
package/src/cp.ts DELETED
@@ -1,328 +0,0 @@
1
- // fs.cp / fs.cpSync / fs.promises.cp — recursive copy
2
- // Reference: Node.js lib/internal/fs/cpSync.js
3
- // Reimplemented for GJS using Gio.File synchronous operations
4
-
5
- import Gio from '@girs/gio-2.0';
6
- import { join } from 'node:path';
7
- import { normalizePath } from './utils.js';
8
- import { createNodeError } from './errors.js';
9
-
10
- import type { PathLike } from 'node:fs';
11
-
12
- export interface CpSyncOptions {
13
- dereference?: boolean;
14
- errorOnExist?: boolean;
15
- filter?: (src: string, dest: string) => boolean;
16
- force?: boolean;
17
- mode?: number;
18
- preserveTimestamps?: boolean;
19
- recursive?: boolean;
20
- verbatimSymlinks?: boolean;
21
- }
22
-
23
- export interface CpOptions extends Omit<CpSyncOptions, 'filter'> {
24
- filter?: (src: string, dest: string) => boolean | Promise<boolean>;
25
- }
26
-
27
- function makeEEXIST(destStr: string): NodeJS.ErrnoException {
28
- const e: NodeJS.ErrnoException = new Error(`ERR_FS_CP_EEXIST: file already exists, copyfile '${destStr}'`);
29
- e.code = 'ERR_FS_CP_EEXIST';
30
- e.syscall = 'copyfile';
31
- e.path = destStr;
32
- return e;
33
- }
34
-
35
- function makeEISDIR(srcStr: string): NodeJS.ErrnoException {
36
- const e: NodeJS.ErrnoException = new Error(`ERR_FS_EISDIR: illegal operation on a directory, copyfile '${srcStr}'`);
37
- e.code = 'ERR_FS_EISDIR';
38
- e.syscall = 'copyfile';
39
- e.path = srcStr;
40
- return e;
41
- }
42
-
43
- function makeENOTDIR(srcStr: string, destStr: string): NodeJS.ErrnoException {
44
- const e: NodeJS.ErrnoException = new Error(`ENOTDIR: not a directory, copyfile '${srcStr}' -> '${destStr}'`);
45
- e.code = 'ENOTDIR';
46
- e.syscall = 'copyfile';
47
- e.path = srcStr;
48
- (e as any).dest = destStr;
49
- return e;
50
- }
51
-
52
- function makeSYMLINKLOOP(srcStr: string, destStr: string): NodeJS.ErrnoException {
53
- const e: NodeJS.ErrnoException = new Error(`ELOOP: too many levels of symbolic links, copyfile '${srcStr}' -> '${destStr}'`);
54
- e.code = 'ELOOP';
55
- e.syscall = 'copyfile';
56
- e.path = srcStr;
57
- (e as any).dest = destStr;
58
- return e;
59
- }
60
-
61
- function queryCopyFlags(opts: CpSyncOptions | CpOptions): Gio.FileCopyFlags {
62
- const force = opts.force ?? true;
63
- let flags = Gio.FileCopyFlags.NONE;
64
- if (force) flags |= Gio.FileCopyFlags.OVERWRITE;
65
- if (opts.preserveTimestamps) flags |= Gio.FileCopyFlags.TARGET_DEFAULT_MODIFIED_TIME;
66
- if (!opts.dereference) flags |= Gio.FileCopyFlags.NOFOLLOW_SYMLINKS;
67
- return flags;
68
- }
69
-
70
- function copyOneSyncFile(srcFile: Gio.File, destFile: Gio.File, srcStr: string, destStr: string, opts: CpSyncOptions | CpOptions): void {
71
- const force = opts.force ?? true;
72
-
73
- // Check dest is not a directory
74
- try {
75
- const destInfo = destFile.query_info('standard::type', Gio.FileQueryInfoFlags.NONE, null);
76
- if (destInfo.get_file_type() === Gio.FileType.DIRECTORY) {
77
- throw makeENOTDIR(srcStr, destStr);
78
- }
79
- if (!force) {
80
- if (opts.errorOnExist) throw makeEEXIST(destStr);
81
- return; // silent skip when force=false and no errorOnExist
82
- }
83
- } catch (e: any) {
84
- if (typeof e.code === 'string') throw e; // re-throw Node-style errors (string codes)
85
- // Gio errors have numeric codes (e.g. NOT_FOUND=1) — dest doesn't exist, fine
86
- }
87
-
88
- const flags = queryCopyFlags(opts);
89
- try {
90
- srcFile.copy(destFile, flags, null, null);
91
- } catch (err: unknown) {
92
- throw createNodeError(err, 'copyfile', srcStr, destStr);
93
- }
94
- }
95
-
96
- function cpOneDirSync(srcFile: Gio.File, destFile: Gio.File, srcStr: string, destStr: string, opts: CpSyncOptions | CpOptions): void {
97
- // Detect src ⊂ dest cycle (if dest path starts with srcStr + separator)
98
- const sep = srcStr.endsWith('/') ? '' : '/';
99
- if (destStr.startsWith(srcStr + sep) && destStr !== srcStr) {
100
- throw makeSYMLINKLOOP(srcStr, destStr);
101
- }
102
-
103
- // Create dest directory if it doesn't exist
104
- if (!destFile.query_exists(null)) {
105
- try {
106
- destFile.make_directory_with_parents(null);
107
- } catch (err: unknown) {
108
- throw createNodeError(err, 'mkdir', destStr);
109
- }
110
- } else {
111
- // dest exists — must be a directory
112
- try {
113
- const destInfo = destFile.query_info('standard::type', Gio.FileQueryInfoFlags.NONE, null);
114
- if (destInfo.get_file_type() !== Gio.FileType.DIRECTORY) {
115
- throw makeENOTDIR(destStr, srcStr);
116
- }
117
- } catch (e: any) {
118
- if (typeof e.code === 'string') throw e;
119
- }
120
- }
121
-
122
- // Enumerate children
123
- let enumerator: Gio.FileEnumerator;
124
- try {
125
- enumerator = srcFile.enumerate_children(
126
- 'standard::name,standard::type,standard::is-symlink',
127
- opts.dereference ? Gio.FileQueryInfoFlags.NONE : Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
128
- null,
129
- );
130
- } catch (err: unknown) {
131
- throw createNodeError(err, 'scandir', srcStr);
132
- }
133
-
134
- let childInfo = enumerator.next_file(null);
135
- while (childInfo !== null) {
136
- const name = childInfo.get_name();
137
- const childSrc = join(srcStr, name);
138
- const childDest = join(destStr, name);
139
-
140
- const filter = (opts as CpSyncOptions).filter;
141
- if (filter && !filter(childSrc, childDest)) {
142
- childInfo = enumerator.next_file(null);
143
- continue;
144
- }
145
-
146
- cpSyncInternal(childSrc, childDest, opts);
147
- childInfo = enumerator.next_file(null);
148
- }
149
- }
150
-
151
- function cpSyncInternal(srcStr: string, destStr: string, opts: CpSyncOptions | CpOptions): void {
152
- const srcFile = Gio.File.new_for_path(srcStr);
153
- const destFile = Gio.File.new_for_path(destStr);
154
-
155
- let info: Gio.FileInfo;
156
- try {
157
- info = srcFile.query_info(
158
- 'standard::type,standard::is-symlink',
159
- opts.dereference ? Gio.FileQueryInfoFlags.NONE : Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
160
- null,
161
- );
162
- } catch (err: unknown) {
163
- throw createNodeError(err, 'stat', srcStr);
164
- }
165
-
166
- const type = info.get_file_type();
167
-
168
- if (type === Gio.FileType.DIRECTORY) {
169
- if (!opts.recursive) throw makeEISDIR(srcStr);
170
- cpOneDirSync(srcFile, destFile, srcStr, destStr, opts);
171
- } else {
172
- copyOneSyncFile(srcFile, destFile, srcStr, destStr, opts);
173
- }
174
- }
175
-
176
- // ─── Public API ──────────────────────────────────────────────────────────────
177
-
178
- export function cpSync(src: PathLike, dest: PathLike, options?: CpSyncOptions): void {
179
- const srcStr = normalizePath(src);
180
- const destStr = normalizePath(dest);
181
- const opts: CpSyncOptions = options ?? {};
182
-
183
- const srcFile = Gio.File.new_for_path(srcStr);
184
-
185
- // Apply top-level filter before doing anything
186
- const filter = opts.filter;
187
- if (filter && !filter(srcStr, destStr)) return;
188
-
189
- // Check src is a directory without recursive option
190
- try {
191
- const info = srcFile.query_info(
192
- 'standard::type',
193
- opts.dereference ? Gio.FileQueryInfoFlags.NONE : Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
194
- null,
195
- );
196
- if (info.get_file_type() === Gio.FileType.DIRECTORY && !opts.recursive) {
197
- throw makeEISDIR(srcStr);
198
- }
199
- } catch (e: any) {
200
- if (typeof e.code === 'string') throw e;
201
- throw createNodeError(e, 'stat', srcStr);
202
- }
203
-
204
- cpSyncInternal(srcStr, destStr, opts);
205
- }
206
-
207
- export function cp(
208
- src: PathLike,
209
- dest: PathLike,
210
- options: CpOptions | ((err: NodeJS.ErrnoException | null) => void),
211
- callback?: (err: NodeJS.ErrnoException | null) => void,
212
- ): void {
213
- let opts: CpOptions;
214
- let cb: (err: NodeJS.ErrnoException | null) => void;
215
-
216
- if (typeof options === 'function') {
217
- cb = options;
218
- opts = {};
219
- } else {
220
- cb = callback!;
221
- opts = options;
222
- }
223
-
224
- const asyncFilter = opts.filter;
225
- if (asyncFilter) {
226
- // Wrap async filter through a sync-compatible adapter by running
227
- // a mini async pipeline: resolve filter for each entry, then cpSync.
228
- // For simplicity, resolve the top-level filter and then run cpSync
229
- // with a synchronous shim. Full async recursive filtering uses the
230
- // promise variant.
231
- Promise.resolve(asyncFilter(normalizePath(src), normalizePath(dest))).then((include) => {
232
- if (!include) { cb(null); return; }
233
- try {
234
- // For directories, we must run asynchronously through promises.cp
235
- cpPromises(src, dest, opts).then(() => cb(null)).catch(cb);
236
- } catch (e: any) {
237
- cb(e);
238
- }
239
- }).catch(cb);
240
- return;
241
- }
242
-
243
- // No async filter — use synchronous implementation on next tick
244
- Promise.resolve().then(() => {
245
- try {
246
- cpSync(src, dest, opts as CpSyncOptions);
247
- cb(null);
248
- } catch (e: any) {
249
- cb(e);
250
- }
251
- });
252
- }
253
-
254
- // ─── promises.cp ─────────────────────────────────────────────────────────────
255
-
256
- async function cpPromisesDir(srcStr: string, destStr: string, opts: CpOptions): Promise<void> {
257
- const sep = srcStr.endsWith('/') ? '' : '/';
258
- if (destStr.startsWith(srcStr + sep) && destStr !== srcStr) {
259
- throw makeSYMLINKLOOP(srcStr, destStr);
260
- }
261
-
262
- const destFile = Gio.File.new_for_path(destStr);
263
- if (!destFile.query_exists(null)) {
264
- try {
265
- destFile.make_directory_with_parents(null);
266
- } catch (err: unknown) {
267
- throw createNodeError(err, 'mkdir', destStr);
268
- }
269
- }
270
-
271
- const srcFile = Gio.File.new_for_path(srcStr);
272
- let enumerator: Gio.FileEnumerator;
273
- try {
274
- enumerator = srcFile.enumerate_children(
275
- 'standard::name,standard::type',
276
- opts.dereference ? Gio.FileQueryInfoFlags.NONE : Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
277
- null,
278
- );
279
- } catch (err: unknown) {
280
- throw createNodeError(err, 'scandir', srcStr);
281
- }
282
-
283
- let childInfo = enumerator.next_file(null);
284
- while (childInfo !== null) {
285
- const name = childInfo.get_name();
286
- const childSrc = join(srcStr, name);
287
- const childDest = join(destStr, name);
288
-
289
- const filter = opts.filter;
290
- if (filter) {
291
- const include = await Promise.resolve(filter(childSrc, childDest));
292
- if (!include) {
293
- childInfo = enumerator.next_file(null);
294
- continue;
295
- }
296
- }
297
-
298
- await cpPromises(childSrc, childDest, opts);
299
- childInfo = enumerator.next_file(null);
300
- }
301
- }
302
-
303
- async function cpPromises(src: PathLike, dest: PathLike, opts: CpOptions = {}): Promise<void> {
304
- const srcStr = normalizePath(src);
305
- const destStr = normalizePath(dest);
306
-
307
- const srcFile = Gio.File.new_for_path(srcStr);
308
- let info: Gio.FileInfo;
309
- try {
310
- info = srcFile.query_info(
311
- 'standard::type',
312
- opts.dereference ? Gio.FileQueryInfoFlags.NONE : Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
313
- null,
314
- );
315
- } catch (err: unknown) {
316
- throw createNodeError(err, 'stat', srcStr);
317
- }
318
-
319
- if (info.get_file_type() === Gio.FileType.DIRECTORY) {
320
- if (!opts.recursive) throw makeEISDIR(srcStr);
321
- await cpPromisesDir(srcStr, destStr, opts);
322
- } else {
323
- const destFile = Gio.File.new_for_path(destStr);
324
- copyOneSyncFile(srcFile, destFile, srcStr, destStr, opts);
325
- }
326
- }
327
-
328
- export { cpPromises as cpAsync };