Skip to content

Commit

Permalink
refactor(state): refactors state
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Apr 24, 2019
1 parent 4d9f822 commit ee809f5
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 93 deletions.
7 changes: 6 additions & 1 deletion src/bin/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default async function main(argv: string[]): Promise<void> {
});

let first = cmd._.shift();
let isScoped = false;
while (!first || first[0] === '@') {
if (!first) {
console.log(help + '\n');
Expand All @@ -97,8 +98,12 @@ export default async function main(argv: string[]): Promise<void> {
first = command.length
? `:${command.join(':')}`
: (cmd._.shift() as string);

isScoped = true;
}
if (isScoped) {
logger.info('Scope: ' + chalk.bold('@' + state.get('scopes').join(' @')));
}
logger.info('Scope: ' + chalk.bold('@' + state.get('scopes').join(' @')));

// TODO
if (first[0] === ':') {
Expand Down
16 changes: 10 additions & 6 deletions src/state/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import paths, { IPaths } from './paths';
import paths from './paths';
import mergewith from 'lodash.mergewith';
import { IBaseOptions, IScopeOptions } from '~/types';
import { DEFAULT_LOG_LEVEL } from '~/constants';
import { setLevel } from '~/utils/logger';
import { lazy } from 'promist';
import load, { ILoaded } from './load';
import load from './load';
import scope from './scope';
import { IPaths, ILoaded } from './types';

export const states = {
base: {
Expand All @@ -19,7 +20,7 @@ export const states = {
options: {} as IScopeOptions,

internal: {
scopes: ['self'] as string[]
scopes: [] as string[]
}
};

Expand All @@ -41,9 +42,12 @@ export default {
merge();
},
async setScope(name: string): Promise<void> {
const scopeName = await scope(name);
if (scopeName) states.internal.scopes.push(scopeName);
this.setOptions();
const definition = await scope(name);
if (definition) {
states.internal.scopes.push(definition.name);
this.setBase({ file: null, directory: definition.directory });
this.setOptions();
}
},
get(key: keyof TState): any {
return state[key];
Expand Down
9 changes: 2 additions & 7 deletions src/state/load/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
import fs from 'fs-extra';
import { IScripts, IOfType } from '~/types';
import { IOfType } from '~/types';
import { rejects } from 'errorish';
import { IBasePaths } from '~/state/paths';
import { IBasePaths, ILoaded } from '../types';
import hash from 'object-hash';
import readFile from './read-file';

export interface ILoaded {
kpo: IScripts | null;
pkg: IOfType<any> | null;
}

const cache: IOfType<ILoaded> = {};
export default async function load(paths: IBasePaths): Promise<ILoaded> {
const key = hash(paths);
Expand Down
82 changes: 17 additions & 65 deletions src/state/paths/index.ts
Original file line number Diff line number Diff line change
@@ -1,87 +1,39 @@
import path from 'path';
import getFile from './get-file';
import logger from '~/utils/logger';
import { wrap } from '~/utils/errors';
import load from '../load';
import state from '../index';
import up from 'find-up';
import { IPathsOpts, IPaths } from '../types';
import { getSelfPaths, getRootPaths } from './retrieve';

export interface IPathsOpts {
file?: string;
directory?: string;
}

export interface IBasePaths {
kpo: string | null;
pkg: string | null;
bin: string[];
directory: string;
}

export interface IPaths extends IBasePaths {
root: IBasePaths | null;
children: IBasePaths[];
}

/**
* - if no `file` or `directory` are passed, it will **recurse up** from `cwd` to find both the `package.json` and `kpo.scripts` files. If the `package.json` is found closer to `cwd` or no `kpo.scripts` is found, then its `kpo.path` key will be used -if found- to locate the `kpo.scripts` file.
* - if only `file` is passed, it will get the `kpo.scripts` on that location, and **recurse up** to find a `package.json`.
* - if a `file` and a `directory` are passed, it will get the `kpo.scripts` on `file` location, and only use a `package.json` if it's found in `directory`.
* - if only a `directory` is passed, it will try to find both the `kpo.script` and `package.json` files on `directory`; if no `kpo.scripts` is found exactly on directory, it will fall back to the `kpo.path` in `package.json` -if found.
*/
export default async function paths(opts: IPathsOpts): Promise<IPaths> {
// It will be strict if directory exists (it's passed on cli),
// otherwise it will recurse up w/ strict = false.
const base = await trunk(opts, Boolean(opts.directory));
await load(base);
const self = await getSelfPaths(opts);
await load(self);

// has to to called after load to wait for scope options to modify state
const root = await trunk(
{ directory: state.get('root') || path.join(base.directory, '../') },
false
const rootDir = state.get('root');
const root = await getRootPaths(
rootDir || path.join(self.directory, '../')
).catch(async (err) => {
return state.get('root')
? wrap.rejects(err, {
message: `root scope couldn't be retrieved: ${state.get('root')}`
})
: null;
// don't fail if root directory wasn't explicitly passed via options,
// just set as null
if (!rootDir) return null;

return wrap.rejects(err, {
message: `root scope couldn't be retrieved: ${state.get('root')}`
});
});

return {
...base,
...self,
// add also root bin path
bin: [base.bin[0]]
bin: [self.bin[0]]
.concat(root ? root.bin[0] : [])
.concat(base.bin.slice(1))
.concat(self.bin.slice(1))
.concat(root ? root.bin.slice(1) : [])
.filter((x, i, arr) => x && arr.indexOf(x) === i),
root,
children: []
};
}

export async function trunk(
opts: IPathsOpts,
strict: boolean
): Promise<IBasePaths> {
const { kpo, pkg } = await getFile(opts, strict);
let dir = path.parse(pkg || kpo || process.cwd()).dir;

if (kpo) logger.debug('kpo configuration file found at: ' + kpo);
if (pkg) logger.debug('package.json found at: ' + pkg);
if (!kpo && !pkg) {
throw Error(`No file or package.json was found in directory`);
}

return {
kpo: kpo,
pkg: pkg,
directory: dir,
bin: await getBin(dir)
};
}

export async function getBin(dir: string): Promise<string[]> {
const bin = await up('node_modules/.bin', { cwd: dir });
return bin ? [bin].concat(await getBin(path.join(dir, '../'))) : [];
}
7 changes: 7 additions & 0 deletions src/state/paths/retrieve/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import path from 'path';
import up from 'find-up';

export default async function getBin(dir: string): Promise<string[]> {
const bin = await up('node_modules/.bin', { cwd: dir });
return bin ? [bin].concat(await getBin(path.join(dir, '../'))) : [];
}
15 changes: 10 additions & 5 deletions src/state/paths/get-file.ts → src/state/paths/retrieve/files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import { rejects } from 'errorish';
import { FILE_NAME } from '~/constants';
import { find, exists } from '~/utils/file';

export interface IGetFile {
export interface IGetFiles {
kpo: string | null;
pkg: string | null;
}

export default async function getFile(
/**
* - if `strict` is `false`, it will look recursively in `directory` -if passed, otherwise `cwd`- for both `package.json` and `kpo.scripts` files.
* - if `strict` is `true`, files will be expected to be in exactly `directory`.
* - if `file` is provided, that will determine the path for the `kpo.scripts` file, and will make the `directory` to look for the `package.json` its directory, unless otherwise provided.
*/
export default async function getFiles(
opts: {
file?: string;
directory?: string;
},
strict: boolean
): Promise<IGetFile> {
): Promise<IGetFiles> {
const cwd = process.cwd();

const directory =
Expand All @@ -39,7 +44,7 @@ export async function getExplicit(
file: string,
directory: string | undefined,
strict: boolean
): Promise<IGetFile> {
): Promise<IGetFiles> {
const { ext } = path.parse(file);
const validExt = ['.js', '.json', '.yml', '.yaml'].includes(ext);
if (!validExt) return Promise.reject(Error(`Extension ${ext} is not valid`));
Expand All @@ -57,7 +62,7 @@ export async function getExplicit(
export async function getDefault(
directory: string,
strict: boolean
): Promise<IGetFile> {
): Promise<IGetFiles> {
let dir = path.join(path.parse(directory).dir, path.parse(directory).base);

let kpo: string | null | undefined = await find(
Expand Down
17 changes: 17 additions & 0 deletions src/state/paths/retrieve/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IPathsOpts, IBasePaths } from '../../types';
import getPaths from './paths';

/**
* - `file` determines the path for the `kpo.scripts` file; if not passed, it will be resolved from `directory`.
* - `directory` determines the path to look for the `package.json` and `kpo.scripts` files; if passed, files will be expected to be exactly in that directory, otherwise `directory` will be `cwd` and the search will **recurse up** until the root folder is reached. If a `package.json` is found closer to `directory` than any `kpo.scripts` containing a `kpo.path` key, that path for a `kpo.scripts` file will take precedence.
*/
export async function getSelfPaths(opts: IPathsOpts): Promise<IBasePaths> {
return getPaths(opts, Boolean(opts.directory));
}

/**
* Will always recurse up
*/
export async function getRootPaths(directory: string): Promise<IBasePaths> {
return getPaths({ directory }, false);
}
26 changes: 26 additions & 0 deletions src/state/paths/retrieve/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import path from 'path';
import getFiles from './files';
import logger from '~/utils/logger';
import getBin from './bin';
import { IPathsOpts, IBasePaths } from '../../types';

export default async function getPaths(
opts: IPathsOpts,
strict: boolean
): Promise<IBasePaths> {
const { kpo, pkg } = await getFiles(opts, strict);

if (kpo) logger.debug('kpo configuration file found at: ' + kpo);
if (pkg) logger.debug('package.json found at: ' + pkg);
if (!kpo && !pkg) {
throw Error(`No file or package.json was found in directory`);
}

const dir = path.parse((pkg || kpo) as string).dir;
return {
kpo: kpo,
pkg: pkg,
directory: dir,
bin: await getBin(dir)
};
}
14 changes: 5 additions & 9 deletions src/state/scope.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import state from './index';
import logger from '~/utils/logger';
import { IScopeDefinition } from './types';

export default async function setScope(scope: string): Promise<string | null> {
if (scope === 'self') return null;

export default async function scope(
scope: string
): Promise<IScopeDefinition | null> {
const paths = await state.paths();
if (scope === 'root') {
if (paths.root) {
return set('root', paths.root.directory);
return { name: 'root', directory: paths.root.directory };
} else {
logger.debug('root scope was not found and was assigned to self');
return null;
Expand All @@ -16,8 +17,3 @@ export default async function setScope(scope: string): Promise<string | null> {

throw Error(`Scope ${scope} was not found`);
}

export function set(name: string, path: string): string {
state.setBase({ file: null, directory: path });
return name;
}
28 changes: 28 additions & 0 deletions src/state/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { IScripts, IOfType } from '~/types';

export interface IPathsOpts {
file?: string;
directory?: string;
}

export interface IBasePaths {
kpo: string | null;
pkg: string | null;
bin: string[];
directory: string;
}

export interface IPaths extends IBasePaths {
root: IBasePaths | null;
children: IBasePaths[];
}

export interface ILoaded {
kpo: IScripts | null;
pkg: IOfType<any> | null;
}

export interface IScopeDefinition {
name: string;
directory: string;
}

0 comments on commit ee809f5

Please sign in to comment.