expo-updates
Version:
Fetches and manages remotely-hosted assets and updates to your app's JS bundle.
124 lines (107 loc) • 3.4 kB
text/typescript
import spawnAsync from '@expo/spawn-async';
import fs from 'fs/promises';
import { glob } from 'glob';
import createIgnore, { Ignore as SingleFileIgnore } from 'ignore';
import path from 'path';
export interface Client {
getRootPathAsync(): Promise<string>;
isFileIgnoredAsync(filePath: string): Promise<boolean>;
}
export default async function getVCSClientAsync(projectDir: string): Promise<Client> {
if (await isGitInstalledAndConfiguredAsync()) {
return new GitClient();
} else {
return new NoVCSClient(projectDir);
}
}
class GitClient implements Client {
public async getRootPathAsync(): Promise<string> {
return (await spawnAsync('git', ['rev-parse', '--show-toplevel'])).stdout.trim();
}
async isFileIgnoredAsync(filePath: string): Promise<boolean> {
try {
await spawnAsync('git', ['check-ignore', '-q', filePath], {
cwd: path.normalize(await this.getRootPathAsync()),
});
return true;
} catch {
return false;
}
}
}
class NoVCSClient implements Client {
constructor(private readonly projectDir: string) {}
async getRootPathAsync(): Promise<string> {
return this.projectDir;
}
async isFileIgnoredAsync(filePath: string): Promise<boolean> {
const ignore = new Ignore(this.projectDir);
await ignore.initIgnoreAsync();
return ignore.ignores(filePath);
}
}
async function isGitInstalledAndConfiguredAsync(): Promise<boolean> {
try {
await spawnAsync('git', ['--help']);
} catch (error: any) {
if (error.code === 'ENOENT') {
return false;
}
throw error;
}
try {
await spawnAsync('git', ['rev-parse', '--show-toplevel']);
} catch {
return false;
}
return true;
}
const GITIGNORE_FILENAME = '.gitignore';
const DEFAULT_IGNORE = `
.git
node_modules
`;
/**
* Ignore wraps the 'ignore' package to support multiple .gitignore files
* in subdirectories.
*
* Inconsistencies with git behavior:
* - if parent .gitignore has ignore rule and child has exception to that rule,
* file will still be ignored,
* - node_modules is always ignored
*
* Differs from the eas-cli Ignore class by not using `.easignore`. Otherwise this is copied. May try
* to merge the implementations soon.
*/
class Ignore {
private ignoreMapping: (readonly [string, SingleFileIgnore])[] = [];
constructor(private rootDir: string) {}
public async initIgnoreAsync(): Promise<void> {
const ignoreFilePaths = (
await glob(`**/${GITIGNORE_FILENAME}`, {
cwd: this.rootDir,
ignore: ['node_modules'],
follow: false,
})
)
// ensure that parent dir is before child directories
.sort((a, b) => a.length - b.length && a.localeCompare(b));
const ignoreMapping = await Promise.all(
ignoreFilePaths.map(async (filePath) => {
return [
filePath.slice(0, filePath.length - GITIGNORE_FILENAME.length),
createIgnore().add(await fs.readFile(path.join(this.rootDir, filePath), 'utf-8')),
] as const;
})
);
this.ignoreMapping = [['', createIgnore().add(DEFAULT_IGNORE)], ...ignoreMapping];
}
public ignores(relativePath: string): boolean {
for (const [prefix, ignore] of this.ignoreMapping) {
if (relativePath.startsWith(prefix) && ignore.ignores(relativePath.slice(prefix.length))) {
return true;
}
}
return false;
}
}