Skip to content

Commit

Permalink
feat(state/scope): adds getChildren
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Apr 24, 2019
1 parent f4c0972 commit 9fb9ddf
Show file tree
Hide file tree
Showing 10 changed files with 205 additions and 39 deletions.
34 changes: 17 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"@types/js-yaml": "^3.12.1",
"@types/lodash.mergewith": "^4.6.6",
"@types/object-hash": "^1.2.0",
"@types/pify": "^3.0.2",
"@types/signal-exit": "^3.0.0",
"@types/uuid": "^3.4.4",
"@typescript-eslint/eslint-plugin": "^1.6.0",
Expand Down Expand Up @@ -120,11 +121,13 @@
"errorish": "^0.2.1",
"find-up": "^3.0.0",
"fs-extra": "^7.0.1",
"glob": "^7.1.3",
"js-yaml": "^3.13.1",
"lodash.mergewith": "^4.6.1",
"loglevel": "^1.6.1",
"manage-path": "^2.0.0",
"object-hash": "^1.3.1",
"pify": "^4.0.1",
"promist": "^0.5.3",
"signal-exit": "^3.0.2",
"slimconf": "^0.9.0",
Expand Down
5 changes: 4 additions & 1 deletion src/state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ export default {
async setScope(name: string): Promise<void> {
const definition = await scope(name);
if (definition) {
states.internal.scopes.push(definition.name);
// keep track of scope branches
states.internal.scopes = states.internal.scopes.concat(definition.names);
// set current directory as the the one of the scope
this.setBase({ file: null, directory: definition.directory });
// reset options
this.setOptions();
}
},
Expand Down
19 changes: 0 additions & 19 deletions src/state/scope.ts

This file was deleted.

80 changes: 80 additions & 0 deletions src/state/scope/children/from-globs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import path from 'path';
import fs from 'fs-extra';
import glob from 'glob';
import pify from 'pify';
import { parallel } from 'promist';
import { exists } from '~/utils/file';
import { FILE_NAME, FILE_EXT } from '~/constants';
import { IChild } from '../../types';

export default async function getChildrenFromGlobs(
patterns: string[],
directory: string
): Promise<IChild[]> {
const arrs = await parallel.map(patterns, (pattern) =>
fromGlob(pattern, directory)
);

const dirs = arrs.reduce((acc: string[], arr: string[]) => {
return acc.concat(arr);
}, []);

// filter and make into IChild
return filter(dirs).map((dir) => ({
// absolute path
directory: path.join(directory, dir),
matcher(name: string) {
return dir.includes(name);
}
}));
}

export async function fromGlob(
pattern: string,
directory: string
): Promise<string[]> {
return parallel.filter(
await pify(glob)(pattern, { cwd: directory }),
async (dir: string) => {
// get absolute path
dir = path.join(directory, dir);

// select only directories
const stat = await fs.stat(dir);
if (!stat.isDirectory()) return false;

// select only directories that have a package.json
// or a kpo configuration file
const toFind = ['package.json']
.concat(FILE_EXT.map((ext) => FILE_NAME + ext))
.map((file) => path.join(dir, file));

for (let file of toFind) {
if (await exists(file)) return true;
}

return false;
}
);
}

/**
* Filter directories: select only the first one in depth
* in which a configuration file was found.
* If we have /foo and /foo/bar, only /foo will be selected
*/
export function filter(dirs: string[]): string[] {
dirs = dirs.sort();
let i = 1;
while (i < dirs.length) {
const current = dirs[i];
const previous = dirs[i - 1];
if (current.slice(0, previous.length) === previous) {
dirs = dirs.slice(0, i).concat(dirs.slice(i + 1));
} else {
i++;
}
}

return dirs;
}
15 changes: 15 additions & 0 deletions src/state/scope/children/from-map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import path from 'path';
import { IOfType } from '~/types';
import { IChild } from '../../types';

export default function getChildrenFromMap(
map: IOfType<string>,
directory: string
): IChild[] {
return Object.entries(map).map(([key, value]) => ({
directory: path.isAbsolute(value) ? value : path.join(directory, value),
matcher(name: string): boolean {
return name === key;
}
}));
}
39 changes: 39 additions & 0 deletions src/state/scope/children/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import path from 'path';
import fs from 'fs-extra';
import logger from '~/utils/logger';
import { exists } from '~/utils/file';
import { rejects } from 'errorish';
import getChildrenFromGlobs from './from-globs';
import getChildrenFromMap from './from-map';
import { IChild } from '../../types';
import { TChildrenDefinition } from '~/types';

export default async function getChildren(
directory: string,
definition?: TChildrenDefinition
): Promise<IChild[]> {
logger.debug('obtaining children');

if (definition) {
logger.debug('children found in options');

if (Array.isArray(definition)) {
return getChildrenFromGlobs(definition, directory);
}
return getChildrenFromMap(definition, directory);
}

const lerna = (await exists(path.join(directory, 'lerna.json')))
? await fs.readJSON(path.join(directory, 'lerna.json')).catch(rejects)
: null;

if (lerna) {
logger.debug('lerna file found');
if (lerna.packages) {
return getChildrenFromGlobs(lerna.packages, directory);
}
}

logger.debug('no children found');
return [];
}
38 changes: 38 additions & 0 deletions src/state/scope/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import state from '../index';
import logger from '~/utils/logger';
import getChildren from './children';
import { IScopeDefinition } from '../types';

export default async function scope(
scope: string
): Promise<IScopeDefinition | null> {
const paths = await state.paths();

// root scope
if (scope === 'root') {
if (paths.root) {
return { names: ['root'], directory: paths.root.directory };
} else {
logger.debug('root scope was not found and was assigned to self');
return null;
}
}

// child scopes
const children = await getChildren(paths.directory, state.get('children'));
const matches = children
.filter((child) => child.matcher(scope))
.map((child) => child.directory);

if (matches.length) {
logger.debug(`scopes found for ${scope}:\n${matches.join('\n')}`);

if (matches.length > 1) {
throw Error(`Several scopes matched name "${scope}"`);
}

return { names: [scope], directory: matches[0] };
}

throw Error(`Scope ${scope} was not found`);
}
7 changes: 6 additions & 1 deletion src/state/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export interface ILoaded {
}

export interface IScopeDefinition {
name: string;
names: string[];
directory: string;
}

export interface IChild {
directory: string;
matcher: (scope: string) => boolean;
}
4 changes: 3 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,7 @@ export interface IBaseOptions extends ICoreOptions {

export interface IScopeOptions extends ICoreOptions {
root?: string | null;
children?: IOfType<string>;
children?: TChildrenDefinition;
}

export type TChildrenDefinition = IOfType<string> | string[];

0 comments on commit 9fb9ddf

Please sign in to comment.