diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..df8c170 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,83 @@ +pipeline { + agent { + docker 'node:lts-alpine3.16' + } + + environment { + NPM_REGISTRY = 'https://nexus.nclazz.de/repository/npm_releases' + NPM_CREDENTIALS = credentials('jenkins_nexus') + NPM_MAIL = 'robots@nclazz.de' + APIBUILDER_TOKEN = credentials('apibuilder_token') + } + + options { + timeout(time: 30, unit: "MINUTES") + } + + stages { + stage('Prepare build') { + steps { + sh 'yarn install' + sh "yarn run registry:login --config-path ./.npmrc -u $NPM_CREDENTIALS_USR -p $NPM_CREDENTIALS_PSW -e $NPM_MAIL -r $NPM_REGISTRY" + } + } + stage('Run build') { + steps { + sh 'yarn build:ci' + } + post { + always { + junit( + allowEmptyResults: true, + testResults: '**/**/junit.xml' + ) + } + } + } + stage('Publish npm packages') { + when { + anyOf { + allOf { + branch 'master' + buildingTag() + } + branch 'develop' + } + } + steps { + sh 'yarn run publish:ci' + } + } +// stage('Publish apibuilder specs') { +// steps { +// sh 'yarn publish:apibuilder' +// } +// } + } + post { + always { + cleanWs() + } + failure { + slackSend( + channel: "notifications", + color: "danger", + message: "There is a *build failure* in ${env.JOB_NAME}.\nBuild: ${env.RUN_DISPLAY_URL} " + ) + } + unstable { + slackSend( + channel: "notifications", + color: "warning", + message: "Some tests have failed in ${env.JOB_NAME}.\nBuild: ${env.RUN_DISPLAY_URL} " + ) + } + fixed { + slackSend( + channel: "notifications", + color: "good", + message: "The build ${env.JOB_NAME} completed successfully and is back to normal.\nBuild: ${env.RUN_DISPLAY_URL} " + ) + } + } +} diff --git a/package.json b/package.json index df2c545..ac77093 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "@nclazz/deptracker", + "name": "@nclazz/depresolve", "version": "1.0.0", "description": "Track dependencies in any project", "main": "./dist/index.js", "bin": { - "deptracker": "./dist/index.js" + "depresolve": "./dist/cli/index.js" }, "author": "Niclas Thobaben", "license": "MIT", diff --git a/src/__tests__/deptracker.config.js b/src/__tests__/depresolver.config.js similarity index 87% rename from src/__tests__/deptracker.config.js rename to src/__tests__/depresolver.config.js index d9f44ce..5464fe3 100644 --- a/src/__tests__/deptracker.config.js +++ b/src/__tests__/depresolver.config.js @@ -9,5 +9,8 @@ module.exports = { }, publisher: { npm: npmPublisher({}) + }, + send: { + npm: [ 'npm' ] } } diff --git a/src/__tests__/project.test.ts b/src/__tests__/project.test.ts index ddd9869..26ae57b 100644 --- a/src/__tests__/project.test.ts +++ b/src/__tests__/project.test.ts @@ -1,9 +1,6 @@ import { Project, ProjectRunner, ScmInfo } from '../project' describe('ProjectLoader', () => { - const newLoader = () => { - return new ProjectRunner() - } const newProjectOptions = (override?: Partial): Project => { return { name: 'my_project', @@ -19,30 +16,25 @@ describe('ProjectLoader', () => { describe('validateProject()', () => { it('throws error for missing name', () => { - const loader = newLoader() - expect(() => loader.validateProject(newProjectOptions({ name: undefined }))) + expect(() => ProjectRunner.validateProject(newProjectOptions({ name: undefined }))) .toThrow(/Missing project name/) }) it('throws error for missing scm info', () => { - const loader = newLoader() - expect(() => loader.validateProject(newProjectOptions({ scm: undefined }))) + expect(() => ProjectRunner.validateProject(newProjectOptions({ scm: undefined }))) .toThrow(/Missing scm/) }) it('throws error for invalid scm', () => { - const loader = newLoader() - expect(() => loader.validateProject(newProjectOptions({ scm: { type: 'git' } as ScmInfo }))) + expect(() => ProjectRunner.validateProject(newProjectOptions({ scm: { type: 'git' } as ScmInfo }))) .toThrow(/Missing scm url/) }) it('does not throw error for missing scm type', () => { - const loader = newLoader() - expect(() => loader.validateProject(newProjectOptions({ scm: { url: 'https://some-git.com' } }))) + expect(() => ProjectRunner.validateProject(newProjectOptions({ scm: { url: 'https://some-git.com' } }))) .not.toThrow() }) }) describe('loadProject()', () => { it('loads project from js file', () => { - const runner = newLoader() - runner.loadProject(__dirname + '/deptracker.config.js') + ProjectRunner.loadProject(__dirname + '/depresolver.config.js') }) }) }) diff --git a/src/cli/cmds/resolve.ts b/src/cli/cmds/resolve.ts new file mode 100644 index 0000000..2f36d37 --- /dev/null +++ b/src/cli/cmds/resolve.ts @@ -0,0 +1,34 @@ +import {CommandModule} from 'yargs' +import { ProjectRunner } from '../../project' + +export const cmd: CommandModule = { + command: 'resolve', + describe: 'Resolve dependencies of this project', + handler: async (argv) => { + const runner: ProjectRunner = argv._project_runner as ProjectRunner + const project = runner.project + + console.debug('Start running resolve...') + const result = await runner.resolve() + + console.log(`------------------- ${project.name} -------------------`) + Object.entries(result).forEach(([name,results]) => { + console.log(`Resolver: ${name}`) + results.forEach((result) => { + const { name, type, location, version } = result.current + const { version: rversion } = result.recommended + console.log(` ${name} (${type}) ${version} -> ${rversion} (found in ${location})`) + }) + }) + + console.debug('Finished running resolve.') + }, +} + +export const { + command, + aliases, + describe, + handler, + builder, +} = cmd diff --git a/src/cli/config.ts b/src/cli/config.ts new file mode 100644 index 0000000..48c5f35 --- /dev/null +++ b/src/cli/config.ts @@ -0,0 +1,20 @@ +import path from 'path' +import fs from 'fs' +import { ArgumentsCamelCase } from 'yargs' +import { ProjectRunner } from '../project' + +export const prepareConfig = (argv: ArgumentsCamelCase) => { + const configArg = argv.config as string + const configPath = path.join(process.cwd(), configArg) + + console.debug(`Load configuration from ${configPath}`) + + if (!fs.existsSync(configPath)) { + console.error(`No config not found at ${configPath}!`) + process.exit(1) + } + + console.debug('Create project runner') + const project = ProjectRunner.loadProject(configPath) + argv._project_runner = new ProjectRunner(project) +} diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..ecffad0 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,32 @@ +#!/usr/bin/env node + +import yargs from 'yargs' +import { prepareConfig } from './config' +import { prepareVerbose } from './verbose' + +const runCLI = () => { + // tslint:disable-next-line:no-unused-expression + yargs(process.argv.slice(2)) + .commandDir('cmds') + .options({ + verbose: { + alias: 'v', + describe: 'Enable verbose output', + type: 'boolean', + default: false, + }, + config: { + alias: 'c', + describe: 'Path to depresolve config file', + type: 'string', + default: './depresolve.config.js', + }, + }) + .demandCommand() + .middleware(prepareVerbose) + .middleware(prepareConfig) + .showHelpOnFail(true) + .help() + .argv +} +runCLI() diff --git a/src/cli/verbose.ts b/src/cli/verbose.ts new file mode 100644 index 0000000..a34b831 --- /dev/null +++ b/src/cli/verbose.ts @@ -0,0 +1,16 @@ +import { ArgumentsCamelCase } from 'yargs' + +const _log = console.debug; +const logOverride = function() { + const new_args: any[] = ['[DEBUG]'] + // @ts-ignore + new_args.push.apply(new_args, arguments); + _log.apply(null, new_args); +}; + +export const prepareVerbose = (argv: ArgumentsCamelCase) => { + console.debug = () => {} + if(argv.verbose) { + console.debug = logOverride + } +} diff --git a/src/index.ts b/src/index.ts index 4633d0b..916d532 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,4 @@ -// keep +export * from './npm' +export * from './resolver' +export * from './publisher' +export * from './project' diff --git a/src/project.ts b/src/project.ts index 84c887b..d144efe 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,4 +1,4 @@ -import { Resolver } from './resolver' +import { Resolver, ResolveResult } from './resolver' import { Publisher } from './publisher' export interface Project { @@ -26,13 +26,30 @@ const validateScmInfo = (scm?: ScmInfo) => { export class ProjectRunner { - public loadProject(path: string) { + readonly project: Project + + constructor(project: Project) { + this.project = project + } + + public async resolve(limit?: string[]): Promise> { + const results: Record = {} + const promises = Object.entries(this.project.resolver) + .filter(([name,]) => !limit || limit.includes(name)) + .map(async ([name, resolver]) => { + results[name] = await resolver.resolve() + }) + await Promise.all(promises) + return results + } + + public static loadProject(path: string) { const project = require(path) as Project - this.validateProject(project) + ProjectRunner.validateProject(project) return project as Project } - public validateProject(project: Project) { + public static validateProject(project: Project) { if(!project.name) { throw Error('Missing project name') }