Skip to content

Commit

Permalink
fix(pipeline): cache yarn deps with corepack (#2405)
Browse files Browse the repository at this point in the history
## Proposed change

In `node-setup` GitHub action, passing `cache: yarn` doesn't work well
with corepack (see issues below).

The goal is to do the cache manually for yarn and to let `node-setup` do
it for `npm`.

## Related issues

<!--
Please make sure to follow the [contribution
guidelines](https://github.com/amadeus-digital/Otter/blob/main/CONTRIBUTING.md)
-->

actions/setup-node#531

actions/setup-node#182

<!-- * 🐛 Fix #issue -->
<!-- * 🐛 Fix resolves #issue -->
<!-- * 🚀 Feature #issue -->
<!-- * 🚀 Feature resolves #issue -->
<!-- * :octocat: Pull Request #issue -->
  • Loading branch information
vscaiceanu-1a authored Nov 7, 2024
2 parents 78ac431 + 8be2a93 commit ca9d8f4
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 20 deletions.
68 changes: 68 additions & 0 deletions packages/@o3r/pipeline/schematics/ng-add/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,71 @@ describe('ng-add', () => {
});

});

describe('ng-add with yarn should generate a GitHub workflow', () => {

let initialTree: Tree;

beforeEach(() => {
initialTree = Tree.empty();
initialTree.create('angular.json', fs.readFileSync(path.resolve(__dirname, '..', '..', 'testing', 'mocks', 'angular.mocks.yarn.json')));
});

it('when no yarnrc.yml', async () => {
const runner = new SchematicTestRunner('@o3r/pipeline', collectionPath);
const tree = await runner.runSchematic('ng-add', {
toolchain: 'github'
} as NgAddSchematicsSchema, initialTree);

expect(tree.exists('.github/actions/setup/action.yml')).toBe(true);
expect(tree.exists('.github/workflows/main.yml')).toBe(true);
expect(tree.exists('.npmrc')).toBe(false);

expect(tree.readText('.github/actions/setup/action.yml')).toContain('(yarn cache dir)');

});

it('with yarnrc.yml without yarnPath', async () => {
const runner = new SchematicTestRunner('@o3r/pipeline', collectionPath);
initialTree.create('.yarnrc.yml', '');
const tree = await runner.runSchematic('ng-add', {
toolchain: 'github'
} as NgAddSchematicsSchema, initialTree);

expect(tree.exists('.github/actions/setup/action.yml')).toBe(true);
expect(tree.exists('.github/workflows/main.yml')).toBe(true);
expect(tree.exists('.npmrc')).toBe(false);

expect(tree.readText('.github/actions/setup/action.yml')).toContain('(yarn config get cacheFolder)');
});

it('with yarnrc.yml with yarnPath v1', async () => {
const runner = new SchematicTestRunner('@o3r/pipeline', collectionPath);
initialTree.create('.yarnrc.yml', 'yarnPath: .yarn/releases/yarn-1.2.3.cjs');
const tree = await runner.runSchematic('ng-add', {
toolchain: 'github'
} as NgAddSchematicsSchema, initialTree);

expect(tree.exists('.github/actions/setup/action.yml')).toBe(true);
expect(tree.exists('.github/workflows/main.yml')).toBe(true);
expect(tree.exists('.npmrc')).toBe(false);

expect(tree.readText('.github/actions/setup/action.yml')).toContain('(yarn cache dir)');
expect(tree.readText('.github/actions/setup/action.yml')).not.toContain('.yarn/unplugged');
});

it('with yarnrc.yml with yarnPath v2', async () => {
const runner = new SchematicTestRunner('@o3r/pipeline', collectionPath);
initialTree.create('.yarnrc.yml', 'yarnPath: .yarn/releases/yarn-3.2.1.cjs');
const tree = await runner.runSchematic('ng-add', {
toolchain: 'github'
} as NgAddSchematicsSchema, initialTree);

expect(tree.exists('.github/actions/setup/action.yml')).toBe(true);
expect(tree.exists('.github/workflows/main.yml')).toBe(true);
expect(tree.exists('.npmrc')).toBe(false);

expect(tree.readText('.github/actions/setup/action.yml')).toContain('(yarn config get cacheFolder)');
expect(tree.readText('.github/actions/setup/action.yml')).toContain('.yarn/unplugged');
});
});
38 changes: 25 additions & 13 deletions packages/@o3r/pipeline/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,22 @@
import { apply, chain, MergeStrategy, mergeWith, move, Rule, template, url } from '@angular-devkit/schematics';
import type { NgAddSchematicsSchema } from './schema';
import {dump, load} from 'js-yaml';
import * as path from 'node:path';
import { apply, chain, MergeStrategy, mergeWith, move, Rule, template, type Tree, url } from '@angular-devkit/schematics';
import { dump, load } from 'js-yaml';
import * as fs from 'node:fs';
import * as path from 'node:path';
import type { PackageJson } from 'type-fest';
import type { NgAddSchematicsSchema } from './schema';

/**
* Determines if the Yarn version is 2 or higher based on the contents of the .yarnrc.yml file.
* @param tree tree
*/
function isYarn2(tree: Tree) {
const yarnrcPath = '/.yarnrc.yml';
if (tree.exists(yarnrcPath)) {
const { yarnPath } = (load(tree.readText(yarnrcPath)) || {}) as { yarnPath?: string };
return !yarnPath || !/yarn-1\./.test(yarnPath);
}
return false;
}

/**
* Add an Otter CI pipeline to an Angular Project
Expand All @@ -26,12 +39,14 @@ function ngAddFn(options: NgAddSchematicsSchema): Rule {
}
context.logger.info(`Setting up pipeline for package manager: "${packageManager}" `);
const setupCommand = packageManager === 'yarn' ? 'yarn install --immutable' : 'npm ci';
const yarn2 = packageManager === 'yarn' && isYarn2(tree);
const baseTemplateSource = apply(url(`./templates/${options.toolchain}`), [
template({
...options,
packageManager,
setupCommand,
actionVersionString,
yarn2,
dot: '.'
}),
move(tree.root.path)
Expand All @@ -41,16 +56,13 @@ function ngAddFn(options: NgAddSchematicsSchema): Rule {
if (!options.npmRegistry) {
return tree;
}
if (packageManager === 'yarn') {
if (yarn2) {
const yarnrcPath = '/.yarnrc.yml';
if (!tree.exists(yarnrcPath)) {
tree.create(yarnrcPath, dump({'npmRegistryServer': options.npmRegistry}, {indent: 2}));
} else {
const yarnrcContent = load(tree.readText(yarnrcPath)) as any;
yarnrcContent.npmRegistryServer = options.npmRegistry;
tree.overwrite(yarnrcPath, dump(yarnrcContent, {indent: 2}));
}
} else if (packageManager === 'npm') {
const yarnrcContent = load(tree.readText(yarnrcPath)) as { npmRegistryServer?: string };
yarnrcContent.npmRegistryServer = options.npmRegistry;
tree.overwrite(yarnrcPath, dump(yarnrcContent, {indent: 2}));
} else {
// both npm and yarn 1 use .npmrc for the registry
const npmrcPath = '/.npmrc';
if (!tree.exists(npmrcPath)) {
tree.create(npmrcPath, `registry=${options.npmRegistry}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,30 @@ runs:
steps:
- uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 20
cache: <%= packageManager %>
node-version: 20<% if (packageManager !== 'yarn') { %>
cache: <%= packageManager %><% } %>
- name: Enable Corepack
shell: bash
run: corepack enable
- name: Install
<% if (npmRegistry) { %>
run: corepack enable<% if (packageManager === 'yarn') { %>
- name: Get yarn cache directory path
shell: bash
id: yarn-cache-dir-path
run: echo "dir=$(<% if (yarn2) { %>yarn config get cacheFolder<% } else { %>yarn cache dir<% } %>)" >> $GITHUB_OUTPUT
- name: Cache dependencies
uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
<% if (yarn2) { %>.yarn/unplugged
.pnp.cjs
.pnp.loader.mjs<% } %>
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn
${{ runner.os }}<% } %>
- name: Install<% if (npmRegistry) { %>
env:
COREPACK_NPM_REGISTRY: <%= npmRegistry %>
COREPACK_INTEGRITY_KEYS: ""
<% } %>
COREPACK_INTEGRITY_KEYS: ""<% } %>
shell: bash
run: <%= setupCommand %>
19 changes: 19 additions & 0 deletions packages/@o3r/pipeline/testing/mocks/angular.mocks.yarn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://raw.githubusercontent.com/angular/angular-cli/master/packages/angular/cli/lib/config/workspace-schema.json",
"version": 1,
"newProjectRoot": ".",
"cli": {
"packageManager": "yarn"
},
"projects": {
"test-project": {
"projectType": "application",
"root": ".",
"sourceRoot": "./src",
"prefix": "tst",
"architect": {

}
}
}
}

0 comments on commit ca9d8f4

Please sign in to comment.