Skip to content

Commit

Permalink
feat: add monorepo support (#16)
Browse files Browse the repository at this point in the history
* feat: add monorepo support to API

* feat: add UI for monorepos

* feat: add react-select

* chore: remove package-lock.json

* chore: add package-lock.json to .gitignore

* chore: fix mege conflicts
  • Loading branch information
azz committed Jan 5, 2018
1 parent e2fcaa9 commit 8e73b6e
Show file tree
Hide file tree
Showing 15 changed files with 345 additions and 77 deletions.
1 change: 1 addition & 0 deletions .eslintrc.yml
Expand Up @@ -15,3 +15,4 @@ env:
browser: true
node: true
jest: true
es6: true
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@ node_modules
*.log
.cache
dist
package-lock.json
5 changes: 2 additions & 3 deletions index.js
@@ -1,7 +1,6 @@
const cors = require('micro-cors')();

let serveStatic = require('./src/serve')('./dist');
let match = require('fs-router')(__dirname + '/routes');
const serveStatic = require('./src/serve')(__dirname + '/dist');
const match = require('fs-router')(__dirname + '/routes');

module.exports = cors(async function(req, res) {
let matched = match(req);
Expand Down
9 changes: 7 additions & 2 deletions package.json
Expand Up @@ -22,7 +22,7 @@
"private": true,
"scripts": {
"start": "npm run build && ./bin/npm-ui.js",
"develop": "concurrently \"micro-dev --ignore=dist\\|ui\" \"parcel watch index.html --public-url ./\"",
"develop": "concurrently \"micro-dev --ignore=/dist\\|/ui\" \"parcel watch index.html --public-url ./\"",
"build": "parcel build index.html --public-url ./",
"test": "jest",
"lint": "eslint . --ext=js,jsx --ignore-path=.gitignore",
Expand All @@ -33,6 +33,7 @@
"ansi-html-stream": "^0.0.3",
"execa": "^0.8.0",
"fs-router": "^0.4.0",
"get-monorepo-packages": "^1.0.1",
"get-port": "^3.2.0",
"has-yarn": "^1.0.0",
"merge-stream": "^1.0.1",
Expand All @@ -48,7 +49,9 @@
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"concurrently": "^3.5.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
Expand All @@ -68,10 +71,12 @@
"react-dom": "^16.2.0",
"react-instantsearch": "^4.4.0",
"react-redux": "^5.0.6",
"react-select": "^1.1.0",
"react-tabs": "^2.1.1",
"redux": "^3.7.2",
"redux-saga": "^0.16.0",
"styled-components": "^2.4.0"
"styled-components": "^2.4.0",
"styled-css-grid": "^0.8.0"
},
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/test/testSetup.js"
Expand Down
13 changes: 11 additions & 2 deletions routes/dependencies/index.js
@@ -1,3 +1,12 @@
const { getDependencies } = require('../../src/packageUtils.js');
const {
getDependencies,
getMonoRepoDependencies,
} = require('../../src/packageUtils.js');

module.exports = () => getDependencies();
module.exports = async () => {
const [dependencies, monorepo] = await Promise.all([
getDependencies(),
getMonoRepoDependencies(),
]);
return { root: dependencies, monorepo };
};
20 changes: 14 additions & 6 deletions src/packageUtils.js
@@ -1,9 +1,13 @@
const readPkgUp = require('read-pkg-up');
const getPackages = require('get-monorepo-packages');

async function extract(keys) {
async function readPkg() {
const { pkg } = await readPkgUp();
return pkg;
}

return keys.reduce((prev, p) => {
const extract = keys => pkg =>
keys.reduce((prev, p) => {
if (Array.isArray(p)) {
prev[p[0]] = extract(pkg[p[0]], p[1]);
} else if (
Expand All @@ -15,10 +19,14 @@ async function extract(keys) {
}
return prev;
}, {});
}

module.exports = {
getScripts: () => extract(['scripts']),
getDependencies: () => extract(['dependencies', 'devDependencies']),
getComplete: () => readPkgUp().then(p => p.pkg),
getScripts: () => readPkg().then(extract(['scripts'])),
getDependencies: () =>
readPkg().then(extract(['name', 'dependencies', 'devDependencies'])),
getComplete: () => readPkg(),
getMonoRepoDependencies: () =>
getPackages(process.cwd())
.map(pkg => pkg.package)
.map(extract(['name', 'dependencies', 'devDependencies'])),
};
2 changes: 1 addition & 1 deletion src/serve.js
Expand Up @@ -5,7 +5,7 @@ const mime = require('mime');

// https://github.com/zeit/serve/issues/267

module.exports = (prefix = './') => async (req, res) => {
module.exports = (prefix = __dirname) => async (req, res) => {
const parseUrl = url.parse(req.url);
let file = `${prefix}${parseUrl.pathname}`;

Expand Down
26 changes: 17 additions & 9 deletions ui/components/Dependencies.jsx
@@ -1,31 +1,39 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Grid, Cell } from 'styled-css-grid';

import { selectors } from '../ducks/dependencies';
import DependenciesList from './DependenciesList';
import Search from './Search';
import { Grid } from './styled';

const Dependencies = ({ dependencies, devDependencies }) => (
<Grid col="2">
<div>
<Grid columns={2}>
<Cell>
<h1>Dependencies</h1>
<DependenciesList dependencies={dependencies} />
<h1>DevDependencies</h1>
<DependenciesList dependencies={devDependencies} />
</div>
<div>
</Cell>
<Cell>
<Search />
</div>
</Cell>
</Grid>
);

Dependencies.propTypes = {
dependencies: PropTypes.array.isRequired,
devDependencies: PropTypes.array.isRequired,
packageName: PropTypes.string.isRequired,
};

export default connect(state => ({
dependencies: selectors.getDependencies(state.dependencies),
devDependencies: selectors.getDevDependencies(state.dependencies),
export default connect((state, ownProps) => ({
dependencies: selectors.getDependencies(
state.dependencies,
ownProps.packageName,
),
devDependencies: selectors.getDevDependencies(
state.dependencies,
ownProps.packageName,
),
}))(Dependencies);
4 changes: 2 additions & 2 deletions ui/components/NpmUi.jsx
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import Scripts from './Scripts';
import Dependencies from './Dependencies';
import Packages from './Packages';

class NpmUi extends Component {
render() {
Expand All @@ -16,7 +16,7 @@ class NpmUi extends Component {
<Scripts />
</TabPanel>
<TabPanel>
<Dependencies />
<Packages />
</TabPanel>
</Tabs>
);
Expand Down
55 changes: 55 additions & 0 deletions ui/components/Packages.jsx
@@ -0,0 +1,55 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import { connect } from 'react-redux';
import styled from 'styled-components';
import { bindActionCreators } from 'redux';

import Dependencies from './Dependencies';
import { actions, selectors } from '../ducks/dependencies';

const PackageSelect = styled(Select)`
width: 300px;
`;

const Packages = ({
className,
selectedPackageName,
packageNames,
onSelectPackage,
}) => (
<div>
<PackageSelect
className={className}
clearable={false}
value={selectedPackageName}
onChange={selectedOption => onSelectPackage(selectedOption.value)}
options={packageNames.map(name => ({
value: name,
label: name || '<anonymous>',
}))}
/>
<Dependencies packageName={selectedPackageName} />
</div>
);

Packages.propTypes = {
className: PropTypes.string,
packageNames: PropTypes.arrayOf(PropTypes.string).isRequired,
selectedPackageName: PropTypes.string.isRequired,
onSelectPackage: PropTypes.func.isRequired,
};

export default connect(
state => ({
selectedPackageName: selectors.getSelectedPackageName(state.dependencies),
packageNames: selectors.getPackageNames(state.dependencies),
}),
dispatch =>
bindActionCreators(
{
onSelectPackage: actions.setSelectedPackage,
},
dispatch,
),
)(Packages);
19 changes: 12 additions & 7 deletions ui/components/Scripts.jsx
Expand Up @@ -2,18 +2,23 @@ import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Grid, Cell } from 'styled-css-grid';

import { selectors, actions } from '../ducks/scripts';
import ScriptList from './ScriptList';
import { Grid, Log, Wrapper } from './styled';
import { Wrapper, Log } from './styled';

const Scripts = ({ scripts, log, onScriptClick }) => (
<Grid col="2">
<ScriptList scripts={scripts} onScriptClick={onScriptClick} />
<Wrapper>
<h3>Terminal output:</h3>
<Log dangerouslySetInnerHTML={{ __html: log }} />
</Wrapper>
<Grid columns={2}>
<Cell>
<ScriptList scripts={scripts} onScriptClick={onScriptClick} />
</Cell>
<Cell>
<Wrapper>
<h3>Terminal output:</h3>
<Log dangerouslySetInnerHTML={{ __html: log }} />
</Wrapper>
</Cell>
</Grid>
);

Expand Down
4 changes: 0 additions & 4 deletions ui/components/styled.js
@@ -1,8 +1,4 @@
import styled from 'styled-components';
export const Grid = styled.div`
display: grid;
grid-template-columns: repeat(${props => props.col}, 1fr);
`;

export const Wrapper = styled.section`
overflow: auto;
Expand Down
65 changes: 46 additions & 19 deletions ui/ducks/dependencies.js
@@ -1,46 +1,73 @@
const initialState = {
packageDependencies: {
dependencies: {},
devDependencies: {},
selectedPackage: '',
root: {
dependencies: [],
devDependencies: [],
name: '',
},
monorepo: [],
};

export const FETCH_DEPENDENCIES = 'FETCH_DEPENDENCIES';
export const SET_DEPENDENCIES = 'SET_DEPENDENCIES';
export const SET_SELECTED_PACKAGE = 'SET_SELECTED_PACKAGE';

export default (state = initialState, { type, ...payload }) => {
switch (type) {
case SET_SELECTED_PACKAGE:
return {
...state,
selectedPackage: payload.name,
};
case SET_DEPENDENCIES:
return {
...state,
packageDependencies: {
devDependencies: payload.devDependencies,
dependencies: payload.dependencies,
selectedPackage: payload.root.name,
root: {
name: payload.root.name,
dependencies: arrayify(payload.root.dependencies, 'version'),
devDependencies: arrayify(payload.root.devDependencies, 'version'),
},
monorepo: payload.monorepo.map(pkg => ({
name: pkg.name,
dependencies: arrayify(pkg.dependencies, 'version'),
devDependencies: arrayify(pkg.devDependencies, 'version'),
})),
};
default:
return state;
}
};

function arrayify(object = {}, key) {
return Object.keys(object).map(name => ({
name,
[key]: object[name],
}));
}

export const actions = {
fetchDependencies: () => ({ type: FETCH_DEPENDENCIES }),
setDependencies: ({ dependencies, devDependencies }) => ({
setDependencies: ({ root, monorepo }) => ({
type: SET_DEPENDENCIES,
dependencies,
devDependencies,
root,
monorepo,
}),
setSelectedPackage: name => ({ type: SET_SELECTED_PACKAGE, name }),
};

export const selectors = {
getDependencies: ({ packageDependencies: { dependencies } }) =>
Object.keys(dependencies).map(name => ({
name,
version: dependencies[name],
})),
getDevDependencies: ({ packageDependencies: { devDependencies } }) =>
Object.keys(devDependencies).map(name => ({
name,
version: devDependencies[name],
})),
getSelectedPackageName: ({ selectedPackage }) => selectedPackage,
getPackageNames: ({ monorepo, root }) => [
root.name,
...monorepo.map(({ name }) => name),
],
getDependencies: ({ monorepo, root }, packageName) =>
root.name === packageName
? root.dependencies
: monorepo.find(({ name }) => name === packageName).dependencies,
getDevDependencies: ({ monorepo, root }, packageName) =>
root.name === packageName
? root.devDependencies
: monorepo.find(({ name }) => name === packageName).devDependencies,
};
1 change: 1 addition & 0 deletions ui/index.css
@@ -1,4 +1,5 @@
@import 'react-tabs/style/react-tabs.css';
@import 'react-select/dist/react-select.css';

body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
Expand Down

0 comments on commit 8e73b6e

Please sign in to comment.