Repository: paularmstrong/build-tracker
Pull request #126 (2020-02-17 18:56:46)

Upgrade typescript
<!--
Thank you so much for contributing to open source and the Build Tracker project!
-->

# Problem

Typescript is out of date

# Solution

* Upgrade TS to latest
* Remove the generic for `ArtifactSizes` from `Build`, etc. It overcomplicated things and isn't particularly useful (or used)

# TODO

- [x] 🤓 Add & update tests (always try to _increase_ test coverage)
- [x] 🔬 Ensure CI is passing (`yarn lint:ci`, `yarn test`, `yarn tsc:ci`)
- [ ] 📖 Update relevant documentation



Patch:
diff --git a/package.json b/package.json
index 8801eb9..49217f9 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "prettier": "^1.16.4",
     "rimraf": "^2.6.3",
     "ts-jest": "^25.0.0",
-    "ts-node": "^8.0.2",
-    "typescript": "^3.3.3"
+    "ts-node": "^8.0.0",
+    "typescript": "^3.7.0"
   }
 }
diff --git a/src/build/src/index.ts b/src/build/src/index.ts
index 1b788a5..8893e5b 100644
--- a/src/build/src/index.ts
+++ b/src/build/src/index.ts
@@ -18,13 +18,13 @@ export interface BuildMeta {
   branch: BuildMetaItem;
 }
 
-export interface Artifact<AS extends ArtifactSizes> {
+export interface Artifact {
   // Unique hash of the contents of this artifact.
   hash: string;
   // Name of this build artifact
   name: string;
   // Computed sizes of the build artifact
-  sizes: AS;
+  sizes: ArtifactSizes;
 }
 
 export interface ArtifactSizes {
@@ -33,13 +33,13 @@ export interface ArtifactSizes {
   [key: string]: number;
 }
 
-export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSizes = ArtifactSizes> {
+export default class Build<M extends BuildMeta = BuildMeta> {
   private _meta: M;
-  private _artifacts: Map<string, Artifact<A>>;
-  private _totals: A;
+  private _artifacts: Map<string, Artifact>;
+  private _totals: ArtifactSizes;
   private _sizeKeys: Set<string>;
 
-  public constructor(meta: M, artifacts: Array<Artifact<A>>) {
+  public constructor(meta: M, artifacts: Array<Artifact>) {
     this._meta = Object.freeze(meta);
     this._artifacts = new Map();
     artifacts.forEach(artifact => {
@@ -47,14 +47,11 @@ export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSi
     });
   }
 
-  public static fromJSON<M extends BuildMeta = BuildMeta, A extends ArtifactSizes = ArtifactSizes>(build: {
-    meta: M;
-    artifacts: Array<Artifact<A>>;
-  }): Build<M, A> {
+  public static fromJSON<M extends BuildMeta = BuildMeta>(build: { meta: M; artifacts: Array<Artifact> }): Build<M> {
     return new Build(build.meta, build.artifacts);
   }
 
-  public toJSON(): { meta: M; artifacts: Array<Artifact<A>> } {
+  public toJSON(): { meta: M; artifacts: Array<Artifact> } {
     return { meta: this.meta, artifacts: Array.from(this._artifacts.values()) };
   }
 
@@ -78,7 +75,7 @@ export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSi
     return typeof val === 'object' && val.hasOwnProperty('url') ? val.url : undefined;
   }
 
-  public get artifacts(): Array<Artifact<A>> {
+  public get artifacts(): Array<Artifact> {
     return Array.from(this._artifacts.values());
   }
 
@@ -94,7 +91,7 @@ export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSi
     return Array.from(this._sizeKeys);
   }
 
-  public getArtifact(name: string): Artifact<A> {
+  public getArtifact(name: string): Artifact {
     return this._artifacts.get(name);
   }
 
@@ -102,9 +99,8 @@ export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSi
     return Array.from(this._artifacts.keys());
   }
 
-  public getSum(artifactNames: Array<string>): A {
-    // @ts-ignore
-    return artifactNames.reduce((sum: A, artifactName: string) => {
+  public getSum(artifactNames: Array<string>): ArtifactSizes {
+    return artifactNames.reduce((sum: ArtifactSizes, artifactName: string) => {
       const artifact = this._artifacts.get(artifactName);
       if (artifact) {
         Object.entries(artifact.sizes).forEach(([key, value]) => {
@@ -118,9 +114,8 @@ export default class Build<M extends BuildMeta = BuildMeta, A extends ArtifactSi
     }, {});
   }
 
-  public getTotals(artifactFilters?: ArtifactFilters): A {
+  public getTotals(artifactFilters?: ArtifactFilters): ArtifactSizes {
     if (!this._totals) {
-      // @ts-ignore we'll build this below
       this._totals = {};
       this._artifacts.forEach(artifact => {
         Object.entries(artifact.sizes).forEach(([key, value]) => {
diff --git a/src/cli/src/modules/config.ts b/src/cli/src/modules/config.ts
index 39018ba..465207c 100644
--- a/src/cli/src/modules/config.ts
+++ b/src/cli/src/modules/config.ts
@@ -3,11 +3,11 @@
  */
 import { ComparisonMatrix } from '@build-tracker/comparator';
 import cosmiconfig from 'cosmiconfig';
-import { Artifact, ArtifactSizes, BuildMeta } from '@build-tracker/build';
+import { Artifact, BuildMeta } from '@build-tracker/build';
 
 interface BuildJson {
   meta: BuildMeta;
-  artifacts: Array<Artifact<ArtifactSizes>>;
+  artifacts: Array<Artifact>;
 }
 
 export interface ApiReturn {
diff --git a/src/comparator/src/ArtifactDelta.ts b/src/comparator/src/ArtifactDelta.ts
index afbb5f1..5f51171 100644
--- a/src/comparator/src/ArtifactDelta.ts
+++ b/src/comparator/src/ArtifactDelta.ts
@@ -5,15 +5,21 @@ import { ArtifactSizes } from '@build-tracker/build';
 import { Budget, BudgetResult } from '@build-tracker/types';
 import { delta, percentDelta } from './artifact-math';
 
-export default class ArtifactDelta<AS extends ArtifactSizes = ArtifactSizes> {
+export default class ArtifactDelta {
   private _name: string;
   private _budgets: Array<Budget>;
   private _results: Array<BudgetResult>;
-  private _sizes: AS;
-  private _prevSizes: AS;
+  private _sizes: ArtifactSizes;
+  private _prevSizes: ArtifactSizes;
   private _hashChanged: boolean;
 
-  public constructor(name: string, budgets: Array<Budget>, sizes: AS, prevSizes: AS, hashChanged: boolean) {
+  public constructor(
+    name: string,
+    budgets: Array<Budget>,
+    sizes: ArtifactSizes,
+    prevSizes: ArtifactSizes,
+    hashChanged: boolean
+  ) {
     this._name = name;
     this._budgets = budgets;
     this._sizes = sizes;
@@ -25,9 +31,10 @@ export default class ArtifactDelta<AS extends ArtifactSizes = ArtifactSizes> {
     return this._name;
   }
 
-  public get sizes(): AS {
+  public get sizes(): ArtifactSizes {
     return Object.keys(this._sizes).reduce(
       (memo, sizeKey) => {
+        // @ts-ignore
         memo[sizeKey] = delta(sizeKey, this._sizes, this._prevSizes);
         return memo;
       },
@@ -35,9 +42,10 @@ export default class ArtifactDelta<AS extends ArtifactSizes = ArtifactSizes> {
     );
   }
 
-  public get percents(): AS {
+  public get percents(): ArtifactSizes {
     return Object.keys(this._sizes).reduce(
       (memo, sizeKey) => {
+        // @ts-ignore
         memo[sizeKey] = percentDelta(sizeKey, this._sizes, this._prevSizes);
         return memo;
       },
diff --git a/src/comparator/src/BuildDelta.ts b/src/comparator/src/BuildDelta.ts
index a0c3081..6c0987d 100644
--- a/src/comparator/src/BuildDelta.ts
+++ b/src/comparator/src/BuildDelta.ts
@@ -13,19 +13,19 @@ export interface DeltaOptions {
   groups?: Array<Group>;
 }
 
-export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends ArtifactSizes = ArtifactSizes> {
-  private _baseBuild: Build<M, A>;
-  private _prevBuild: Build<M, A>;
+export default class BuildDelta<M extends BuildMeta = BuildMeta> {
+  private _baseBuild: Build<M>;
+  private _prevBuild: Build<M>;
 
   private _artifactBudgets: ArtifactBudgets;
-  private _artifactDeltas: Map<string, ArtifactDelta<A>>;
+  private _artifactDeltas: Map<string, ArtifactDelta>;
   private _artifactFilters: ArtifactFilters;
   private _artifactNames: Set<string>;
   private _groups: Array<Group>;
-  private _groupDeltas: Map<string, ArtifactDelta<A>>;
+  private _groupDeltas: Map<string, ArtifactDelta>;
   private _sizeKeys: Set<string>;
 
-  public constructor(baseBuild: Build<M, A>, prevBuild: Build<M, A>, options: DeltaOptions = {}) {
+  public constructor(baseBuild: Build<M>, prevBuild: Build<M>, options: DeltaOptions = {}) {
     this._baseBuild = baseBuild;
     this._prevBuild = prevBuild;
     this._artifactBudgets = options.artifactBudgets || emptyObject;
@@ -33,11 +33,11 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
     this._groups = options.groups || [];
   }
 
-  public get baseBuild(): Build<M, A> {
+  public get baseBuild(): Build<M> {
     return this._baseBuild;
   }
 
-  public get prevBuild(): Build<M, A> {
+  public get prevBuild(): Build<M> {
     return this._prevBuild;
   }
 
@@ -71,7 +71,7 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
         memo[key] = 0;
         return memo;
       }, {});
-      return new ArtifactDelta<A>(name, [], noSize, noSize, false);
+      return new ArtifactDelta(name, [], noSize, noSize, false);
     }
     return this._artifactDeltas.get(name);
   }
@@ -90,7 +90,7 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
     return this._artifactNames;
   }
 
-  private get _fauxArtifactSizes(): A {
+  private get _fauxArtifactSizes(): ArtifactSizes {
     // @ts-ignore
     return this.artifactSizes.reduce((memo, sizeKey) => {
       memo[sizeKey] = 0;
@@ -98,7 +98,7 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
     }, {});
   }
 
-  public get artifactDeltas(): Array<ArtifactDelta<A>> {
+  public get artifactDeltas(): Array<ArtifactDelta> {
     if (!this._artifactDeltas) {
       this._artifactDeltas = new Map();
       this.artifactNames.forEach(artifactName => {
@@ -108,7 +108,7 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
         const sizes = baseArtifact ? baseArtifact.sizes : this._fauxArtifactSizes;
         const prevSizes = prevArtifact ? prevArtifact.sizes : this._fauxArtifactSizes;
 
-        const delta = new ArtifactDelta<A>(
+        const delta = new ArtifactDelta(
           artifactName,
           this._artifactBudgets[artifactName] || [],
           sizes,
@@ -123,14 +123,14 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
     return Array.from(this._artifactDeltas.values());
   }
 
-  public getGroupDelta(groupName: string): ArtifactDelta<A> {
+  public getGroupDelta(groupName: string): ArtifactDelta {
     if (!this._groupDeltas) {
       this.groupDeltas;
     }
     return this._groupDeltas.get(groupName);
   }
 
-  public get groupDeltas(): Map<string, ArtifactDelta<A>> {
+  public get groupDeltas(): Map<string, ArtifactDelta> {
     if (!this._groupDeltas) {
       this._groupDeltas = new Map();
       this._groups.forEach(group => {
@@ -150,7 +150,7 @@ export default class BuildDelta<M extends BuildMeta = BuildMeta, A extends Artif
           return changed ? true : !baseArtifact || !prevArtifact || baseArtifact.hash !== prevArtifact.hash;
         }, false);
 
-        const delta = new ArtifactDelta<A>(
+        const delta = new ArtifactDelta(
           group.name,
           group.budgets || [],
           baseSum || this._fauxArtifactSizes,
diff --git a/src/comparator/src/index.ts b/src/comparator/src/index.ts
index a2658df..a81186e 100644
--- a/src/comparator/src/index.ts
+++ b/src/comparator/src/index.ts
@@ -151,7 +151,7 @@ export default class BuildComparator {
     this.builds = builds;
     this._artifactFilters = artifactFilters || [];
     this._artifactBudgets = artifactBudgets || emptyObject;
-    this._groups = [{ name: 'All', artifactNames: this.artifactNames, budgets }, ...groups].filter(Boolean);
+    this._groups = [{ name: 'All', artifactNames: this.artifactNames, budgets }, ...(groups || [])].filter(Boolean);
     this._emptySizes = Object.freeze(
       this.sizeKeys.reduce((memo, key) => {
         memo[key] = 0;
@@ -411,7 +411,9 @@ export default class BuildComparator {
     const groups = this.getStringFormattedGroups(formatTotal, formatDelta, sizeKey);
     const rows = this.getStringFormattedRows(formatTotal, formatDelta, sizeKey, artifactFilter);
 
-    return markdownTable([header, ...groups, ...rows], { align: rows[0].map((_, i) => (i === 0 ? 'l' : 'r')) });
+    const allRows = [header, ...groups, ...rows];
+
+    return markdownTable(allRows, { align: allRows[0].map((_, i) => (i === 0 ? 'l' : 'r')) });
   }
 
   public toCsv({
diff --git a/src/server/src/types.ts b/src/server/src/types.ts
index e7606a6..aa4278c 100644
--- a/src/server/src/types.ts
+++ b/src/server/src/types.ts
@@ -6,7 +6,7 @@ import { Artifact, BuildMeta } from '@build-tracker/build';
 
 export interface Build {
   meta: BuildMeta;
-  artifacts: Array<Artifact<{}>>;
+  artifacts: Array<Artifact>;
 }
 
 export interface Queries {
diff --git a/yarn.lock b/yarn.lock
index 4c5b2a8..99da50d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5185,6 +5185,11 @@ diff@^3.1.0:
   resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
   integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
 
+diff@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -13817,6 +13822,17 @@ ts-loader@^5.3.3:
     micromatch "^3.1.4"
     semver "^5.0.1"
 
+ts-node@^8.0.0:
+  version "8.6.2"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.6.2.tgz#7419a01391a818fbafa6f826a33c1a13e9464e35"
+  integrity sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==
+  dependencies:
+    arg "^4.1.0"
+    diff "^4.0.1"
+    make-error "^1.1.1"
+    source-map-support "^0.5.6"
+    yn "3.1.1"
+
 ts-node@^8.0.2:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.1.0.tgz#8c4b37036abd448577db22a061fd7a67d47e658e"
@@ -13904,10 +13920,10 @@ typedarray@^0.0.6:
   resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
-typescript@^3.3.3:
-  version "3.4.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.3.tgz#0eb320e4ace9b10eadf5bc6103286b0f8b7c224f"
-  integrity sha512-FFgHdPt4T/duxx6Ndf7hwgMZZjZpB+U0nMNGVCYPq0rEzWKjEDobm4J6yb3CS7naZ0yURFqdw9Gwc7UOh/P9oQ==
+typescript@^3.7.0:
+  version "3.7.5"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
+  integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
 
 ua-parser-js@^0.7.18:
   version "0.7.20"
@@ -14892,6 +14908,11 @@ yauzl@^2.4.2:
     buffer-crc32 "~0.2.3"
     fd-slicer "~1.1.0"
 
+yn@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
+
 yn@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.0.tgz#fcbe2db63610361afcc5eb9e0ac91e976d046114"


Test patch:
diff --git a/src/comparator/src/__tests__/index.test.ts b/src/comparator/src/__tests__/index.test.ts
index 749f696..02313a2 100644
--- a/src/comparator/src/__tests__/index.test.ts
+++ b/src/comparator/src/__tests__/index.test.ts
@@ -20,6 +20,14 @@ const build1 = new Build(
     { name: 'tacos', hash: 'abc', sizes: { stat: 123, gzip: 45 } }
   ]
 );
+// Same as build 1 but different revision
+const build1b = new Build(
+  { branch: 'master', revision: '1234567abcdeg', parentRevision: '1234567abcdef', timestamp: 1234567 },
+  [
+    { name: 'burritos', hash: 'abc', sizes: { stat: 456, gzip: 90 } },
+    { name: 'tacos', hash: 'abc', sizes: { stat: 123, gzip: 45 } }
+  ]
+);
 
 const build2 = new Build(
   { branch: 'master', revision: '8901234abcdef', parentRevision: 'abcdef', timestamp: 8901234 },
@@ -338,6 +346,15 @@ describe('BuildComparator', () => {
       `);
     });
 
+    test('handles 0 artifacts', () => {
+      const comparator = new BuildComparator({ builds: [build1, build1b] });
+      expect(comparator.toMarkdown({ artifactFilter: () => false })).toMatchInlineSnapshot(`
+      "|     |  1234567 |  1234567 |           Δ1 |
+      | :-- | -------: | -------: | -----------: |
+      | All | 0.13 KiB | 0.13 KiB | 0 KiB (0.0%) |"
+      `);
+    });
+
     test('can specify a different size key', () => {
       const comparator = new BuildComparator({ builds: [build1, build2] });
       expect(comparator.toMarkdown({ sizeKey: 'stat' })).toMatchInlineSnapshot(`