Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: seek-oss/skuba
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: skuba@10.0.4
Choose a base ref
...
head repository: seek-oss/skuba
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: skuba@10.1.0
Choose a head ref
  • 2 commits
  • 9 files changed
  • 3 contributors

Commits on Mar 19, 2025

  1. Remove yarn --ignore-optional, pin eslint-config-seek for now (#1834)

    AaronMoat authored Mar 19, 2025
    Copy the full SHA
    c05fb14 View commit details
  2. Version Packages (#1836)

    Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
    seek-oss-ci and github-actions[bot] authored Mar 19, 2025
    Copy the full SHA
    1014829 View commit details
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# skuba

## 10.1.0

### Minor Changes

- **lint:** Automatically remove yarn `--ignore-optional` flags ([#1834](https://github.com/seek-oss/skuba/pull/1834))

This flag is no longer templated by **skuba**, having moved to pnpm, but was in the past. The use of this flag has started causing issues with some dependencies which declare optional dependencies for different platforms when using compiled binaries, with each marked as optional (but at least one being required).

This change uses heuristics, and may not find all use, or may remove false positives; you should review the changes.

### Patch Changes

- **deps:** pin eslint-config-seek to 14.3.2 ([#1834](https://github.com/seek-oss/skuba/pull/1834))

This change sets **skuba** to use a known-good version of its dependency set that doesn't clash with the use of `yarn --ignore-optional` in **skuba** projects.

This yarn flag is not recommended by **skuba**. A future version of **skuba** will revert this change, effectively removing support for the flag.

## 10.0.4

### Patch Changes
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "skuba",
"version": "10.0.4",
"version": "10.1.0",
"private": false,
"description": "SEEK development toolkit for backend applications and packages",
"homepage": "https://github.com/seek-oss/skuba#readme",
@@ -169,6 +169,6 @@
"entryPoint": "src/index.ts",
"template": null,
"type": "package",
"version": "10.0.0"
"version": "10.1.0"
}
}
10 changes: 10 additions & 0 deletions packages/eslint-config-skuba/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# eslint-config-skuba

## 5.1.3

### Patch Changes

- **deps:** pin eslint-config-seek to 14.3.2 ([#1834](https://github.com/seek-oss/skuba/pull/1834))

This change sets **skuba** to use a known-good version of its dependency set that doesn't clash with the use of `yarn --ignore-optional` in **skuba** projects.

This yarn flag is not recommended by **skuba**. A future version of **skuba** will revert this change, effectively removing support for the flag.

## 5.1.2

### Patch Changes
4 changes: 2 additions & 2 deletions packages/eslint-config-skuba/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "eslint-config-skuba",
"version": "5.1.2",
"version": "5.1.3",
"private": false,
"description": "ESLint config for skuba",
"homepage": "https://github.com/seek-oss/skuba/tree/main/packages/eslint-config-skuba#readme",
@@ -26,7 +26,7 @@
"skuba": "node --experimental-vm-modules ../../lib/skuba"
},
"dependencies": {
"eslint-config-seek": "^14.3.2",
"eslint-config-seek": "14.3.2",
"eslint-plugin-yml": "^1.14.0",
"typescript-eslint": "^8.26.0"
},
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions src/cli/__snapshots__/format.int.test.ts.snap
Original file line number Diff line number Diff line change
@@ -36,6 +36,8 @@ Upgraded to Node.js 22
Patch applied: Upgrade Node.js to version 22
Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found
skuba update complete.
Refreshed .gitignore. refresh-config-files
@@ -127,6 +129,8 @@ Upgraded to Node.js 22
Patch applied: Upgrade Node.js to version 22
Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found
skuba update complete.
Refreshed .gitignore. refresh-config-files
@@ -213,6 +217,8 @@ Upgraded to Node.js 22
Patch applied: Upgrade Node.js to version 22
Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found
skuba update complete.
Refreshed .gitignore. refresh-config-files
@@ -270,6 +276,8 @@ Upgraded to Node.js 22
Patch applied: Upgrade Node.js to version 22
Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found
skuba update complete.
Refreshed .gitignore. refresh-config-files
10 changes: 10 additions & 0 deletions src/cli/lint/internalLints/upgrade/patches/10.0.4/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Patches } from '../..';

import { tryRemoveYarnIgnoreOptionalFlags } from './removeYarnIgnoreOptionalFlags';

export const patches: Patches = [
{
apply: tryRemoveYarnIgnoreOptionalFlags,
description: 'Remove yarn --ignore-optional flags in Dockerfiles',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import memfs, { vol } from 'memfs';

import type { PatchConfig } from '../..';
import { configForPackageManager } from '../../../../../../utils/packageManager';

import { tryRemoveYarnIgnoreOptionalFlags } from './removeYarnIgnoreOptionalFlags';

const volToJson = () => vol.toJSON(process.cwd(), undefined, true);

jest.mock('fs-extra', () => memfs);
jest.mock('fast-glob', () => ({
glob: (pat: any, opts: any) =>
jest.requireActual('fast-glob').glob(pat, { ...opts, fs: memfs }),
}));

beforeEach(() => vol.reset());

describe('removeYarnIgnoreOptionalFlags', () => {
const baseArgs = {
manifest: {} as PatchConfig['manifest'],
packageManager: configForPackageManager('yarn'),
};

afterEach(() => jest.resetAllMocks());

describe.each(['lint', 'format'] as const)('%s', (mode) => {
it('should skip if no Dockerfile files are found', async () => {
await expect(
tryRemoveYarnIgnoreOptionalFlags({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'skip',
reason: 'no Dockerfiles found',
});

expect(volToJson()).toEqual({});
});

it('should skip if no yarn --ignore-optional flags are found', async () => {
const input = `
RUN pnpm install
# etc
`;
const input2 = `${input}\nRUN yarn install stuff`;

vol.fromJSON({
Dockerfile: input,
'Dockerfile.dev-deps': input2,
});

await expect(
tryRemoveYarnIgnoreOptionalFlags({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'skip',
reason: 'no Dockerfiles to patch',
});

expect(volToJson()).toEqual({
Dockerfile: input,
'Dockerfile.dev-deps': input2,
});
});

it('should handle a variety of formats', async () => {
const inputVolume = {
'Dockerfile.no':
'yarn\nRUN stuff --ignore-optional\n RUN other-stuff \\\n --ignore-optional\n',
'Dockerfile.oneline':
'# stuff\nRUN yarn install --ignore-optional\n# other-stuff\n',
'Dockerfile.other-args':
'RUN yarn install --frozen-lockfile --ignore-optional --other-arg\n',
'Dockerfile.newline': 'RUN yarn install \\\n --ignore-optional\n',
'Dockerfile.newline-with-more-after':
'RUN yarn install \\\n --ignore-optional\\\n --other-arg\n',
'Dockerfile.newline-multiple-args':
'RUN yarn install \\\n --ignore-optional --other-arg\n',
};

vol.fromJSON(inputVolume);

await expect(
tryRemoveYarnIgnoreOptionalFlags({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'apply',
});

expect(volToJson()).toEqual(
mode === 'lint'
? inputVolume
: {
'Dockerfile.no':
'yarn\nRUN stuff --ignore-optional\n RUN other-stuff \\\n --ignore-optional\n',
'Dockerfile.oneline':
'# stuff\nRUN yarn install\n# other-stuff\n',
'Dockerfile.other-args':
'RUN yarn install --frozen-lockfile --other-arg\n',
'Dockerfile.newline': 'RUN yarn install\n',
'Dockerfile.newline-with-more-after':
'RUN yarn install \\\n --other-arg\n',
'Dockerfile.newline-multiple-args':
'RUN yarn install \\\n --other-arg\n',
},
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { inspect } from 'util';

import { glob } from 'fast-glob';
import { promises as fs } from 'fs-extra';

import type { PatchFunction, PatchReturnType } from '../..';
import { log } from '../../../../../../utils/logging';

const fetchFiles = async (files: string[]) =>
Promise.all(
files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8');

return {
file,
contents,
};
}),
);

const regex = /\s*--ignore-optional/;

const removeYarnIgnoreFlag = (contents: string) => {
let isInYarn = false;

const lines = contents.split('\n');

for (let i = 0; i < lines.length; i++) {
const line = lines[i]!;

if (line.includes('yarn')) isInYarn = true;

if (isInYarn && regex.test(line)) {
lines[i] = line.replace(regex, '');

// If we're now an empty line, remove it and get rid of the \ at the end of the previous line
if (!lines[i]!.trim()) {
lines.splice(i, 1);
if (i > 0) {
lines[i - 1] = lines[i - 1]!.replace(/\s*\\$/, '');
}
i--;
} else if (lines[i] === '\\') {
lines.splice(i, 1);
i--;
}

isInYarn = false;
}

if (!line.endsWith('\\')) isInYarn = false;
}

return lines.join('\n');
};

const removeYarnIgnoreOptionalFlags: PatchFunction = async ({
mode,
}): Promise<PatchReturnType> => {
const maybeDockerFilesPaths = await glob(['Dockerfile*']);

if (!maybeDockerFilesPaths.length) {
return {
result: 'skip',
reason: 'no Dockerfiles found',
};
}

const dockerFiles = await fetchFiles(maybeDockerFilesPaths);

const mapped = dockerFiles.map(({ file, contents }) => ({
file,
before: contents,
after: removeYarnIgnoreFlag(contents),
}));

if (!mapped.some(({ before, after }) => before !== after)) {
return {
result: 'skip',
reason: 'no Dockerfiles to patch',
};
}

if (mode === 'lint') {
return {
result: 'apply',
};
}

await Promise.all(
mapped.map(async ({ file, after }) => {
await fs.writeFile(file, after);
}),
);

return { result: 'apply' };
};

export const tryRemoveYarnIgnoreOptionalFlags: PatchFunction = async (
config,
) => {
try {
return await removeYarnIgnoreOptionalFlags(config);
} catch (err) {
log.warn('Failed to remove yarn --ignore-optional flags');
log.subtle(inspect(err));
return { result: 'skip', reason: 'due to an error' };
}
};