-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsnapshot.ts
103 lines (90 loc) · 2.86 KB
/
snapshot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import * as fs from 'node:fs/promises';
import path from 'node:path';
import parse from 'parse-gitignore';
import type { FileSystemTree } from '@webcontainer/api';
import ignore, { Ignore } from 'ignore';
import { constants, PathLike } from 'node:fs';
export const pathExists = async (filePath: PathLike) => {
try {
await fs.access(filePath, constants.F_OK);
return true; // The path exists
}
catch {
return false; // The path does not exist
}
};
export const buildTree = async function* (dir: string, tree?: FileSystemTree, exclude?: Ignore, rootDir = dir): AsyncGenerator<string> {
for await (const d of await fs.opendir(dir)) {
const entry = path.join(dir, d.name);
const relativeEntry = path.relative(rootDir, entry);
if (exclude && exclude.ignores(relativeEntry)) {
continue;
}
if (d.isDirectory()) {
const node: FileSystemTree = {};
if (tree) {
tree[d.name] = { directory: node };
}
yield* buildTree(entry, node, exclude, rootDir);
}
else if (d.isFile()) {
const contents = await fs.readFile(entry, 'utf8');
if (tree) {
tree[d.name] = { file: { contents } };
}
yield entry;
}
else if (d.isSymbolicLink()) {
const symlink = await fs.readlink(entry);
if (tree) {
tree[d.name] = { file: { symlink } };
}
yield entry;
}
}
};
export const buildIgnore = async (root: string, include: string[], exclude: string[], gitignore: string | null) => {
const excluded = ignore().add(exclude);
if (gitignore !== null) {
const resolved = path.resolve(root, gitignore);
if (await pathExists(resolved)) {
const ignoreFiles = await fs.readFile(resolved);
// @ts-ignore
const { patterns } = parse(ignoreFiles);
excluded.add(patterns);
}
}
// include patterns are negated and should supercede exclude
include = include.map((pattern) => pattern.startsWith('!') ? pattern.slice(1) : `!${pattern}`);
excluded.add(include);
return excluded;
};
export const snapshotDefaults: Required<SnapshotProps<unknown>> = {
root: process.cwd(),
include: [],
exclude: ['.git'],
gitignore: '.gitignore',
transform: async (fs: FileSystemTree) => fs,
};
export type SnapshotProps<T> = {
transform?: (tree: FileSystemTree) => Promise<FileSystemTree>;
} & Partial<TakeSnapshotProps> & T;
export type TakeSnapshotProps = {
root: string;
include: string[];
exclude: string[];
gitignore: string | null;
};
/**
* Takes a snapshot of the file system based on the provided properties.
*
* @param props - The properties to configure the snapshot.
*/
export const takeSnapshot = async (props: Partial<TakeSnapshotProps> = {}) => {
const { root, include, exclude, gitignore } = { ...snapshotDefaults, ...props };
const filetree: FileSystemTree = {};
const excluded = await buildIgnore(root, include, exclude, gitignore);
// eslint-disable-next-line
for await (const _ of buildTree(root, filetree, excluded)) { }
return filetree;
};