Skip to content

Commit

Permalink
fixup! Fix lint errors from unicorn/filename-case
Browse files Browse the repository at this point in the history
  • Loading branch information
codykaup committed Sep 27, 2024
1 parent 0608de1 commit 5e3d8eb
Show file tree
Hide file tree
Showing 14 changed files with 449 additions and 0 deletions.
21 changes: 21 additions & 0 deletions node-src/io/getDnsResolveAgent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import dns from 'dns';
import { Agent, AgentOptions } from 'https';

export class DNSResolveAgent extends Agent {
constructor(options: AgentOptions = {}) {
super({
...options,
lookup(
hostname: string,
_options: dns.LookupOneOptions,
callback: (err: NodeJS.ErrnoException, address: string, family: number) => void
) {
dns.resolve(hostname, (err, addresses) => callback(err, addresses?.[0], 4));
},
});
}
}

const getDNSResolveAgent = () => new DNSResolveAgent();

export default getDNSResolveAgent;
72 changes: 72 additions & 0 deletions node-src/io/graphqlClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import retry from 'async-retry';

import { InitialContext } from '..';
import HTTPClient, { HTTPClientOptions } from './httpClient';

const RETRYABLE_ERROR_CODE = 'RETRYABLE_ERROR_CODE';

export interface GraphQLError {
message: string;
locations?: { line: number; column: number }[];
extensions: {
code: string;
exception?: { stacktrace?: string[] };
};
}

export default class GraphQLClient {
endpoint: string;
headers: HTTPClientOptions['headers'];
client: HTTPClient;

constructor(ctx: InitialContext, endpoint: string, httpClientOptions: HTTPClientOptions) {
if (!endpoint) throw new Error('Option `endpoint` required.');
this.endpoint = endpoint;
this.client = new HTTPClient(ctx, httpClientOptions);
this.headers = { 'Content-Type': 'application/json' };
}

setAuthorization(token: string) {
this.headers.Authorization = `Bearer ${token}`;
}

async runQuery<T>(
query: string,
variables: Record<string, any>,
{ endpoint = this.endpoint, headers = {}, retries = 2 } = {}
): Promise<T> {
return retry(
async (bail) => {
const { data, errors } = await this.client
.fetch(
endpoint,
{
body: JSON.stringify({ query, variables }),
headers: { ...this.headers, ...headers },
method: 'post',
},
{ retries }
)
.then((res) => res.json() as any)
.catch(bail);

if (!errors) return data;
if (!Array.isArray(errors)) return bail(errors);

// GraphQL typically returns a list of errors
this.client.log.debug({ errors }, 'GraphQL errors');
for (const err of errors) {
// Throw an error to retry the query if it's safe to do so, otherwise bail
if (err.extensions && err.extensions.code === RETRYABLE_ERROR_CODE) throw err;

err.name = err.name || 'GraphQLError';
err.at = `${err.path.join('.')} ${err.locations
.map((l) => `${l.line}:${l.column}`)
.join(', ')}`;
}
return bail(errors.length === 1 ? errors[0] : errors);
},
{ retries }
);
}
}
109 changes: 109 additions & 0 deletions node-src/io/httpClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import retry from 'async-retry';
import dns from 'dns';
import { HttpsProxyAgentOptions } from 'https-proxy-agent';
import fetch, { RequestInit, Response } from 'node-fetch';

import { Context } from '../types';
import getDNSResolveAgent from './getDnsResolveAgent';
import getProxyAgent from './getProxyAgent';

export class HTTPClientError extends Error {
response: Response;

constructor(fetchResponse: Response, message?: string, ...params: any[]) {
super(...params);

// Maintains proper stack trace for where our error was thrown (only available on V8)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, HTTPClientError);
}

this.response = fetchResponse;
this.message =
message ||
`HTTPClient failed to fetch ${fetchResponse.url}, got ${fetchResponse.status}/${fetchResponse.statusText}`;
}
}

export interface HTTPClientOptions {
headers?: Record<string, string>;
retries?: number;
}

export interface HTTPClientFetchOptions {
noLogErrorBody?: boolean;
proxy?: HttpsProxyAgentOptions<any>;
retries?: number;
}

// A basic wrapper class for fetch with the ability to retry fetches
export default class HTTPClient {
env: Context['env'];

log: Context['log'];

headers: Record<string, string>;

retries: number;

constructor(
{ env, log }: Pick<Context, 'env' | 'log'>,
{ headers, retries = 0 }: HTTPClientOptions = {}
) {
if (!log) throw new Error(`Missing required option in HTTPClient: log`);
this.env = env;
this.log = log;
this.headers = headers;
this.retries = retries;
}

async fetch(url: string, options: RequestInit = {}, opts: HTTPClientFetchOptions = {}) {
let agent = options.agent || getProxyAgent({ env: this.env, log: this.log }, url, opts.proxy);

if (this.env.CHROMATIC_DNS_SERVERS.length > 0) {
this.log.debug(`Using custom DNS servers: ${this.env.CHROMATIC_DNS_SERVERS.join(', ')}`);
dns.setServers(this.env.CHROMATIC_DNS_SERVERS);
agent = getDNSResolveAgent();
}

// The user can override retries and set it to 0
const retries = opts.retries === undefined ? this.retries : opts.retries;
const onRetry = (err, n) => {
this.log.debug({ url, err }, `Fetch failed; retrying ${n}/${retries}`);
// node-fetch includes ENOTFOUND in the message, but undici (native fetch in ts-node) doesn't.
if (err.message.includes('ENOTFOUND') || [err.code, err.cause?.code].includes('ENOTFOUND')) {
if (!agent) {
this.log.warn('Fetch failed due to DNS lookup; switching to custom DNS resolver');
agent = getDNSResolveAgent();
} else if (this.env.CHROMATIC_DNS_FAILOVER_SERVERS.length > 0) {
this.log.warn('Fetch failed due to DNS lookup; switching to failover DNS servers');
dns.setServers(this.env.CHROMATIC_DNS_FAILOVER_SERVERS);
}
}
};

return retry(
async () => {
const headers = { ...this.headers, ...options.headers };
const res = await fetch(url, { ...options, agent, headers });
if (!res.ok) {
const error = new HTTPClientError(res);
// You can only call text() or json() once, so if we are going to handle it outside of here..
if (!opts.noLogErrorBody) {
const body = await res.text();
this.log.debug(error.message);
this.log.debug(body);
}
throw error;
}
return res;
},
{ retries, onRetry }
);
}

async fetchBuffer(url, options) {
const res = await this.fetch(url, options);
return res.buffer();
}
}
17 changes: 17 additions & 0 deletions node-src/lib/fileReaderBlob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { createReadStream, ReadStream } from 'fs';

export class FileReaderBlob {
readStream: ReadStream;
size: number;

readonly [Symbol.toStringTag] = 'Blob';
constructor(filePath: string, contentLength: number, onProgress: (delta: number) => void) {
this.size = contentLength;
this.readStream = createReadStream(filePath);
this.readStream.on('data', (chunk: Buffer | string) => onProgress(chunk.length));
}

stream() {
return this.readStream;
}
}
32 changes: 32 additions & 0 deletions node-src/lib/loggingRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import UpdateRenderer from 'listr-update-renderer';

export default class LoggingRenderer {
static readonly nonTTY = false;
tasks;
options;
updateRenderer;

constructor(tasks: any, options: any) {
this.tasks = tasks;
this.options = options;
this.updateRenderer = new UpdateRenderer(tasks, options);
}

render() {
this.updateRenderer.render();
for (const task of this.tasks) {
let lastData;
task.subscribe((event) => {
if (event.type === 'TITLE') this.options.log.file(`${task.title}`);
if (event.type === 'DATA' && lastData !== event.data) {
lastData = event.data;
this.options.log.file(` → ${event.data}`);
}
});
}
}

end() {
this.updateRenderer.end();
}
}
27 changes: 27 additions & 0 deletions node-src/lib/nonTtyRenderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export default class NonTTYRenderer {
static readonly nonTTY = true;
tasks;
options;

constructor(tasks: any, options: any) {
this.tasks = tasks;
this.options = options;
}

render() {
for (const task of this.tasks) {
let lastData;
task.subscribe((event) => {
if (event.type === 'TITLE') this.options.log.info(`${task.title}`);
if (event.type === 'DATA' && lastData !== event.data) {
lastData = event.data;
this.options.log.info(` → ${event.data}`);
}
});
}
}

end() {
// do nothing
}
}
24 changes: 24 additions & 0 deletions node-src/ui/messages/errors/noCsfGlobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import chalk from 'chalk';
import { dedent } from 'ts-dedent';

import { error, info } from '../../components/icons';
import link from '../../components/link';

export default ({ statsPath, storybookDir, storybookBuildDir, entryFile, viewLayer = 'react' }) => {
if (entryFile) {
const hint = storybookBuildDir
? chalk`Configure {bold --storybook-config-dir} with the value for {bold --config-dir} or {bold -c} from your build-storybook script.`
: chalk`Configure {bold --build-script-name} to point at the {bold build-storybook} script which has {bold --config-dir} or {bold -c} set.`;
return dedent(chalk`
${error} Did not find any CSF globs in {bold ${statsPath}}
Found an entry file at {bold ${entryFile}} but expected it at {bold ${storybookDir}/generated-stories-entry.js}.
${hint}
${info} Read more at ${link('https://www.chromatic.com/docs/turbosnap')}
`);
}
return dedent(chalk`
${error} Did not find any CSF globs in {bold ${statsPath}}
Check your stories configuration in {bold ${storybookDir}/main.js}
${info} Read more at ${link(`https://storybook.js.org/docs/${viewLayer}/configure/overview`)}
`);
};
7 changes: 7 additions & 0 deletions node-src/ui/messages/info/speedUpCi.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import speedUpCI from './speedUpCi';

export default {
title: 'CLI/Messages/Info',
};

export const SpeedUpCI = () => speedUpCI('github');
File renamed without changes.
22 changes: 22 additions & 0 deletions test-stories/a.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import PropTypes from 'prop-types';
import React from 'react';

const style = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '50px',
height: '50px',
backgroundColor: 'darkkhaki',
};

export default function A({ backgroundColor, ...props }) {
let computedStyle = style;
if (backgroundColor) {
computedStyle = { ...style, backgroundColor };
}

return <div {...props} style={computedStyle} />;
}

A.propTypes = { thing: PropTypes.func.isRequired };
15 changes: 15 additions & 0 deletions test-stories/aWrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';

import A from './A';

const HOC = (Component) => {
const Wrapped = (props) => {
return <Component {...props} />;
};

Wrapped.displayName = `wrapped(${Component.displayName || Component.name})`;

return Wrapped;
};

export default HOC(HOC(HOC(A)));
14 changes: 14 additions & 0 deletions test-stories/b.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';

const style = {
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '20px',
height: '20px',
backgroundColor: 'blueviolet',
};

export default function B(props) {
return <span {...props} style={style} />;
}
9 changes: 9 additions & 0 deletions test-stories/star.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import React from 'react';

export default function Star() {
return (
<svg>
<path fill="#000" stroke="#000" d="m25,1 6,17h18l-14,11 5,17-15-10-15,10 5-17-14-11h18z" />
</svg>
);
}
Loading

0 comments on commit 5e3d8eb

Please sign in to comment.