Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/mono-pub/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ export default async function publish(
plugins: Array<MonoPubPlugin>,
options: MonoPubOptions = {}
) {
const { stdout = process.stdout, stderr = process.stderr, ...restOptions } = options
const { stdout = process.stdout, stderr = process.stderr, ignoreDependencies, ...restOptions } = options
const logger = getLogger({ stdout, stderr })
const context: MonoPubContext = {
cwd: process.cwd(),
env: process.env,
ignoreDependencies: ignoreDependencies || {},
...restOptions,
logger,
}
Expand Down
12 changes: 11 additions & 1 deletion packages/mono-pub/src/types/config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import type { Signale } from 'signale'

export type IgnoringDependencies = Record<string, Array<string>>

export type MonoPubContext = {
/** Path to start scanning from, process.cwd() by default */
cwd: string
/** Plugins shared environment, process.env by default */
env: Record<string, string | undefined>
/** Context-specific logger which can be used by plugins to display progress */
logger: Signale<'info' | 'error' | 'log' | 'success'>
/**
* List of dependencies per package, which should not affect its version bump.
* Might be helpful to break cyclic dependencies, so proper release order can be resolved
* */
ignoreDependencies: IgnoringDependencies
}

export type MonoPubOptions = Partial<
Pick<MonoPubContext, 'cwd' | 'env'> & {
Pick<MonoPubContext, 'cwd' | 'env' | 'ignoreDependencies'> & {
stdout: NodeJS.WriteStream
stderr: NodeJS.WriteStream
}
Expand Down
89 changes: 88 additions & 1 deletion packages/mono-pub/src/utils/deps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import { getDependencies, getExecutionOrder, patchPackageDeps } from './deps'
import { getNewVersion, versionToString } from './versions'

import type { DirResult } from 'tmp'
import type { BasePackageInfo, LatestPackagesReleases, PackageVersion } from '@/types'
import type {
BasePackageInfo,
LatestPackagesReleases,
PackageVersion,
PackageInfoWithDependencies,
IgnoringDependencies,
} from '@/types'

function writePackageJson(obj: Record<string, unknown>, packagePath: string, cwd: string) {
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
Expand Down Expand Up @@ -149,6 +155,87 @@ describe('Dependencies utils', () => {
const batches = getExecutionOrder(Object.values(deps), { batching: true })
expect(batches).toEqual([[pkg1Info, pkg4Info], [pkg2Info], [pkg3Info]])
})
describe('Should respect ignoreDependencies', () => {
const cycleLength = 6
let packages: Array<PackageInfoWithDependencies> = []
beforeEach(() => {
packages = Array.from({ length: cycleLength }, (_, i) => {
const packageName = `package${i}`
const prevPackageName = `package${(i - 1 + cycleLength) % cycleLength}`

return {
name: packageName,
// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
location: path.join(tmpDir.name, 'packages', packageName, 'package.json'),
dependsOn: [
{
name: prevPackageName,
type: Math.random() > 0.5 ? 'dep' : 'devDep',
value: versionToString(getRandomVersion()),
},
],
}
})
})

it('Simple cyclic graph test', () => {
expect(() => {
getExecutionOrder(packages)
}).toThrow('The release cannot be done because of cyclic dependencies')

for (let i = 0; i < cycleLength; i++) {
const ignoreDependencies = {
[packages[i].name]: [packages[(i - 1 + cycleLength) % cycleLength].name],
}

const expectedNonBatchedOrder = Array.from(
{ length: cycleLength },
(_, idx) => packages[(i + idx) % cycleLength].name
)

const nonBatchedOrder = getExecutionOrder(packages, {
ignoreDependencies,
}).map((pkg) => pkg.name)

expect(nonBatchedOrder).toEqual(expectedNonBatchedOrder)

const expectedBatchedOrder = expectedNonBatchedOrder.map((name) => [name])
const batchedOrder = getExecutionOrder(packages, {
ignoreDependencies,
batching: true,
}).map((batch) => batch.map((pkg) => pkg.name))

expect(batchedOrder).toEqual(expectedBatchedOrder)
}
})
it('Advanced test', () => {
const ignoreDependencies: IgnoringDependencies = {}
const firstBatchExpected: Array<string> = []
const secondBatchExpected: Array<string> = []

for (let i = 0; i < cycleLength; i++) {
const packageName = packages[i].name

if (i % 2 === 0) {
const prevPackageName = packages[(i + cycleLength - 1) % cycleLength].name
ignoreDependencies[packageName] = [prevPackageName]
firstBatchExpected.push(packageName)
} else {
secondBatchExpected.push(packageName)
}
}

const batchedOrder = getExecutionOrder(packages, {
ignoreDependencies,
batching: true,
}).map((batch) => batch.map((pkg) => pkg.name))

expect(batchedOrder).toEqual([
expect.objectContaining(firstBatchExpected),
expect.objectContaining(secondBatchExpected),
])
})
})
})
describe('patchPackageDeps', () => {
it('Should patch package with new version or version from latest release', async () => {
Expand Down
14 changes: 12 additions & 2 deletions packages/mono-pub/src/utils/deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import get from 'lodash/get'
import set from 'lodash/set'
import { versionToString, getVersionCriteria } from '@/utils/versions'

import type { BasePackageInfo, PackageInfoWithDependencies, DependencyInfo, LatestPackagesReleases } from '@/types'
import type {
BasePackageInfo,
PackageInfoWithDependencies,
DependencyInfo,
LatestPackagesReleases,
IgnoringDependencies,
} from '@/types'

export async function getDependencies(
packages: Array<BasePackageInfo>
Expand Down Expand Up @@ -40,6 +46,7 @@ type ExecutionOrder<T extends boolean | undefined> = T extends true

type TaskPlanningOptions<T extends boolean | undefined> = {
batching?: T
ignoreDependencies?: IgnoringDependencies
}

export function getExecutionOrder<T extends boolean | undefined = undefined>(
Expand All @@ -48,12 +55,15 @@ export function getExecutionOrder<T extends boolean | undefined = undefined>(
): ExecutionOrder<T> {
const batches: Array<Array<BasePackageInfo>> = []
const pkgMap = Object.fromEntries(packages.map((pkg) => [pkg.name, pkg]))
const ignoreDependencies = options?.ignoreDependencies || {}

const dependencies = new Map<string, Array<string>>()
for (const pkg of packages) {
const packageIgnoreList = ignoreDependencies[pkg.name] || []

dependencies.set(
pkg.name,
pkg.dependsOn.map((dep) => dep.name)
pkg.dependsOn.map((dep) => dep.name).filter((name) => !packageIgnoreList.includes(name))
)
}

Expand Down
7 changes: 6 additions & 1 deletion packages/mono-pub/src/utils/plugins.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,12 @@ describe('CombinedPlugin', () => {
packages = Object.values(await getDependencies([pkg3Info, pkg2Info, pkg1Info]))

eventLog = []
ctx = { cwd: process.cwd(), env: {}, logger: getLogger({ stdout: process.stdout, stderr: process.stderr }) }
ctx = {
cwd: process.cwd(),
env: {},
logger: getLogger({ stdout: process.stdout, stderr: process.stderr }),
ignoreDependencies: {},
}
chain = {
getter: getFakeVersionGetter(eventLog),
extractor: getFakeExtractor(eventLog),
Expand Down
Loading