initial CLI implementation
nclazz/depresolve/pipeline/head This commit looks good Details

master
Niclas Thobaben 2022-11-27 11:46:28 +01:00
parent 20e38492c9
commit e45a3118f5
10 changed files with 220 additions and 20 deletions

83
Jenkinsfile vendored 100644
View File

@ -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} "
)
}
}
}

View File

@ -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",

View File

@ -9,5 +9,8 @@ module.exports = {
},
publisher: {
npm: npmPublisher({})
},
send: {
npm: [ 'npm' ]
}
}

View File

@ -1,9 +1,6 @@
import { Project, ProjectRunner, ScmInfo } from '../project'
describe('ProjectLoader', () => {
const newLoader = () => {
return new ProjectRunner()
}
const newProjectOptions = (override?: Partial<Project>): 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')
})
})
})

View File

@ -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

20
src/cli/config.ts 100644
View File

@ -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)
}

32
src/cli/index.ts 100644
View File

@ -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()

16
src/cli/verbose.ts 100644
View File

@ -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
}
}

View File

@ -1 +1,4 @@
// keep
export * from './npm'
export * from './resolver'
export * from './publisher'
export * from './project'

View File

@ -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<Record<string, ResolveResult[]>> {
const results: Record<string, ResolveResult[]> = {}
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')
}