@form8ion/github-workflows-core 5.7.0 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@form8ion/github-workflows-core",
3
3
  "description": "core functionality for form8ion plugins that manage github workflows",
4
4
  "license": "MIT",
5
- "version": "5.7.0",
5
+ "version": "5.7.1",
6
6
  "type": "commonjs",
7
7
  "engines": {
8
8
  "node": "^18.17 || >=20.6.1"
@@ -52,7 +52,8 @@
52
52
  },
53
53
  "files": [
54
54
  "example.js",
55
- "lib/"
55
+ "lib/",
56
+ "src/"
56
57
  ],
57
58
  "publishConfig": {
58
59
  "access": "public",
package/src/index.js ADDED
@@ -0,0 +1,14 @@
1
+ export {
2
+ executeVerification as scaffoldVerificationStep,
3
+ installDependencies as scaffoldDependencyInstallationStep,
4
+ setupNode as scaffoldNodeSetupStep,
5
+ checkout as scaffoldCheckoutStep
6
+ } from './steps/scaffolders.js';
7
+ export {
8
+ fileExists as workflowFileExists,
9
+ load as loadWorkflowFile,
10
+ write as writeWorkflowFile,
11
+ rename as renameWorkflowFile
12
+ } from './workflow-file/index.js';
13
+ export {default as test} from './tester.js';
14
+ export {removeActionFromJobs} from './jobs/remover.js';
@@ -0,0 +1,13 @@
1
+ import {removeActionFromSteps} from '../steps/remover.js';
2
+
3
+ export function removeActionFromJobs(jobs, actionName) {
4
+ return Object.fromEntries(
5
+ Object.entries(jobs).map(([jobName, job]) => ([
6
+ jobName,
7
+ {
8
+ ...job,
9
+ .../* job.steps && */{steps: removeActionFromSteps(job.steps, actionName)}
10
+ }
11
+ ]))
12
+ );
13
+ }
@@ -0,0 +1,26 @@
1
+ import {describe, it, expect, vi} from 'vitest';
2
+ import {when} from 'vitest-when';
3
+ import any from '@travi/any';
4
+ import zip from 'lodash.zip';
5
+
6
+ import {removeActionFromSteps} from '../steps/remover.js';
7
+ import {removeActionFromJobs} from './remover.js';
8
+
9
+ vi.mock('../steps/remover.js');
10
+
11
+ describe('jobs-level remover', () => {
12
+ const anySteps = () => any.listOf(any.simpleObject);
13
+
14
+ it('should remove an action by name from an indexed list of jobs', async () => {
15
+ const actionName = any.word();
16
+ const jobNames = any.listOf(any.word);
17
+ const jobs = jobNames.map(() => ({...any.simpleObject(), steps: anySteps()}));
18
+ const updatedJobs = jobs.map(job => ({...job, steps: anySteps()}));
19
+ zip(jobs, updatedJobs).forEach(([job, updatedJob]) => {
20
+ when(removeActionFromSteps).calledWith(job.steps, actionName).thenReturn(updatedJob.steps);
21
+ });
22
+
23
+ expect(removeActionFromJobs(Object.fromEntries(zip(jobNames, jobs)), actionName))
24
+ .toEqual(Object.fromEntries(zip(jobNames, updatedJobs)));
25
+ });
26
+ });
@@ -0,0 +1,3 @@
1
+ export default function () {
2
+ return undefined;
3
+ }
@@ -0,0 +1,3 @@
1
+ export function removeActionFromSteps(steps, actionName) {
2
+ return steps.filter(step => !step.uses?.includes(actionName));
3
+ }
@@ -0,0 +1,20 @@
1
+ import {describe, it, expect} from 'vitest';
2
+ import any from '@travi/any';
3
+
4
+ import {removeActionFromSteps} from './remover.js';
5
+
6
+ describe('steps-level remover', () => {
7
+ const anyStep = ({actionName} = {}) => ({
8
+ ...any.simpleObject(),
9
+ ...actionName && {uses: `${any.string()}${actionName}${any.string()}`}
10
+ });
11
+
12
+ it('should remove an action by name from a list of steps', async () => {
13
+ const stepsBeforeRemoved = any.listOf(anyStep);
14
+ const stepsAfterRemoved = any.listOf(anyStep);
15
+ const actionName = any.word();
16
+ const steps = [...stepsBeforeRemoved, anyStep({actionName}), ...stepsAfterRemoved];
17
+
18
+ expect(removeActionFromSteps(steps, actionName)).toEqual([...stepsBeforeRemoved, ...stepsAfterRemoved]);
19
+ });
20
+ });
@@ -0,0 +1,28 @@
1
+ export function setupNode({versionDeterminedBy}) {
2
+ return {
3
+ name: 'Setup node',
4
+ uses: 'actions/setup-node@v6.1.0',
5
+ with: {
6
+ ...'nvmrc' === versionDeterminedBy && {'node-version-file': '.nvmrc'},
7
+ // eslint-disable-next-line no-template-curly-in-string
8
+ ...'matrix' === versionDeterminedBy && {'node-version': '${{ matrix.node }}'},
9
+ cache: 'npm'
10
+ }
11
+ };
12
+ }
13
+
14
+ export function executeVerification() {
15
+ return {run: 'npm test'};
16
+ }
17
+
18
+ export function installDependencies() {
19
+ return [
20
+ {run: 'npm clean-install'},
21
+ {run: 'npm install --global corepack@latest'},
22
+ {run: 'corepack npm audit signatures'}
23
+ ];
24
+ }
25
+
26
+ export function checkout() {
27
+ return {uses: 'actions/checkout@v6.0.1'};
28
+ }
@@ -0,0 +1,38 @@
1
+ import {describe, expect, it} from 'vitest';
2
+
3
+ import {checkout, executeVerification, installDependencies, setupNode} from './scaffolders.js';
4
+
5
+ describe('step scaffolders', () => {
6
+ it('should scaffold the details of the checkout step', () => {
7
+ expect(checkout()).toEqual({uses: 'actions/checkout@v6.0.1'});
8
+ });
9
+
10
+ it('should set up node correctly when the version is determined from the `.nvmrc`', () => {
11
+ expect(setupNode({versionDeterminedBy: 'nvmrc'})).toEqual({
12
+ name: 'Setup node',
13
+ uses: 'actions/setup-node@v6.1.0',
14
+ with: {'node-version-file': '.nvmrc', cache: 'npm'}
15
+ });
16
+ });
17
+
18
+ it('should set up node correctly when the version is determined based on a matrix', () => {
19
+ expect(setupNode({versionDeterminedBy: 'matrix'})).toEqual({
20
+ name: 'Setup node',
21
+ uses: 'actions/setup-node@v6.1.0',
22
+ // eslint-disable-next-line no-template-curly-in-string
23
+ with: {'node-version': '${{ matrix.node }}', cache: 'npm'}
24
+ });
25
+ });
26
+
27
+ it('should install dependencies correctly', () => {
28
+ expect(installDependencies()).toEqual([
29
+ {run: 'npm clean-install'},
30
+ {run: 'npm install --global corepack@latest'},
31
+ {run: 'corepack npm audit signatures'}
32
+ ]);
33
+ });
34
+
35
+ it('should execute verification correctly', () => {
36
+ expect(executeVerification()).toEqual({run: 'npm test'});
37
+ });
38
+ });
package/src/tester.js ADDED
@@ -0,0 +1,5 @@
1
+ import {directoryExists} from '@form8ion/core';
2
+
3
+ export default function ({projectRoot}) {
4
+ return directoryExists(`${projectRoot}/.github/workflows`);
5
+ }
@@ -0,0 +1,25 @@
1
+ import any from '@travi/any';
2
+ import {directoryExists} from '@form8ion/core';
3
+
4
+ import {it, expect, describe, vi} from 'vitest';
5
+ import {when} from 'vitest-when';
6
+
7
+ import projectUsesGithubWorkflows from './tester.js';
8
+
9
+ vi.mock('@form8ion/core');
10
+
11
+ describe('workflows predicate', () => {
12
+ const projectRoot = any.string();
13
+
14
+ it('should return `true` when the project uses GitHub workflows', async () => {
15
+ when(directoryExists).calledWith(`${projectRoot}/.github/workflows`).thenResolve(true);
16
+
17
+ expect(await projectUsesGithubWorkflows({projectRoot})).toEqual(true);
18
+ });
19
+
20
+ it('should return `false` when the project does not use GitHub workflows', async () => {
21
+ when(directoryExists).calledWith(`${projectRoot}/.github/workflows`).thenResolve(false);
22
+
23
+ expect(await projectUsesGithubWorkflows({projectRoot})).toEqual(false);
24
+ });
25
+ });
@@ -0,0 +1,5 @@
1
+ import {fileExists} from '@form8ion/core';
2
+
3
+ export default function ({projectRoot, name}) {
4
+ return fileExists(`${projectRoot}/.github/workflows/${name}.yml`);
5
+ }
@@ -0,0 +1,20 @@
1
+ import {fileExists} from '@form8ion/core';
2
+
3
+ import {when} from 'vitest-when';
4
+ import any from '@travi/any';
5
+ import {describe, expect, it, vi} from 'vitest';
6
+
7
+ import workflowFileExists from './existence-checker.js';
8
+
9
+ vi.mock('@form8ion/core');
10
+
11
+ describe('workflow existence checker', () => {
12
+ it('should check for existence of the workflow file in the workflows directory', async () => {
13
+ const projectRoot = any.string();
14
+ const name = any.word();
15
+ const exists = any.boolean();
16
+ when(fileExists).calledWith(`${projectRoot}/.github/workflows/${name}.yml`).thenResolve(exists);
17
+
18
+ expect(await workflowFileExists({projectRoot, name})).toBe(exists);
19
+ });
20
+ });
@@ -0,0 +1,4 @@
1
+ export {default as fileExists} from './existence-checker.js';
2
+ export {default as load} from './loader.js';
3
+ export {default as write} from './writer.js';
4
+ export {default as rename} from './renamer.js';
@@ -0,0 +1,5 @@
1
+ import {fileTypes, loadConfigFile} from '@form8ion/core';
2
+
3
+ export default function ({projectRoot, name}) {
4
+ return loadConfigFile({path: `${projectRoot}/.github/workflows`, name, format: fileTypes.YAML});
5
+ }
@@ -0,0 +1,22 @@
1
+ import {fileTypes, loadConfigFile} from '@form8ion/core';
2
+
3
+ import {when} from 'vitest-when';
4
+ import any from '@travi/any';
5
+ import {describe, expect, it, vi} from 'vitest';
6
+
7
+ import loadWorkflowFile from './loader.js';
8
+
9
+ vi.mock('@form8ion/core');
10
+
11
+ describe('workflow loader', () => {
12
+ it('should load the workflow from the workflows directory', async () => {
13
+ const projectRoot = any.string();
14
+ const name = any.word();
15
+ const workflowDetails = any.simpleObject();
16
+ when(loadConfigFile)
17
+ .calledWith({path: `${projectRoot}/.github/workflows`, name, format: fileTypes.YAML})
18
+ .thenResolve(workflowDetails);
19
+
20
+ expect(await loadWorkflowFile({projectRoot, name})).toEqual(workflowDetails);
21
+ });
22
+ });
@@ -0,0 +1,8 @@
1
+ import {promises as fs} from 'node:fs';
2
+
3
+ export default function ({projectRoot, oldName, newName}) {
4
+ return fs.rename(
5
+ `${projectRoot}/.github/workflows/${oldName}.yml`,
6
+ `${projectRoot}/.github/workflows/${newName}.yml`
7
+ );
8
+ }
@@ -0,0 +1,23 @@
1
+ import {promises as fs} from 'node:fs';
2
+
3
+ import any from '@travi/any';
4
+ import {describe, expect, it, vi} from 'vitest';
5
+
6
+ import renameWorkflowFile from './renamer.js';
7
+
8
+ vi.mock('node:fs');
9
+
10
+ describe('workflow file renamer', () => {
11
+ it('should rename the workflow file in the workflows directory', async () => {
12
+ const projectRoot = any.string();
13
+ const oldName = any.word();
14
+ const newName = any.word();
15
+
16
+ await renameWorkflowFile({projectRoot, oldName, newName});
17
+
18
+ expect(fs.rename).toHaveBeenCalledWith(
19
+ `${projectRoot}/.github/workflows/${oldName}.yml`,
20
+ `${projectRoot}/.github/workflows/${newName}.yml`
21
+ );
22
+ });
23
+ });
@@ -0,0 +1,5 @@
1
+ import {fileTypes, writeConfigFile} from '@form8ion/core';
2
+
3
+ export default function ({projectRoot, name, config}) {
4
+ return writeConfigFile({path: `${projectRoot}/.github/workflows`, name, config, format: fileTypes.YAML});
5
+ }
@@ -0,0 +1,25 @@
1
+ import {fileTypes, writeConfigFile} from '@form8ion/core';
2
+
3
+ import any from '@travi/any';
4
+ import {describe, expect, it, vi} from 'vitest';
5
+
6
+ import writeWorkflowFile from './writer.js';
7
+
8
+ vi.mock('@form8ion/core');
9
+
10
+ describe('workflow writer', () => {
11
+ it('should write the workflow to the workflows directory as a yaml file', async () => {
12
+ const projectRoot = any.string();
13
+ const name = any.word();
14
+ const config = any.simpleObject();
15
+
16
+ await writeWorkflowFile({projectRoot, config, name});
17
+
18
+ expect(writeConfigFile).toHaveBeenCalledWith({
19
+ path: `${projectRoot}/.github/workflows`,
20
+ name,
21
+ config,
22
+ format: fileTypes.YAML
23
+ });
24
+ });
25
+ });