Skip to content

Commit

Permalink
Support SDK-style projects with a Version
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Oct 14, 2020
1 parent a17e9d7 commit fab97c7
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 59 deletions.
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dotnet-bump

**CLI to increment and tag assembly version(s) in a .NET project.**
**CLI to increment and tag assembly version(s) in a .NET project. Supports SDK-style and non-SDK projects.**

[![npm status](http://img.shields.io/npm/v/dotnet-bump.svg)](https://www.npmjs.org/package/dotnet-bump)
[![node](https://img.shields.io/node/v/dotnet-bump.svg)](https://www.npmjs.org/package/dotnet-bump)
Expand All @@ -23,15 +23,15 @@

```
> dotnet-bump minor --dry-run
- Would stage Foo\Properties\AssemblyInfo.cs
- Would stage FooTests\Properties\AssemblyInfo.cs
- Would stage Foo\Foo.csproj
- Would stage Bar\Properties\AssemblyInfo.cs
- Would commit and tag v1.1.0
```

```
> dotnet-bump minor
- Stage Foo\Properties\AssemblyInfo.cs
- Stage FooTests\Properties\AssemblyInfo.cs
- Stage Foo\Foo.csproj
- Stage Bar\Properties\AssemblyInfo.cs
- Commit and tag v1.1.0
```

Expand All @@ -43,13 +43,16 @@ dotnet-bump <target> [options] [file..]

Bump to `target` version, one of:

- `major`, `minor`, `patch`;
- a specific version like 2.4.0 (must be [semver](https://semver.org/)).
- A release type: `major`, `minor`, `patch`, `premajor`, `preminor`, `prepatch`, `prerelease`
- The `major` type bumps the major version (for example `2.4.1 => 3.0.0`); `minor` and `patch` work the same way.
- The `premajor` type bumps the version up to the next major version and down to a prerelease of that major version; `preminor` and `prepatch` work the same way.
- The `prerelease` type works the same as `prepatch` if the input version is a non-prerelease. If the input is already a prerelease then it's simply incremented (for example `4.0.0-rc.2 => 4.0.0-rc.3`).
- A specific version like 2.4.0 (must be [semver](https://semver.org/)).

Files can be glob patterns or paths to a:

- Visual Studio Solution (`*.sln`) (parsed to find projects)
- Project (`*.csproj` or `*.fsproj`) (parsed to find `AssemblyInfo`)
- Project (`*.csproj` or `*.fsproj`) (parsed to find a `Version` element or `AssemblyInfo` file)
- C# or F# source code file;
- Directory containing any of the above.

Expand Down
61 changes: 22 additions & 39 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
const semver = require('semver')
const findRoots = require('common-roots')
const isDirty = require('is-dirty')
const Assembly = require('assembly-source')
const after = require('after')
const series = require('run-series')
const cp = require('child_process')
const path = require('path')
const fs = require('fs')
const Emitter = require('events').EventEmitter
const Finder = require('./lib/finder')

const TARGETS = new Set(['major', 'minor', 'patch'])
const TARGETS = new Set(['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease'])
const kLog = Symbol('kLog')

module.exports = class Updater extends Emitter {
Expand Down Expand Up @@ -57,7 +55,6 @@ module.exports = class Updater extends Emitter {

for (const file of files) {
file.root = mapping[file.path]
file.relative = path.relative(file.root, file.path)

if (rootFiles.has(file.root)) rootFiles.get(file.root).push(file)
else rootFiles.set(file.root, [file])
Expand All @@ -75,8 +72,8 @@ module.exports = class Updater extends Emitter {
}

series([
(next) => readAssemblies(files, next),
(next) => updateAssemblies(files, target, next),
(next) => readFiles(files, next),
(next) => updateFiles(files, target, next),
(next) => this.verify(files, next),
(next) => this.save(files, next),
(next) => this.stage(files, next),
Expand All @@ -91,10 +88,6 @@ module.exports = class Updater extends Emitter {
const counts = {}

for (const { version } of files) {
if (!version) {
throw new Error('Version missing on instance')
}

counts[version] = (counts[version] || 0) + 1
}

Expand All @@ -113,7 +106,7 @@ module.exports = class Updater extends Emitter {
}
}

return done(new Error(`Version mismatch between assemblies:\n\n${lines.join('\n')}`))
return done(new Error(`Version mismatch between files:\n\n${lines.join('\n')}`))
}

done()
Expand All @@ -125,7 +118,7 @@ module.exports = class Updater extends Emitter {
const next = after(files.length, done)

for (const file of files) {
fs.writeFile(file.path, file.assembly.toSource(), next)
file.write(next)
}
}

Expand Down Expand Up @@ -206,28 +199,26 @@ function dirtyWorkingTrees (roots, done) {
}
}

function readAssemblies (files, done) {
function readFiles (files, done) {
const next = after(files.length, done)

for (const file of files) {
fs.readFile(file.path, 'utf8', (err, source) => {
if (err) return next(err)

file.assembly = Assembly(source)
next()
})
file.read(next)
}
}

function updateAssemblies (files, target, done) {
for (const file of files) {
const assembly = file.assembly
const currentRaw = assembly.get('AssemblyVersion')
function updateFiles (files, target, done) {
for (let i = 0; i < files.length; i++) {
const file = files[i]
const currentRaw = file.version

if (!currentRaw) {
files.splice(i--, 1)
continue
}

if (currentRaw == null) {
return done(new Error(`Could not find AssemblyVersion in ${shortPath(file.path)}`))
} else if (typeof currentRaw !== 'string') {
return done(new Error(`AssemblyVersion is not a string in ${shortPath(file.path)}`))
if (typeof currentRaw !== 'string') {
return done(new Error(`Version is not a string in ${shortPath(file.path)}`))
}

const hasRevision = /^\d+\.\d+\.\d+\.\d+$/.test(currentRaw)
Expand All @@ -246,23 +237,15 @@ function updateAssemblies (files, target, done) {

if (current === next) {
return done(new Error(`Target is equal to current version ${currentRaw} in ${shortPath(file.path)}`))
} else {
assembly.set('AssemblyVersion', next)
}

for (const additional of ['AssemblyFileVersion', 'AssemblyInformationalVersion']) {
const value = assembly.get(additional)

if (value === current || value === currentRaw) {
assembly.set(additional, next)
} else if (value != null) {
return done(new Error(`${additional} mismatch: ${JSON.stringify(value)} in ${shortPath(file.path)}`))
}
}

file.version = next
}

if (!files.length) {
return done(new Error('None of the files contain a version'))
}

done()
}

Expand Down
50 changes: 50 additions & 0 deletions lib/abstract-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict'

const path = require('path')

const kRoot = Symbol('kRoot')
const kRelative = Symbol('kRelative')

class AbstractFile {
constructor (path) {
this.path = path
this[kRoot] = null
this[kRelative] = null
}

set root (root) {
this[kRoot] = root
this[kRelative] = path.relative(root, this.path)
}

get root () {
return this[kRoot]
}

get relative () {
return this[kRelative]
}

read (callback) {
this[AbstractFile.read](callback)
}

write (callback) {
this[AbstractFile.write](callback)
}

get version () {
return this[AbstractFile.getVersion]()
}

set version (version) {
this[AbstractFile.setVersion](version)
}
}

AbstractFile.read = Symbol('read')
AbstractFile.write = Symbol('write')
AbstractFile.getVersion = Symbol('getVersion')
AbstractFile.setVersion = Symbol('setVersion')

module.exports = AbstractFile
43 changes: 43 additions & 0 deletions lib/assembly-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict'

const Assembly = require('assembly-source')
const fs = require('fs')
const AbstractFile = require('./abstract-file')
const kAssembly = Symbol('kAssembly')

module.exports = class AssemblyFile extends AbstractFile {
constructor (path, language) {
super(path)

this[kAssembly] = null
}

[AbstractFile.read] (callback) {
this[kAssembly] = null

fs.readFile(this.path, 'utf8', (err, source) => {
if (err) return callback(err)

this[kAssembly] = Assembly(source)
callback()
})
}

[AbstractFile.write] (callback) {
fs.writeFile(this.path, this[kAssembly].toSource(), callback)
}

[AbstractFile.getVersion] () {
return this[kAssembly].get('AssemblyVersion')
}

[AbstractFile.setVersion] (version) {
this[kAssembly].set('AssemblyVersion', version)

for (const additional of ['AssemblyFileVersion', 'AssemblyInformationalVersion']) {
if (this[kAssembly].get(additional) != null) {
this[kAssembly].set(additional, version)
}
}
}
}
23 changes: 13 additions & 10 deletions lib/finder.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const path = require('path')
const fs = require('fs')
const Emitter = require('events').EventEmitter
const detectLanguage = require('language-detect')
const AssemblyFile = require('./assembly-file')
const ProjectFile = require('./project-file')

const SOURCE_EXT_LANGUAGES = {
'.cs': 'C#',
Expand Down Expand Up @@ -45,13 +47,8 @@ module.exports = class Finder extends Emitter {
return false
}

found (sourceFile, language, done) {
if (language === 'C#' || language === 'F#') {
this.result.set(sourceFile, { path: sourceFile, language })
} else {
const rel = path.relative('.', sourceFile)
this.emit('warning', 'Ignoring "%s": %s is not supported', rel, language)
}
found (file) {
this.result.set(file.path, file)
}

find (input, opts, done) {
Expand Down Expand Up @@ -99,12 +96,12 @@ module.exports = class Finder extends Emitter {
} else if (PROJECT_EXT_LANGUAGES[ext]) {
this.readProject(file, PROJECT_EXT_LANGUAGES[ext], done)
} else if (SOURCE_EXT_LANGUAGES[ext]) {
this.found(file, SOURCE_EXT_LANGUAGES[ext])
this.found(new AssemblyFile(file, SOURCE_EXT_LANGUAGES[ext]))
done()
} else {
detectLanguage(file, (err, lang) => {
if (err) return done(err)
this.found(file, lang)
this.found(new AssemblyFile(file, lang))
done()
})
}
Expand All @@ -114,6 +111,8 @@ module.exports = class Finder extends Emitter {
readProject (projectFile, language, done) {
if (this.skip('project', projectFile)) return done()

this.found(new ProjectFile(projectFile))

const dir = path.dirname(projectFile)
const source = fs.createReadStream(projectFile)
const sax = saxophonist('Compile')
Expand All @@ -135,7 +134,7 @@ module.exports = class Finder extends Emitter {
const language = SOURCE_EXT_LANGUAGES[ext]

if (language && path.basename(file, ext).toLowerCase() === 'assemblyinfo') {
this.found(file, language)
this.found(new AssemblyFile(file, language))
}

next()
Expand Down Expand Up @@ -181,6 +180,10 @@ module.exports = class Finder extends Emitter {
solution.projects.forEach(project => {
if (!project.typeGuid) {
this.emit('warning', 'Ignoring invalid project "%s"', project.path)
} else if (project.path.endsWith('.csproj')) {
return this.readProject(path.resolve(root, project.path), 'C#', next)
} else if (project.path.endsWith('.fsproj')) {
return this.readProject(path.resolve(root, project.path), 'F#', next)
} else if (!project.type) {
this.emit('warning', 'Ignoring project "%s": unknown type "%s"', project.path, project.typeGuid)
} else if (project.type.indexOf('C#') >= 0) {
Expand Down
Loading

0 comments on commit fab97c7

Please sign in to comment.