-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(package-json-hook)!: share state for package.json hook installers
This is a breaking change because these new install hooks rely on the new commitInstall method to actually write to your package.json file, and will conflict with the older implementation which rewrote the manifest file for every hook. If some of your hooks use the old version of this plugin, and others use the new version, then any hooks using the old version will be lost, as commitInstall is always called last and ignores any hooks not present in its state object.
- Loading branch information
1 parent
454f9cd
commit 0c8729f
Showing
7 changed files
with
78 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,76 @@ | ||
import { Hook } from '@dotcom-tool-kit/types' | ||
import type { PackageJson } from '@financial-times/package-json' | ||
import loadPackageJson from '@financial-times/package-json' | ||
import fs from 'fs' | ||
import get from 'lodash/get' | ||
import mapValues from 'lodash/mapValues' | ||
import merge from 'lodash/merge' | ||
import update from 'lodash/update' | ||
import path from 'path' | ||
|
||
type PackageJsonConfigField = { | ||
[key: string]: string | ||
} | ||
interface PackageJson { | ||
[field: string]: PackageJson | string | ||
} | ||
|
||
export abstract class PackageJsonHelper extends Hook { | ||
_packageJson?: PackageJson | ||
abstract field: string | ||
abstract key: string | ||
abstract hook: string | ||
interface PackageJsonStateValue { | ||
hooks: string[] | ||
trailingString: string | ||
} | ||
|
||
get packageJson(): PackageJson { | ||
if (!this._packageJson) { | ||
const filepath = path.resolve(process.cwd(), 'package.json') | ||
this._packageJson = loadPackageJson({ filepath }) | ||
} | ||
interface PackageJsonState { | ||
[field: string]: PackageJsonState | PackageJsonStateValue | ||
} | ||
|
||
return this._packageJson | ||
} | ||
export abstract class PackageJsonHelper extends Hook<PackageJsonState> { | ||
private _packageJson?: PackageJson | ||
abstract field: string | string[] | ||
abstract key: string | ||
abstract hook: string | ||
trailingString?: string | ||
|
||
async check(): Promise<boolean> { | ||
const commands = this.packageJson.getField<PackageJsonConfigField>(this.field) | ||
return commands?.[this.key]?.includes(this.hook) | ||
} | ||
installGroup = 'package-json' | ||
|
||
abstract install(): Promise<void> | ||
} | ||
filepath = path.resolve(process.cwd(), 'package.json') | ||
|
||
async getPackageJson(): Promise<PackageJson> { | ||
if (!this._packageJson) { | ||
const rawPackageJson = await fs.promises.readFile(this.filepath, 'utf8') | ||
const packageJson = JSON.parse(rawPackageJson) | ||
this._packageJson = packageJson | ||
return packageJson | ||
} | ||
|
||
return this._packageJson | ||
} | ||
|
||
private get hookPath(): string[] { | ||
return Array.isArray(this.field) ? [...this.field, this.key] : [this.field, this.key] | ||
} | ||
|
||
async check(): Promise<boolean> { | ||
const packageJson = await this.getPackageJson() | ||
return get(packageJson, this.hookPath)?.includes(this.hook) | ||
} | ||
|
||
async install(state?: PackageJsonState): Promise<PackageJsonState> { | ||
state ??= {} | ||
// prepend each hook to maintain the same order as previous implementations | ||
update(state, this.hookPath, (hookState?: PackageJsonStateValue) => ({ | ||
hooks: [this.hook, ...(hookState?.hooks ?? [])], | ||
trailingString: this.trailingString | ||
})) | ||
return state | ||
} | ||
|
||
async commitInstall(state: PackageJsonState): Promise<void> { | ||
const reduceHooks = (state: PackageJsonState): PackageJson => | ||
mapValues(state, (field) => | ||
Array.isArray(field?.hooks) | ||
? `dotcom-tool-kit ${field.hooks.join(' ')}${ | ||
field.trailingString ? ' ' + field.trailingString : '' | ||
}` | ||
: reduceHooks(field as PackageJsonState) | ||
) | ||
|
||
const newPackageJson = merge(await this.getPackageJson(), reduceHooks(state)) | ||
await fs.promises.writeFile(this.filepath, JSON.stringify(newPackageJson, null, 2) + '\n') | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,5 @@ | ||
import { PackageJsonHelper } from './package-json-helper' | ||
|
||
type Scripts = { | ||
[script: string]: string | ||
} | ||
|
||
export abstract class PackageJsonScriptHook extends PackageJsonHelper { | ||
field = 'scripts' | ||
|
||
async install(): Promise<void> { | ||
let command = `dotcom-tool-kit ${this.hook}` | ||
const existingCommand = this.packageJson.getField<Scripts>(this.field)[this.key] | ||
if (existingCommand && existingCommand.startsWith('dotcom-tool-kit ')) { | ||
command = command.concat(existingCommand.replace('dotcom-tool-kit', '')) | ||
} | ||
this.packageJson.requireScript({ | ||
stage: this.key, | ||
command | ||
}) | ||
|
||
this.packageJson.writeChanges() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,5 @@ | ||
import { PackageJsonHelper } from '@dotcom-tool-kit/package-json-hook' | ||
|
||
type HuskyField = { | ||
hooks?: { | ||
[hook: string]: string | ||
} | ||
} | ||
|
||
export abstract class HuskyHook extends PackageJsonHelper { | ||
field = 'husky' | ||
|
||
async install(): Promise<void> { | ||
let command = `dotcom-tool-kit ${this.hook}` | ||
|
||
const huskyHooks = this.packageJson.getField<HuskyField>(this.field) || {} | ||
|
||
if(!huskyHooks.hooks) { | ||
huskyHooks.hooks = {} | ||
} | ||
|
||
const existingCommand = huskyHooks.hooks[this.key] | ||
|
||
if (existingCommand?.startsWith('dotcom-tool-kit ')) { | ||
command = command.concat(existingCommand.replace('dotcom-tool-kit', '')) | ||
} | ||
|
||
huskyHooks.hooks[this.key] = command | ||
|
||
this.packageJson.setField(this.field, huskyHooks) | ||
this.packageJson.writeChanges() | ||
} | ||
|
||
async check(): Promise<boolean> { | ||
const husky = this.packageJson.getField<HuskyField>(this.field) | ||
return husky?.hooks?.[this.key]?.includes(this.hook) ?? false | ||
} | ||
field = ['husky', 'hooks'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,6 @@ | ||
import { PackageJsonHelper } from '@dotcom-tool-kit/package-json-hook' | ||
|
||
type LintStagedHooks = { | ||
[glob: string]: string | ||
} | ||
|
||
export abstract class LintStagedHook extends PackageJsonHelper { | ||
field = 'lint-staged' | ||
|
||
async install(): Promise<void> { | ||
let command = `dotcom-tool-kit ${this.hook}` | ||
const commands = this.packageJson.getField<LintStagedHooks>(this.field) || {} | ||
const existingCommand = commands[this.key] | ||
if (existingCommand?.startsWith('dotcom-tool-kit ')) { | ||
command = command.concat(existingCommand.replace('dotcom-tool-kit', '')) | ||
} else { | ||
command = command.concat(' --') | ||
} | ||
commands[this.key] = command | ||
this.packageJson.setField(this.field, commands) | ||
|
||
this.packageJson.writeChanges() | ||
} | ||
trailingString = '--' | ||
} |