From 4cb995745a451a4c3f8bbb3461a75b49a66cbd0b Mon Sep 17 00:00:00 2001 From: Rafa Mel Date: Fri, 3 May 2019 18:29:37 +0200 Subject: [PATCH] feat: properly passes state between kpo instances and processes via KPO_STATE env var --- src/bin/main/index.ts | 6 +- src/core/index.ts | 279 +++++++++++++++----------- src/core/load.ts | 48 +---- src/core/options.ts | 33 +-- src/core/paths/files.ts | 10 +- src/core/paths/index.ts | 3 +- src/core/paths/paths.ts | 4 +- src/core/scope/children/from-globs.ts | 1 + src/core/types.ts | 2 +- src/core/wrap.ts | 15 ++ src/index.ts | 1 - src/public/exec/stream.ts | 5 +- src/public/fs/copy.ts | 10 +- src/public/fs/mkdir.ts | 2 +- src/public/fs/move.ts | 10 +- src/public/fs/remove.ts | 2 +- src/public/fs/rw.ts | 6 +- src/public/fs/write.ts | 6 +- src/public/kpo/list.ts | 6 +- src/public/tags/exists.ts | 3 +- src/types.ts | 2 +- src/utils/errors.ts | 2 +- src/utils/object-base.ts | 9 + src/utils/version-range.ts | 23 +++ 24 files changed, 263 insertions(+), 225 deletions(-) create mode 100644 src/core/wrap.ts create mode 100644 src/utils/object-base.ts create mode 100644 src/utils/version-range.ts diff --git a/src/bin/main/index.ts b/src/bin/main/index.ts index a6bf356..df2ed89 100644 --- a/src/bin/main/index.ts +++ b/src/bin/main/index.ts @@ -96,7 +96,6 @@ export default async function main(argv: string[]): Promise { const scope = command.shift() as string; await core.setScope(scope === '@' ? ['root'] : [scope.slice(1)]); - first = command.length ? `:${command.join(':')}` : (cmd._.shift() as string); @@ -110,12 +109,11 @@ export default async function main(argv: string[]): Promise { } // Log full command to be run w/ resolved scopes - const scopes = core.state.scopes; + const scopes = await core.scopes(); logger.info( chalk.bold('kpo') + (scopes.length ? chalk.bold.yellow(' @' + scopes.join(' @')) : '') + - chalk.bold.blue(' ' + first) + - ` ${join(cmd._)}` + `${chalk.bold.blue(' ' + first)} ${join(cmd._)}` ); switch (first) { diff --git a/src/core/index.ts b/src/core/index.ts index fd496a6..7d2a21c 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -13,147 +13,182 @@ import logger from '~/utils/logger'; import { TCoreOptions, IExecOptions, TScript } from '~/types'; import { rejects } from 'errorish'; import { absolute } from '~/utils/file'; +import wrapCore from './wrap'; import { loadPackage } from 'cli-belt'; -import { clean } from 'semver'; +import ob from '~/utils/object-base'; +import versionRange from '~/utils/version-range'; export interface ICoreState { + version: string | null; scopes: string[]; + cwd: string; + paths: IPaths; } export const state: ICoreState = { - scopes: [] + version: null, + scopes: [], + cwd: process.cwd(), + paths: { + directory: process.cwd(), + kpo: null, + pkg: null + } }; +let loaded: ILoaded = { kpo: null, pkg: null }; + function cache(fn: () => T): () => T { return _cache(() => options.id, fn); } -// Core changes might constitute major version changes even if internal, as -// core is used overwritten for different kpo instances on load (requireLocal) -const core = { - get options() { - return options; - }, - get state(): ICoreState { - return state; - }, - async get(key: T): Promise { - // we're ensuring we've loaded user options when - // any option is requested - await core.load(); - return options.raw()[key]; - }, - paths: cache(async function(): Promise { - const opts = options.raw(); - return getSelfPaths({ - file: opts.file || undefined, - directory: opts.directory || undefined - }); - }), - load: cache(async function(): Promise { - return load(await core.paths(), options.raw(), await core.version()); - }), - cwd: cache(async function(): Promise { - const cwd = await core.get('cwd'); - const paths = await core.paths(); - if (!cwd) return paths.directory; +// Core changes might constitute major version changes even if internal, +// as core state is overwritten for different kpo instances below +const core = wrapCore( + // These will run in order before any core function call + [ + async function initialize(): Promise { + if (state.version) return; - // as cwd is a IScopeOptions property -it can't be set on cli- - // if it is set and it's not absolute, it must be relative - // to a kpo scripts file -if on package.json it was already set as absolute - return absolute({ - path: cwd, - cwd: paths.kpo ? path.parse(paths.kpo).dir : paths.directory - }); - }), - root: cache(async function(): Promise { - const cwd = await core.cwd(); + const pkg = await loadPackage(__dirname, { title: false }); + if (!pkg.version) throw Error(`kpo version couldn't be retrieved`); + state.version = pkg.version; - return getRootPaths({ - cwd, - root: await core.get('root') - }); - }), - children: cache(async function(): Promise { - const paths = await core.paths(); - const cwd = await core.cwd(); - const children = await core.get('children'); + if (process.env.KPO_STATE) { + const decoded = ob.decode(process.env.KPO_STATE); + versionRange( + decoded && decoded.core && decoded.core.version, + pkg.version + ); + Object.assign(state, decoded.core); + options.setBase(decoded.options); - return getChildren( - { cwd, pkg: paths.pkg ? path.parse(paths.pkg).dir : cwd }, - children - ); - }), - bin: cache(async function(): Promise { - const cwd = await core.cwd(); - const root = await core.root(); - return root ? getBin(cwd, root.directory) : getBin(cwd); - }), - tasks: cache(async function(): Promise { - const { kpo, pkg } = await core.load(); - return getAllTasks(kpo || undefined, pkg || undefined); - }), - async task(path: string): Promise { - const { kpo, pkg } = await core.load(); - return getTask(path, kpo || undefined, pkg || undefined); - }, - async run( - script: TScript, - args: string[], - opts?: IExecOptions - ): Promise { - return run(script, async (item) => { - return typeof item === 'string' - ? core.exec(item, args, false, opts) - : item(args); - }).catch(rejects); - }, - async exec( - command: string, - args: string[], - fork: boolean, - opts: IExecOptions = {} - ): Promise { - const cwd = opts.cwd - ? absolute({ path: opts.cwd, cwd: await core.cwd() }) - : await core.cwd(); - const bin = opts.cwd ? await getBin(cwd) : await core.bin(); - const env = opts.env - ? Object.assign({}, await core.get('env'), opts.env) - : await core.get('env'); - return exec(command, args, fork, cwd, bin, env); - }, - async setScope(names: string[]): Promise { - const root = await core.root(); + process.chdir(state.paths.directory); + } + }, + cache(async function(): Promise { + state.paths = await getSelfPaths({ + cwd: state.cwd, + directory: options.raw.directory || undefined, + file: options.raw.file || undefined + }); + process.chdir(state.paths.directory); - const { next, scope } = await setScope( - names, - { root: root ? root.directory : undefined }, - await core.children() - ); - if (scope) { - logger.debug(`${scope.name} scope set`); - // keep track of scope branches - state.scopes = state.scopes.concat(scope.name); - // set current directory as the the one of the scope - options.setCli({ file: null, directory: scope.directory }); - // reset options - options.resetScope(); - } - // Continue recursively - if (next.length) return core.setScope(next); - }, - async version(): Promise { - const pkg = await loadPackage(__dirname, { title: false }); - if (!pkg || !pkg.version) { - throw Error(`kpo version couldn't be retrieved`); - } + // if any options change on load that's no problem, the only + // path option that can change is cwd, which is dealt with below + loaded = await load(state.paths); - const version = clean(pkg.version); - if (!version) throw Error(`kpo version couldn't be retrieved`); + // options cwd can only be set on scope options (on load()) + state.paths.directory = options.raw.cwd + ? absolute({ + path: options.raw.cwd, + // we're setting it relative to the file + cwd: state.paths.kpo + ? path.parse(state.paths.kpo).dir + : state.paths.directory + }) + : state.paths.directory; + process.chdir(state.paths.directory); - return version; + process.env.KPO_STATE = ob.encode({ + core: state, + options: options.raw + }); + }) + ], + // Core functions + { + async get(key: T): Promise { + return options.raw[key]; + }, + async scopes(): Promise { + return state.scopes; + }, + // TODO use process.cwd() to obtain directory on public fns + async paths(): Promise { + return state.paths; + }, + async load(): Promise { + return loaded; + }, + root: cache(async function(): Promise { + return getRootPaths({ + cwd: state.paths.directory, + root: await core.get('root') + }); + }), + children: cache(async function(): Promise { + const children = await core.get('children'); + + return getChildren( + { + cwd: state.paths.directory, + pkg: state.paths.pkg + ? path.parse(state.paths.pkg).dir + : state.paths.directory + }, + children + ); + }), + bin: cache(async function(): Promise { + const root = await core.root(); + return root + ? getBin(state.paths.directory, root.directory) + : getBin(state.paths.directory); + }), + tasks: cache(async function(): Promise { + return getAllTasks(loaded.kpo || undefined, loaded.pkg || undefined); + }), + async task(path: string): Promise { + return getTask(path, loaded.kpo || undefined, loaded.pkg || undefined); + }, + async run( + script: TScript, + args: string[], + opts?: IExecOptions + ): Promise { + return run(script, async (item) => { + return typeof item === 'string' + ? core.exec(item, args, false, opts) + : item(args); + }).catch(rejects); + }, + async exec( + command: string, + args: string[], + fork: boolean, + opts: IExecOptions = {} + ): Promise { + const cwd = opts.cwd + ? absolute({ path: opts.cwd, cwd: state.paths.directory }) + : state.paths.directory; + const bin = opts.cwd ? await getBin(cwd) : await core.bin(); + const env = opts.env + ? Object.assign({}, await core.get('env'), opts.env) + : await core.get('env'); + return exec(command, args, fork, cwd, bin, env); + }, + async setScope(names: string[]): Promise { + const root = await core.root(); + + const { next, scope } = await setScope( + names, + { root: root ? root.directory : undefined }, + await core.children() + ); + if (scope) { + logger.debug(`${scope.name} scope set`); + // keep track of scope branches + state.scopes = state.scopes.concat(scope.name); + // set current directory as the the one of the scope + options.setCli({ file: null, directory: scope.directory }); + // reset options + options.resetScope(); + } + // Continue recursively + if (next.length) return core.setScope(next); + } } -}; +); export { core as default, options }; diff --git a/src/core/load.ts b/src/core/load.ts index 55465a4..70386a3 100644 --- a/src/core/load.ts +++ b/src/core/load.ts @@ -4,17 +4,12 @@ import yaml from 'js-yaml'; import { rejects } from 'errorish'; import errors from '~/utils/errors'; import { ILoaded, IPaths } from './types'; -import { IOfType, IPackageOptions, TCoreOptions } from '~/types'; +import { IOfType, IPackageOptions } from '~/types'; import options from './options'; import { absolute } from '~/utils/file'; -import { diff } from 'semver'; import * as _public from '../public'; -export default async function load( - paths: IPaths, - raw: TCoreOptions, - version: string -): Promise { +export default async function load(paths: IPaths): Promise { // pkg must be loaded first to set options first, if present at key `kpo` const pkg = paths.pkg ? await fs @@ -23,21 +18,17 @@ export default async function load( .catch(rejects) : null; - const kpo = paths.kpo ? await loadFile(paths.kpo, raw, version) : null; + const kpo = paths.kpo ? await loadFile(paths.kpo) : null; return { kpo, pkg }; } -export async function loadFile( - file: string, - raw: TCoreOptions, - version: string -): Promise | null> { +export async function loadFile(file: string): Promise | null> { const { ext } = path.parse(file); switch (ext) { case '.js': - return requireLocal(file, raw, version); + return requireLocal(file); case '.json': return fs .readJSON(file) @@ -78,11 +69,7 @@ export function processPkg(file: string, pkg: IOfType): IOfType { return pkg; } -export async function requireLocal( - file: string, - raw: TCoreOptions, - version: string -): Promise> { +export async function requireLocal(file: string): Promise> { // Ensure local kpo has equal state let kpoPath: string | null = null; try { @@ -90,29 +77,8 @@ export async function requireLocal( } catch (_) {} if (kpoPath) { const local = errors.open.throws(() => require(kpoPath as string)); - - if (!local || !local.core || !local.core.version) { - throw Error(`Local kpo version doesn't match executing instance version`); - } - - const localVersion = await local.core.version(); - const verDiff = diff(localVersion, version); - // Error out if difference is a major version or we're on v0.x.x - if ( - verDiff === 'major' || - verDiff === 'premajor' || - (verDiff && version[0] === '0') - ) { - throw Error( - `Local kpo version (${localVersion})` + - ` doesn't match executing instance version (${version})` - ); - } - - // Overwrite options - local.core.options.setBase(raw, 'post'); // Overwrite error constructors and helpers - Object.assign(local.errors, errors); + if (local && local.errors) Object.assign(local.errors, errors); } const scripts = errors.open.throws(() => require(file)); diff --git a/src/core/options.ts b/src/core/options.ts index d3b80f6..42eb81c 100644 --- a/src/core/options.ts +++ b/src/core/options.ts @@ -4,10 +4,9 @@ import { setLevel } from '~/utils/logger'; import hash from 'object-hash'; // Option changes should constitute major version changes even if internal, -// as they're overwritten for different kpo instances on load (requireLocal) +// as they're overwritten for different kpo instances on core load export const state = { base: { - force: 0, file: null, directory: null, env: {}, @@ -18,22 +17,22 @@ export const state = { scope: {} as IScopeOptions }; -let id = 'INIT'; -if (!process.env.KPO_STATE_ID) process.env.KPO_STATE_ID = id; - +let id = ''; +let force = 0; let options: TCoreOptions = {}; +merge(); export default { get id(): string { return id; }, - raw(): TCoreOptions { - if (id === 'INIT') merge(); + // Raw should only be called from core (after initialization) + get raw(): TCoreOptions { return Object.assign({}, options); }, - setBase(opts: TCoreOptions, verify?: 'post' | 'pre'): void { + setBase(opts: TCoreOptions): void { Object.assign(state.base, opts); - merge(verify); + merge(); }, setCli(opts: ICliOptions): void { Object.assign(state.cli, stripUndefined(opts)); @@ -48,14 +47,12 @@ export default { merge(); }, forceUpdate(): void { - state.base.force = state.base.force ? state.base.force + 1 : 0; + force += 1; merge(); } }; -function merge(verify: 'post' | 'pre' = 'pre'): void { - if (verify === 'pre') verifyId(); - +function merge(): void { // merge base and scope options = Object.assign({}, state.base, state.scope, state.cli, { env: Object.assign({}, state.base.env, state.scope.env, state.cli.env) @@ -68,9 +65,7 @@ function merge(verify: 'post' | 'pre' = 'pre'): void { // Set logging level if (options.log) setLevel(options.log); // Set id to object hash - id = hash(options); - if (verify === 'post') verifyId(); - process.env.KPO_STATE_ID = id; + id = hash(options) + force; } function stripUndefined(obj: IOfType): IOfType { @@ -79,9 +74,3 @@ function stripUndefined(obj: IOfType): IOfType { return acc; }, {}); } - -function verifyId(): void { - if (id !== process.env.KPO_STATE_ID) { - throw Error(`Local kpo instance doesn't match executing instance`); - } -} diff --git a/src/core/paths/files.ts b/src/core/paths/files.ts index aa9a1b6..11de9cc 100644 --- a/src/core/paths/files.ts +++ b/src/core/paths/files.ts @@ -16,21 +16,21 @@ export interface IGetFiles { */ export default async function getFiles( opts: { + cwd: string; file?: string; directory?: string; }, strict: boolean ): Promise { - const cwd = process.cwd(); - - const directory = opts.directory && absolute({ path: opts.directory, cwd }); + const directory = + opts.directory && absolute({ path: opts.directory, cwd: opts.cwd }); const file = - opts.file && absolute({ path: opts.file, cwd: directory || cwd }); + opts.file && absolute({ path: opts.file, cwd: directory || opts.cwd }); return file ? getExplicit(file, directory, strict) - : getDefault(directory || cwd, strict); + : getDefault(directory || opts.cwd, strict); } export async function getExplicit( diff --git a/src/core/paths/index.ts b/src/core/paths/index.ts index eb60c18..77642bf 100644 --- a/src/core/paths/index.ts +++ b/src/core/paths/index.ts @@ -9,6 +9,7 @@ import { absolute } from '~/utils/file'; * - `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: { + cwd: string; file?: string; directory?: string; }): Promise { @@ -30,7 +31,7 @@ export async function getRootPaths(directories: { : path.join(cwd, '../'); try { - return await getPaths({ directory }, Boolean(root)); + return await getPaths({ directory, cwd }, Boolean(root)); } catch (err) { if (!root) return null; throw new errors.WrappedError( diff --git a/src/core/paths/paths.ts b/src/core/paths/paths.ts index 24bc490..9f9a64f 100644 --- a/src/core/paths/paths.ts +++ b/src/core/paths/paths.ts @@ -4,7 +4,7 @@ import logger from '~/utils/logger'; import { IPaths } from '../types'; export default async function getPaths( - opts: { file?: string; directory?: string }, + opts: { cwd: string; file?: string; directory?: string }, strict: boolean ): Promise { const { kpo, pkg } = await getFiles(opts, strict); @@ -17,8 +17,8 @@ export default async function getPaths( const dir = path.parse((pkg || kpo) as string).dir; return { - kpo: kpo, pkg: pkg, + kpo: kpo, directory: dir }; } diff --git a/src/core/scope/children/from-globs.ts b/src/core/scope/children/from-globs.ts index b1b534d..6743493 100644 --- a/src/core/scope/children/from-globs.ts +++ b/src/core/scope/children/from-globs.ts @@ -19,6 +19,7 @@ export default async function getChildrenFromGlobs( return acc.concat(arr); }, []); + // TODO verify names don't conflict // filter and make into IChild return filter(dirs).map((dir) => ({ name: path.parse(dir).name, diff --git a/src/core/types.ts b/src/core/types.ts index c093ad9..171c2ec 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -1,8 +1,8 @@ import { IScripts, IOfType, TScript } from '~/types'; export interface IPaths { - kpo: string | null; pkg: string | null; + kpo: string | null; directory: string; } diff --git a/src/core/wrap.ts b/src/core/wrap.ts new file mode 100644 index 0000000..d49fcf0 --- /dev/null +++ b/src/core/wrap.ts @@ -0,0 +1,15 @@ +import { IOfType } from '~/types'; + +export default function wrapCore< + T extends IOfType<(...args: any[]) => Promise> +>(before: Array<() => Promise>, core: T): T { + return Object.entries(core).reduce((acc: Partial, [key, value]) => { + acc[key] = async (...args: any[]) => { + for (let fn of before) { + await fn(); + } + return value(...args); + }; + return acc; + }, {}) as T; +} diff --git a/src/index.ts b/src/index.ts index cc8d697..c142e00 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ export * from './public'; -export { default as core } from './core'; export { default as errors } from './utils/errors'; export * from './types'; diff --git a/src/public/exec/stream.ts b/src/public/exec/stream.ts index 9d6ca37..4b05248 100644 --- a/src/public/exec/stream.ts +++ b/src/public/exec/stream.ts @@ -63,7 +63,7 @@ function stream( if (!children.length) throw Error(`No project children selected`); const commands = children.map((child) => { - return [NODE_PATH, KPO_PATH, '-d', child.directory].concat(argv); + return [NODE_PATH, KPO_PATH, '@' + child.name].concat(argv); }); await (options.parallel @@ -80,7 +80,8 @@ function stream( NODE_PATH, '-e', oneLine`console.log( - "\\nScope: ${chalk.bold.yellow('@' + children[i].name)}" + "${i === 0 ? 'Scope:' : '\\nScope:'} + ${chalk.bold.yellow('@' + children[i].name)}" )` ]), join(cmd) diff --git a/src/public/fs/copy.ts b/src/public/fs/copy.ts index 1120dd0..ab550c2 100644 --- a/src/public/fs/copy.ts +++ b/src/public/fs/copy.ts @@ -65,13 +65,13 @@ export async function trunk( const filter: TCopyFilterFn = args.find((x) => typeof x === 'function') || (() => true); - const cwd = await core.cwd(); - src = absolute({ path: src, cwd }); - dest = absolute({ path: dest, cwd }); + const paths = await core.paths(); + src = absolute({ path: src, cwd: paths.directory }); + dest = absolute({ path: dest, cwd: paths.directory }); const relatives = { - src: './' + path.relative(cwd, src), - dest: './' + path.relative(cwd, dest) + src: './' + path.relative(paths.directory, src), + dest: './' + path.relative(paths.directory, dest) }; const srcExist = await exists(src, { fail: options.fail }); diff --git a/src/public/fs/mkdir.ts b/src/public/fs/mkdir.ts index 79d981d..2321bd4 100644 --- a/src/public/fs/mkdir.ts +++ b/src/public/fs/mkdir.ts @@ -23,7 +23,7 @@ function mkdir( options: IFsOptions = {} ): () => Promise { return async () => { - const cwd = await core.cwd(); + const cwd = await core.paths().then((paths) => paths.directory); paths = Array.isArray(paths) ? paths : [paths]; paths = paths.map((path) => absolute({ path, cwd })); diff --git a/src/public/fs/move.ts b/src/public/fs/move.ts index 7e20ba9..f6dee4a 100644 --- a/src/public/fs/move.ts +++ b/src/public/fs/move.ts @@ -39,13 +39,13 @@ export async function trunk( ): Promise { options = Object.assign({ overwrite: true }, options); - const cwd = await core.cwd(); - src = absolute({ path: src, cwd }); - dest = absolute({ path: dest, cwd }); + const paths = await core.paths(); + src = absolute({ path: src, cwd: paths.directory }); + dest = absolute({ path: dest, cwd: paths.directory }); const relatives = { - src: './' + path.relative(cwd, src), - dest: './' + path.relative(cwd, dest) + src: './' + path.relative(paths.directory, src), + dest: './' + path.relative(paths.directory, dest) }; const srcExist = await exists(src, { fail: options.fail }); diff --git a/src/public/fs/remove.ts b/src/public/fs/remove.ts index 1680889..9711313 100644 --- a/src/public/fs/remove.ts +++ b/src/public/fs/remove.ts @@ -23,7 +23,7 @@ function remove( options: IFsOptions = {} ): () => Promise { return async () => { - const cwd = await core.cwd(); + const cwd = await core.paths().then((paths) => paths.directory); paths = Array.isArray(paths) ? paths : [paths]; paths = paths.map((path) => absolute({ path, cwd })); diff --git a/src/public/fs/rw.ts b/src/public/fs/rw.ts index c14d73c..210eee3 100644 --- a/src/public/fs/rw.ts +++ b/src/public/fs/rw.ts @@ -21,9 +21,9 @@ function rw( options: IFsOptions = {} ): () => Promise { return async () => { - const cwd = await core.cwd(); - file = absolute({ path: file, cwd }); - const relative = './' + path.relative(cwd, file); + const paths = await core.paths(); + file = absolute({ path: file, cwd: paths.directory }); + const relative = './' + path.relative(paths.directory, file); const doesExist = await exists(file, { fail: options.fail }); diff --git a/src/public/fs/write.ts b/src/public/fs/write.ts index 74e8194..1e92fdc 100644 --- a/src/public/fs/write.ts +++ b/src/public/fs/write.ts @@ -36,9 +36,9 @@ function write(file: string, ...args: any[]): () => Promise { args.find((x) => typeof x === 'object') || {} ); - const cwd = await core.cwd(); - file = absolute({ path: file, cwd }); - const relative = './' + path.relative(cwd, file); + const paths = await core.paths(); + file = absolute({ path: file, cwd: paths.directory }); + const relative = './' + path.relative(paths.directory, file); const doesExist = await exists(file); if (options.fail && doesExist) { diff --git a/src/public/kpo/list.ts b/src/public/kpo/list.ts index bef7711..1fc0355 100644 --- a/src/public/kpo/list.ts +++ b/src/public/kpo/list.ts @@ -64,16 +64,16 @@ export function fromTasks(tasks: ITasks): string { /** @hidden */ export async function fromScopes(): Promise { - const cwd = await core.cwd(); + const paths = await core.paths(); const root = await core.root(); const scopes = await core.children(); let rows = scopes.map((child) => [ child.name, - path.relative(cwd, child.directory) + path.relative(paths.directory, child.directory) ]); if (root) { - rows.unshift(['root', path.relative(cwd, root.directory)]); + rows.unshift(['root', path.relative(paths.directory, root.directory)]); } const nonUnique = rows diff --git a/src/public/tags/exists.ts b/src/public/tags/exists.ts index 0c57679..9c2edf1 100644 --- a/src/public/tags/exists.ts +++ b/src/public/tags/exists.ts @@ -20,9 +20,10 @@ function exists( */ function exists(...args: any[]): () => Promise { return async () => { + const paths = await core.paths(); const file = absolute({ path: asTag(args.shift(), ...args), - cwd: await core.cwd() + cwd: paths.directory }); await _exists(file, { fail: true }); diff --git a/src/types.ts b/src/types.ts index 8e14aea..4a57e46 100644 --- a/src/types.ts +++ b/src/types.ts @@ -128,7 +128,7 @@ export interface ICliOptions extends IOptions { directory?: string | null; } -export type TCoreOptions = ICliOptions & IPackageOptions & { force?: number }; +export type TCoreOptions = ICliOptions & IPackageOptions; /** * A project children scopes, defined either: diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 5cb52fc..ea5a318 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,7 +1,7 @@ import { scope, Errorish } from 'errorish'; // Error changes might constitute major version changes even if internal, -// as they're overwritten for different kpo instances on load (requireLocal) +// as they're overwritten for different kpo instances on core load class CustomError extends Errorish {} diff --git a/src/utils/object-base.ts b/src/utils/object-base.ts new file mode 100644 index 0000000..4624af8 --- /dev/null +++ b/src/utils/object-base.ts @@ -0,0 +1,9 @@ +function encode(value: any): string { + return Buffer.from(JSON.stringify(value)).toString('base64'); +} + +function decode(value: string): any { + return JSON.parse(String(Buffer.from(value, 'base64'))); +} + +export default { encode, decode }; diff --git a/src/utils/version-range.ts b/src/utils/version-range.ts new file mode 100644 index 0000000..f77bf5d --- /dev/null +++ b/src/utils/version-range.ts @@ -0,0 +1,23 @@ +import { clean, diff } from 'semver'; + +export default function versionRange( + executable?: string, + local?: string +): void { + const versions = [executable && clean(executable), local && clean(local)]; + if (!versions[0] || !versions[1]) throw Error(`Version could not be parsed`); + + const release = diff(versions[0] as string, versions[1] as string); + + // Error out if difference is a major version or we're on v0.x.x + if ( + release === 'major' || + release === 'premajor' || + (release && (versions[0] as string)[0] === '0') + ) { + throw Error( + `Local kpo version (${versions[1]})` + + ` doesn't match executing version (${versions[0]})` + ); + } +}