Refactor node packages to yarn workspaces & ditch next.js for CRA. (#161)

* chore: restructure project into yarn workspaces, remove next

* fix tests, remove webapp from terraform

* remove more ui deployment bits

* remove pages, fix FUNDING.yml

* remove isomorphism

* remove next providers

* fix linting issues

* feat: start basis of new web ui system on CRA

* chore: move types to @roleypoly/types package

* chore: move src/common/utils to @roleypoly/misc-utils

* chore: remove roleypoly/ path remappers

* chore: renmove vercel config

* chore: re-add worker-types to api package

* chore: fix type linting scope for api

* fix(web): craco should include all of packages dir

* fix(ci): change api webpack path for wrangler

* chore: remove GAR actions from CI

* chore: update codeql job

* chore: test better github dar matcher in lint-staged
This commit is contained in:
41666 2021-03-12 18:04:49 -05:00 committed by GitHub
parent 49e308507e
commit 2ff6588030
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
328 changed files with 16624 additions and 3525 deletions

View file

@ -0,0 +1,9 @@
import { shallow } from 'enzyme';
import * as React from 'react';
import { ReactifyNewlines } from './ReactifyNewlines';
it('renders a correct number of divs per newlines', () => {
const view = shallow(<ReactifyNewlines>{`1\n2\n3`}</ReactifyNewlines>);
expect(view.find('div').length).toBe(3);
});

View file

@ -0,0 +1,12 @@
import * as React from 'react';
export const ReactifyNewlines = (props: { children: string }) => {
const textArray = props.children.split('\n');
return (
<>
{textArray.map((part, idx) => (
<div key={`rifynl${idx}`}>{part || <>&nbsp;</>}</div>
))}
</>
);
};

View file

@ -0,0 +1,19 @@
import * as React from 'react';
export enum FeatureFlag {
AllowListsBlockLists = 'AllowListsBlockLists',
}
export class FeatureFlagProvider {
activeFlags: FeatureFlag[] = [];
constructor(flags: FeatureFlag[] = []) {
this.activeFlags = flags;
}
has(flag: FeatureFlag) {
return this.activeFlags.includes(flag);
}
}
export const FeatureFlagsContext = React.createContext(new FeatureFlagProvider());

View file

@ -0,0 +1 @@
export * from './FeatureFlags';

View file

@ -0,0 +1,12 @@
import * as React from 'react';
import { FeatureFlag, FeatureFlagProvider, FeatureFlagsContext } from './FeatureFlags';
export const FeatureFlagDecorator = (flags: FeatureFlag[]) => (
storyFn: () => React.ReactNode
) => {
return (
<FeatureFlagsContext.Provider value={new FeatureFlagProvider(flags)}>
{storyFn()}
</FeatureFlagsContext.Provider>
);
};

View file

@ -0,0 +1,47 @@
import { roleCategory } from '@roleypoly/design-system/fixtures/storyData';
import { Role } from '@roleypoly/types';
import { hasPermission, hasPermissionOrAdmin } from './hasPermission';
export const permissions = {
KICK_MEMBERS: BigInt(0x2),
BAN_MEMBERS: BigInt(0x4),
ADMINISTRATOR: BigInt(0x8),
SPEAK: BigInt(0x200000),
CHANGE_NICKNAME: BigInt(0x4000000),
MANAGE_ROLES: BigInt(0x10000000),
};
const roles: Role[] = [
{
...roleCategory[0],
permissions: String(permissions.ADMINISTRATOR),
},
{
...roleCategory[0],
permissions: String(
permissions.SPEAK | permissions.BAN_MEMBERS | permissions.CHANGE_NICKNAME
),
},
{
...roleCategory[0],
permissions: String(permissions.BAN_MEMBERS),
},
];
it('finds a permission within a list of roles', () => {
const result = hasPermission(roles, permissions.CHANGE_NICKNAME);
expect(result).toBe(true);
});
it('finds admin within a list of roles', () => {
const result = hasPermissionOrAdmin(roles, permissions.BAN_MEMBERS);
expect(result).toBe(true);
});
it('does not find a permission within a list of roles without one', () => {
const result = hasPermission(roles, permissions.KICK_MEMBERS);
expect(result).toBe(false);
});

View file

@ -0,0 +1,55 @@
import { Role } from '@roleypoly/types';
export const evaluatePermission = <T extends number | bigint>(
haystack: T,
needle: T
): boolean => {
return (haystack & needle) === needle;
};
export const hasPermission = (roles: Role[], permission: bigint): boolean => {
const aggregateRoles = roles.reduce(
(acc, role) => acc | BigInt(role.permissions),
BigInt(0)
);
return evaluatePermission(aggregateRoles, permission);
};
export const hasPermissionOrAdmin = (roles: Role[], permission: bigint): boolean =>
hasPermission(roles, permission | permissions.ADMINISTRATOR);
export const permissions = {
// IMPORTANT: Only uncomment what's actually used. All are left for convenience.
// CREATE_INSTANT_INVITE: BigInt(0x1),
// KICK_MEMBERS: BigInt(0x2),
// BAN_MEMBERS: BigInt(0x4),
ADMINISTRATOR: BigInt(0x8),
// MANAGE_CHANNELS: BigInt(0x10),
// MANAGE_GUILD: BigInt(0x20),
// ADD_REACTIONS: BigInt(0x40),
// VIEW_AUDIT_LOG: BigInt(0x80),
// VIEW_CHANNEL: BigInt(0x400),
// SEND_MESSAGES: BigInt(0x800),
// SEND_TTS_MESSAGES: BigInt(0x1000),
// MANAGE_MESSAGES: BigInt(0x2000),
// EMBED_LINKS: BigInt(0x4000),
// ATTACH_FILES: BigInt(0x8000),
// READ_MESSAGE_HISTORY: BigInt(0x10000),
// MENTION_EVERYONE: BigInt(0x20000),
// USE_EXTERNAL_EMOJIS: BigInt(0x40000),
// VIEW_GUILD_INSIGHTS: BigInt(0x80000),
// CONNECT: BigInt(0x100000),
// SPEAK: BigInt(0x200000),
// MUTE_MEMBERS: BigInt(0x400000),
// DEAFEN_MEMBERS: BigInt(0x800000),
// MOVE_MEMBERS: BigInt(0x1000000),
// USE_VAD: BigInt(0x2000000),
// PRIORITY_SPEAKER: BigInt(0x100),
// STREAM: BigInt(0x200),
// CHANGE_NICKNAME: BigInt(0x4000000),
// MANAGE_NICKNAMES: BigInt(0x8000000),
MANAGE_ROLES: BigInt(0x10000000),
// MANAGE_WEBHOOKS: BigInt(0x20000000),
// MANAGE_EMOJIS: BigInt(0x40000000),
};

View file

@ -0,0 +1 @@
export const isBrowser = () => typeof window !== 'undefined';

View file

@ -0,0 +1,11 @@
{
"name": "@roleypoly/misc-utils",
"version": "0.1.0",
"dependencies": {
"@roleypoly/types": "*"
},
"optionalDependencies": {
"enzyme": "3.x",
"react": "*"
}
}

View file

@ -0,0 +1,48 @@
import { sortBy } from './sortBy';
it('sorts an array of objects by its key', () => {
const output = sortBy(
[
{
name: 'bbb',
},
{
name: 'aaa',
},
{
name: 'ddd',
},
{
name: 'ccc',
},
],
'name'
);
expect(output.map((v) => v.name)).toEqual(['aaa', 'bbb', 'ccc', 'ddd']);
});
it('sorts an array of objects by its key with a predicate', () => {
const output = sortBy(
[
{
name: 'cc',
},
{
name: 'bbb',
},
{
name: 'aaaa',
},
{
name: 'd',
},
],
'name',
(a, b) => {
return a.length > b.length ? 1 : -1;
}
);
expect(output.map((v) => v.name)).toEqual(['d', 'cc', 'bbb', 'aaaa']);
});

View file

@ -0,0 +1,21 @@
export const sortBy = <T, Key extends keyof T>(
array: T[],
key: Key,
predicate?: (a: T[typeof key], b: T[typeof key]) => number
) => {
return array.sort((a, b) => {
if (predicate) {
return predicate(a[key], b[key]);
}
if (a[key] === b[key]) {
return 0;
}
if (a[key] > b[key]) {
return 1;
}
return -1;
});
};

View file

@ -0,0 +1,11 @@
import * as React from 'react';
export type ContextShimProps<T> = {
context: React.Context<T>;
children: (data: T) => any;
};
export function ContextShim<T>(props: ContextShimProps<T>) {
const context = React.useContext(props.context);
return <>{props.children(context)}</>;
}

View file

@ -0,0 +1,2 @@
export * as testHelpers from './contextTestHelpers';
export * from './withContext';

View file

@ -0,0 +1,10 @@
import * as React from 'react';
export const withContext = <T, K extends T>(
Context: React.Context<T>,
Component: React.ComponentType<K>
): React.FunctionComponent<K> => (props) => (
<Context.Consumer>
{(context) => <Component {...props} {...context} />}
</Context.Consumer>
);