Skip to content

Commit

Permalink
chore: resolve linting issues
Browse files Browse the repository at this point in the history
  • Loading branch information
byCedric committed Apr 5, 2024
1 parent 4542fbf commit 459446b
Show file tree
Hide file tree
Showing 21 changed files with 102 additions and 103 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Expo Atlas

Inspect the bundle stats from Metro.
Inspect bundle contents, on module level, from Metro.

> [!Warning]
> This project is highly experimental and will likely not work for your project.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"sideEffects": false,
"name": "expo-atlas",
"version": "0.0.18-preview.2",
"description": "Inspect bundle stats from Metro",
"description": "Inspect bundle contents, on module level, from Metro",
"keywords": [
"expo",
"atlas",
Expand Down
12 changes: 6 additions & 6 deletions src/cli/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ if (args['--version']) {
if (args['--help']) {
printLines([
chalk.bold('Usage'),
` ${chalk.dim('$')} expo-atlas ${chalk.dim('[statsFile]')}`,
` ${chalk.dim('$')} expo-atlas ${chalk.dim('[atlas file]')}`,
'',
chalk.bold('Options'),
` --port${chalk.dim(', -p')} Port to listen on`,
Expand All @@ -52,7 +52,7 @@ async function run() {

printLines([
`Expo Atlas is ready on: ${chalk.underline(href)}`,
` ${chalk.dim(`Using: ${options.statsFile}`)}`,
` ${chalk.dim(`Using: ${options.atlasFile}`)}`,
]);

if (options.browserOpen) {
Expand All @@ -72,10 +72,10 @@ run().catch((error) => {
throw error;
}

if (error.code === 'STATS_FILE_INCOMPATIBLE') {
const statsFile = path.relative(process.cwd(), error.statsFile);
console.error('Stats file is incompatible with this version, use this instead:');
console.error(` npx expo-atlas@${error.incompatibleVersion} ${statsFile}`);
if (error.code === 'ATLAS_FILE_INCOMPATIBLE') {
const atlasFile = path.relative(process.cwd(), error.filePath);
console.error('Atlas file is incompatible with this version, use this instead:');
console.error(` npx expo-atlas@${error.incompatibleVersion} ${atlasFile}`);
} else {
console.error(`${error.message} (${error.code})`);
}
Expand Down
2 changes: 1 addition & 1 deletion src/cli/createServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { createAtlasMiddleware } from '../utils/middleware';
export function createServer(options: Options) {
process.env.NODE_ENV = 'production';

const source = new AtlasFileSource(options.statsFile);
const source = new AtlasFileSource(options.atlasFile);
const middleware = createAtlasMiddleware(source);
const baseUrl = '/_expo/atlas'; // Keep in sync with webui `app.json` `baseUrl`

Expand Down
14 changes: 7 additions & 7 deletions src/cli/resolveOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import freeport from 'freeport-async';
import path from 'path';

import { type Input } from './bin';
import { getAtlasPath, validateAtlasFile } from '../utils/stats';
import { getAtlasPath, validateAtlasFile } from '../data/AtlasFileSource';

export type Options = Awaited<ReturnType<typeof resolveOptions>>;

export async function resolveOptions(input: Input) {
const statsFile = await resolveStatsFile(input);
const atlasFile = await resolveAtlasFile(input);
const port = await resolvePort(input);
return { statsFile, port, browserOpen: input['--no-open'] !== true };
return { atlasFile, port, browserOpen: input['--no-open'] !== true };
}

async function resolveStatsFile(input: Input) {
const statsFile = input._[0] ?? getAtlasPath(process.cwd());
await validateAtlasFile(statsFile);
return path.resolve(statsFile);
async function resolveAtlasFile(input: Input) {
const atlasFile = input._[0] ?? getAtlasPath(process.cwd());
await validateAtlasFile(atlasFile);
return path.resolve(atlasFile);
}

async function resolvePort(input: Pick<Input, '--port'>) {
Expand Down
80 changes: 40 additions & 40 deletions src/data/AtlasFileSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,31 @@ import { appendJsonLine, forEachJsonLines, parseJsonLine } from '../utils/jsonl'
export type AtlasMetadata = { name: string; version: string };

export class AtlasFileSource implements AtlasSource {
constructor(public readonly statsPath: string) {
constructor(public readonly filePath: string) {
//
}

listEntries() {
return listAtlasEntries(this.statsPath);
return listAtlasEntries(this.filePath);
}

getEntry(id: string) {
const numeric = parseInt(id, 10);
assert(!Number.isNaN(numeric) && numeric > 1, `Invalid entry ID: ${id}`);
return readAtlasEntry(this.statsPath, Number(id));
return readAtlasEntry(this.filePath, Number(id));
}
}

/**
* List all stats entries without parsing the data.
* List all entries without parsing the data.
* This only reads the bundle name, and adds a line number as ID.
*/
export async function listAtlasEntries(statsPath: string) {
export async function listAtlasEntries(filePath: string) {
const bundlePattern = /^\["([^"]+)","([^"]+)","([^"]+)/;
const entries: PartialAtlasEntry[] = [];

await forEachJsonLines(statsPath, (contents, line) => {
// Skip the stats metadata line
await forEachJsonLines(filePath, (contents, line) => {
// Skip the metadata line
if (line === 1) return;

const [_, platform, projectRoot, entryPoint] = contents.match(bundlePattern) ?? [];
Expand All @@ -53,72 +53,72 @@ export async function listAtlasEntries(statsPath: string) {
}

/**
* Get the stats entry by id or line number, and parse the data.
* Get the entry by id or line number, and parse the data.
*/
export async function readAtlasEntry(statsPath: string, id: number): Promise<AtlasEntry> {
const statsEntry = await parseJsonLine<any[]>(statsPath, id);
export async function readAtlasEntry(filePath: string, id: number): Promise<AtlasEntry> {
const atlasEntry = await parseJsonLine<any[]>(filePath, id);
return {
id: String(id),
platform: statsEntry[0],
projectRoot: statsEntry[1],
entryPoint: statsEntry[2],
runtimeModules: statsEntry[3],
modules: new Map(statsEntry[4].map((module) => [module.path, module])),
transformOptions: statsEntry[5],
serializeOptions: statsEntry[6],
platform: atlasEntry[0],
projectRoot: atlasEntry[1],
entryPoint: atlasEntry[2],
runtimeModules: atlasEntry[3],
modules: new Map(atlasEntry[4].map((module) => [module.path, module])),
transformOptions: atlasEntry[5],
serializeOptions: atlasEntry[6],
};
}

/** Simple promise to avoid mixing appended data */
let writeStatsQueue: Promise<any> = Promise.resolve();
let writeQueue: Promise<any> = Promise.resolve();

/**
* Add a new stats entry to the stats file.
* This is appended on a new line, so we can load the stats selectively.
* Add a new entry to the file.
* This is appended on a new line, so we can load the selectively.
*/
export function writeAtlasEntry(statsPath: string, stats: AtlasEntry) {
const entry = [
stats.platform,
stats.projectRoot,
stats.entryPoint,
stats.runtimeModules,
Array.from(stats.modules.values()),
stats.transformOptions,
stats.serializeOptions,
export function writeAtlasEntry(filePath: string, entry: AtlasEntry) {
const line = [
entry.platform,
entry.projectRoot,
entry.entryPoint,
entry.runtimeModules,
Array.from(entry.modules.values()),
entry.transformOptions,
entry.serializeOptions,
];

return (writeStatsQueue = writeStatsQueue.then(() => appendJsonLine(statsPath, entry)));
return (writeQueue = writeQueue.then(() => appendJsonLine(filePath, line)));
}

/** The default location of the metro stats file */
/** The default location of the metro file */
export function getAtlasPath(projectRoot: string) {
return path.join(projectRoot, '.expo/atlas.jsonl');
}

/** The information to validate if a stats file is compatible with this library version */
/** The information to validate if a file is compatible with this library version */
export function getAtlasMetdata(): AtlasMetadata {
return { name, version };
}

/** Validate if the stats file is compatible with this library version */
export async function validateAtlasFile(statsFile: string, metadata = getAtlasMetdata()) {
if (!fs.existsSync(statsFile)) {
throw new AtlasValidationError('STATS_FILE_NOT_FOUND', statsFile);
/** Validate if the file is compatible with this library version */
export async function validateAtlasFile(filePath: string, metadata = getAtlasMetdata()) {
if (!fs.existsSync(filePath)) {
throw new AtlasValidationError('ATLAS_FILE_NOT_FOUND', filePath);
}

if (env.EXPO_ATLAS_NO_STATS_VALIDATION) {
if (env.EXPO_ATLAS_NO_VALIDATION) {
return;
}

const data = await parseJsonLine(statsFile, 1);
const data = await parseJsonLine(filePath, 1);

if (data.name !== metadata.name || data.version !== metadata.version) {
throw new AtlasValidationError('STATS_FILE_INCOMPATIBLE', statsFile, data.version);
throw new AtlasValidationError('ATLAS_FILE_INCOMPATIBLE', filePath, data.version);
}
}

/**
* Create or overwrite the stats file with basic metadata.
* Create or overwrite the file with basic metadata.
* This metdata is used by the API to determine version compatibility.
*/
export async function createAtlasFile(filePath: string) {
Expand Down
10 changes: 5 additions & 5 deletions src/data/MetroGraphSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type ConvertGraphToAtlasOptions = {
};

export class MetroGraphSource implements AtlasSource {
/** All known stats entries, stored by ID */
/** All known entries, stored by ID */
protected entries: Map<AtlasEntry['id'], AtlasEntry> = new Map();

listEntries() {
Expand All @@ -36,14 +36,14 @@ export class MetroGraphSource implements AtlasSource {
getEntry(id: string) {
const entry = this.entries.get(id);
if (!entry) {
throw new Error(`Stats entry "${id}" not found.`);
throw new Error(`Entry "${id}" not found.`);
}
return entry;
}

/**
* Event handler when a new graph instance is ready to serialize.
* This converts all relevant data stored in the graph to stats objects.
* This converts all relevant data stored in the graph to objects.
*/
onSerializeGraph(options: ConvertGraphToAtlasOptions) {
const entry = convertGraph(options);
Expand All @@ -52,7 +52,7 @@ export class MetroGraphSource implements AtlasSource {
}
}

/** Convert a Metro graph instance to a JSON-serializable stats entry */
/** Convert a Metro graph instance to a JSON-serializable entry */
export function convertGraph(options: ConvertGraphToAtlasOptions): AtlasEntry {
const serializeOptions = convertSerializeOptions(options);
const transformOptions = convertTransformOptions(options);
Expand Down Expand Up @@ -92,7 +92,7 @@ export function collectEntryPointModules(
return modules;
}

/** Convert a Metro module to a JSON-serializable stats module */
/** Convert a Metro module to a JSON-serializable Atlas module */
export function convertModule(
options: Pick<ConvertGraphToAtlasOptions, 'graph' | 'extensions'>,
module: MetroModule
Expand Down
22 changes: 11 additions & 11 deletions src/data/__tests__/atlas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ describe('getAtlasPath', () => {
});
});

describe('getStatsMetadata', () => {
describe('getAtlasMetdata', () => {
it('returns package name and version', () => {
expect(getAtlasMetdata()).toMatchObject({ name, version });
});
});

describe('createAtlasFile', () => {
it('creates a stats file with the correct metadata', async () => {
it('creates a file with the correct metadata', async () => {
const file = fixture('create-metadata', { temporary: true });
await createAtlasFile(file);
await expect(fs.promises.readFile(file, 'utf8')).resolves.toBe(
JSON.stringify({ name, version }) + '\n'
);
});

it('overwrites invalid stats file', async () => {
it('overwrites invalid file', async () => {
const file = fixture('create-invalid', { temporary: true });
await fs.promises.writeFile(file, JSON.stringify({ name, version: '0.0.0' }) + '\n');
await createAtlasFile(file);
Expand All @@ -41,7 +41,7 @@ describe('createAtlasFile', () => {
);
});

it('reuses valid stats file', async () => {
it('reuses valid file', async () => {
const file = fixture('create-valid', { temporary: true });
await fs.promises.writeFile(file, JSON.stringify({ name, version }) + '\n');
await createAtlasFile(file);
Expand All @@ -52,26 +52,26 @@ describe('createAtlasFile', () => {
});

describe('validateAtlasFile', () => {
it('passes for valid stats file', async () => {
it('passes for valid file', async () => {
const file = fixture('validate-valid', { temporary: true });
await createAtlasFile(file);
await expect(validateAtlasFile(file)).resolves.pass();
});

it('fails for non-existing stats file', async () => {
it('fails for non-existing file', async () => {
await expect(validateAtlasFile('./this-file-does-not-exists')).rejects.toThrow(
AtlasValidationError
);
});

it('fails for invalid stats file', async () => {
it('fails for invalid file', async () => {
const file = fixture('validate-invalid', { temporary: true });
await fs.promises.writeFile(file, JSON.stringify({ name, version: '0.0.0' }) + '\n');
await expect(validateAtlasFile(file)).rejects.toThrow(AtlasValidationError);
});

it('skips validation when EXPO_ATLAS_NO_STATS_VALIDATION is true-ish', async () => {
using _env = env('EXPO_ATLAS_NO_STATS_VALIDATION', 'true');
it('skips validation when EXPO_ATLAS_NO_VALIDATION is true-ish', async () => {
using _env = env('EXPO_ATLAS_NO_VALIDATION', 'true');
const file = fixture('validate-skip-invalid', { temporary: true });
await fs.promises.writeFile(file, JSON.stringify({ name, version: '0.0.0' }) + '\n');
await expect(validateAtlasFile(file)).resolves.pass();
Expand All @@ -85,8 +85,8 @@ describe('validateAtlasFile', () => {
*/
function fixture(name: string, { temporary = false }: { temporary?: boolean } = {}) {
const file = temporary
? path.join(__dirname, 'fixtures/stats', `${name}.temp.jsonl`)
: path.join(__dirname, 'fixtures/stats', `${name}.jsonl`);
? path.join(__dirname, 'fixtures/atlas', `${name}.temp.jsonl`)
: path.join(__dirname, 'fixtures/atlas', `${name}.jsonl`);

fs.mkdirSync(path.dirname(file), { recursive: true });

Expand Down
6 changes: 3 additions & 3 deletions src/data/types.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { MixedOutput } from 'metro';

export interface AtlasSource {
/** List all available stats entries */
/** List all available entries */
listEntries(): PartialAtlasEntry[] | Promise<PartialAtlasEntry[]>;
/** Load the full stats entry, by reference */
/** Load the full entry, by reference */
getEntry(ref: string): AtlasEntry | Promise<AtlasEntry>;
}

export type PartialAtlasEntry = Pick<AtlasEntry, 'id' | 'platform' | 'projectRoot' | 'entryPoint'>;

export type AtlasEntry = {
/** The unique reference or ID to this stats entry */
/** The unique reference or ID to this entry */
id: string;
/** The platform for which the bundle was created */
platform: 'android' | 'ios' | 'web';
Expand Down
Loading

0 comments on commit 459446b

Please sign in to comment.