Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
16 changes: 14 additions & 2 deletions .github/actions/loki/fuselageSnap/src/getAffectedComponents.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async function mergeCmpDeps(saveDirectDps, saveIndirectDps, pkgToFileMap) {

function potentialFullTest(changedFiles) {
for (const file of changedFiles) {
if (file.includes('tools') || file.includes('yarn.lock')) {
if (file.includes('yarn.lock')) {
return true;
}
const pkg = file.split('/')[0];
Expand All @@ -123,11 +123,23 @@ export const getAffectedComponents = async (changedFiles) => {
layout,
};
}

const filterChangedFiles = [];
const unfilteredChangedFiles = [];
for (const file of changedFiles) {
if (file.includes('packages')) {
filterChangedFiles.push(file);
// SCSS files are not included in the webpack dependency graph.
// If a changed file is a `*.styles.scss` inside `components/`,
// map it to its parent component’s `.tsx` file.
// Example: `Button.styles.scss` → `Button.tsx`
if (file.includes('styles.scss') && file.includes('components')) {
const part = file.split('/');
const replace = part[part.length - 2];
part[part.length - 1] = `${replace}.tsx`;
filterChangedFiles.push(part.join('/'));
} else {
filterChangedFiles.push(file);
}
} else {
unfilteredChangedFiles.push(file);
}
Expand Down
33 changes: 29 additions & 4 deletions .github/actions/loki/fuselageSnap/src/getIndirectDependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,19 +74,44 @@ const graph = [
{ 'tools-utils': 'lint-all' },
{ 'update-readme': 'lint-all' },
];

/**
*
* @param {string} pkgName - Name of the package that a changed file belongs to.
* @returns {Promise<Array<Object<string, Set<string>>>>} - A list of objects where each key is the dependent package name
* and the value is a Set of affected component titles.
*/
export const getIndirectDps = async (pkgName) => {
const relevantGraph = graph.filter((obj) => {
export const traverseGraph = (pkgName) => {
const traversedData = graph.filter((obj) => {
const key = Object.keys(obj)[0];
return isStoryBookPkg(key) && Object.values(obj)[0] === pkgName;
});

return traversedData;
};
export const mergePkgsObj = (pkgName) => {
const relevantGraph = traverseGraph(pkgName);
const transitive = [];
for (const obj of relevantGraph) {
transitive.push(traverseGraph(Object.keys(obj)[0]));
}
for (const arr of transitive) {
for (const obj_tr of arr) {
const transitive_keys = Object.keys(obj_tr)[0];
let isPresent = 0;
for (const obj_rel of relevantGraph) {
const rel_keys = Object.keys(obj_rel)[0];
if (transitive_keys === rel_keys) {
isPresent = 1;
}
}
if (isPresent === 0) {
relevantGraph.push(obj_tr);
}
}
}
return relevantGraph;
};
export const getIndirectDps = async (pkgName) => {
const relevantGraph = mergePkgsObj(pkgName);
const promises = relevantGraph.map(async (obj) => {
const key = Object.keys(obj)[0];
const result = await getReasons(
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"update-storybook": "yarn turbo run update-storybook",
"loki:test": "yarn turbo run loki:test --cache-dir=\".turbo\"",
"loki:test-ci": "yarn turbo run loki:test-ci --cache-dir=\".turbo\"",
"update-readme": "update-readme"
"update-readme": "update-readme",
"fuselageSnap": "fuselagesnap-local"
},
"devDependencies": {
"@changesets/changelog-github": "~0.5.1",
Expand All @@ -44,6 +45,7 @@
"eslint-plugin-react-hooks": "~5.2.0",
"eslint-plugin-storybook": "~9.0.11",
"execa": "~9.6.0",
"fuselagesnap-local": "workspace:~",
"globals": "~16.2.0",
"husky": "~9.1.7",
"hygen": "~6.2.11",
Expand Down
2 changes: 2 additions & 0 deletions tools/fuselageSnap-local/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
2 changes: 2 additions & 0 deletions tools/fuselageSnap-local/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
!.*
209 changes: 209 additions & 0 deletions tools/fuselageSnap-local/bin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import { spawn } from 'child_process';
import { writeFileSync } from 'fs';

import { execa } from 'execa';

import { getAffectedComponents } from '../../../.github/actions/loki/fuselageSnap/src/getAffectedComponents.js';
import { mergePkgsObj } from '../../../.github/actions/loki/fuselageSnap/src/getIndirectDependency.js';
import { trimStatsFile } from '../../../.github/actions/loki/fuselageSnap/src/stats/trimStatsFile.js';
import { copyFiles } from '../../../.github/actions/loki/fuselageSnap/src/utils/copyFiles.js';
import { generateRegex } from '../../../.github/actions/loki/fuselageSnap/src/utils/generateRegex.js';
import { getChangedFileLocal } from '../src/git_local.js';
// yarn build-storybook --stats-json gives project-stats.json which has component titles
// where as index.json gives the webpack base dependency graph
// index.js run from the root of the project
const filesToCopy = [
{
src: './packages/fuselage/storybook-static/index.json',
dest: '.github/actions/loki/fuselageSnap/dist/fuselage-sb.json',
},
{
src: './packages/fuselage-toastbar/storybook-static/index.json',
dest: '.github/actions/loki/fuselageSnap/dist/fuselage-toastbar-sb.json',
},
{
src: './packages/onboarding-ui/storybook-static/index.json',
dest: '.github/actions/loki/fuselageSnap/dist/onboarding-ui-sb.json',
},
{
src: './packages/layout/storybook-static/index.json',
dest: '.github/actions/loki/fuselageSnap/dist/layout-sb.json',
},
{
src: './packages/fuselage/storybook-static/preview-stats.json',
dest: '.github/actions/loki/fuselageSnap/dist/fuselage-stats.json',
},
{
src: './packages/fuselage-toastbar/storybook-static/preview-stats.json',
dest: '.github/actions/loki/fuselageSnap/dist/fuselage-toastbar-stats.json',
},
{
src: './packages/onboarding-ui/storybook-static/preview-stats.json',
dest: '.github/actions/loki/fuselageSnap/dist/onboarding-ui-stats.json',
},
{
src: './packages/layout/storybook-static/preview-stats.json',
dest: '.github/actions/loki/fuselageSnap/dist/layout-stats.json',
},
];

export const execCommand = (command) => {
return new Promise((resolve, reject) => {
const childProcess = spawn(command, {
stdio: 'inherit',
shell: true,
});
childProcess.on('error', (error) => {
reject(error);
});
childProcess.on('exit', () => {
resolve();
});
});
};

writeFileSync(
'.github/actions/loki/fuselageSnap/dist/save.json',
'{"file_names":[]}',
function (err) {
if (err) {
return console.log(err);
}
console.log('The file was saved!');
},
);
writeFileSync(
'.github/actions/loki/fuselageSnap/dist/non-storybook-files.json',
'{"file_names":[]}',
function (err) {
if (err) {
return console.log(err);
}
console.log('The file was saved!');
},
);
async function copyFilesToDest(filesToCopy, location) {
const promises = [];
for (const pkg of location) {
for (const { src, dest } of filesToCopy) {
if (src.split('/')[2] === pkg) {
copyFiles(src, dest);
if (dest.includes('stats')) {
const trimmedPath = `.github/actions/loki/fuselageSnap/dist/trimmed-${dest.split('/').slice(-1)}`;
promises.push(trimStatsFile(dest, trimmedPath));
}
}
}
}

await Promise.all(promises);
}
async function run() {
const buildStoryBookPromises = [];
const headCommit = await execa`git rev-parse HEAD`;
const changedFiles = await getChangedFileLocal(headCommit.stdout);
const storyBookPkgs = new Set();
for (const file of changedFiles) {
if (file.includes('packages')) {
if (file.split('/')[1] === 'fuselage') {
storyBookPkgs.add('fuselage');
break;
}
storyBookPkgs.add(file.split('/')[1]);
const pkgMap = mergePkgsObj(file.split('/')[1]);
for (const pkgName of pkgMap) {
storyBookPkgs.add(Object.keys(pkgName)[0]);
}
}
if (file.includes('yarn.lock') || file.includes('package.json')) {
storyBookPkgs.add('fuselage');
}
}
for (const pkg of storyBookPkgs) {
if (pkg === 'fuselage') {
storyBookPkgs.delete('fuselage-toastbar');
storyBookPkgs.delete('layout');
storyBookPkgs.delete('onboarding-ui');
} else if (pkg === 'layout') {
storyBookPkgs.delete('fuselage-toastbar');
storyBookPkgs.delete('onboarding-ui');
}
}
const storyBookFilesToCopy = [];
for (const pkg of storyBookPkgs) {
if (pkg === 'fuselage') {
buildStoryBookPromises.push(execCommand('yarn build-storybook'));
storyBookFilesToCopy.push('fuselage');
storyBookFilesToCopy.push('fuselage-toastbar');
storyBookFilesToCopy.push('layout');
storyBookFilesToCopy.push('onboarding-ui');
break;
} else if (pkg === 'fuselage-toastbar') {
buildStoryBookPromises.push(
execCommand('cd packages/fuselage-toastbar && yarn build-storybook'),
);
storyBookFilesToCopy.push('fuselage-toastbar');
} else if (pkg === 'layout') {
buildStoryBookPromises.push(
execCommand('cd packages/fuselage-toastbar && yarn build-storybook'),
);
buildStoryBookPromises.push(
execCommand('cd packages/onboarding-ui && yarn build-storybook'),
);
buildStoryBookPromises.push(
execCommand('cd packages/layout && yarn build-storybook'),
);
storyBookFilesToCopy.push('fuselage-toastbar');
storyBookFilesToCopy.push('onboarding-ui');
storyBookFilesToCopy.push('layout');
} else if (pkg === 'onboarding-ui') {
buildStoryBookPromises.push(
execCommand('cd packages/onboarding-ui && yarn build-storybook'),
);
storyBookFilesToCopy.push('onboarding-ui');
}
}
await Promise.all(buildStoryBookPromises);
if (storyBookFilesToCopy.length > 0) {
await copyFilesToDest(filesToCopy, storyBookFilesToCopy);
}
const data = await getAffectedComponents(changedFiles);
const regex = generateRegex(data);
const pkgs = ['fuselage', 'fuselage-toastbar', 'layout', 'onboarding-ui'];

const colorize = (...args) => ({
black: `\x1b[30m${args.join(' ')}`,
red: `\x1b[31m${args.join(' ')}`,
green: `\x1b[32m${args.join(' ')}`,
yellow: `\x1b[33m${args.join(' ')}`,
blue: `\x1b[34m${args.join(' ')}`,
magenta: `\x1b[35m${args.join(' ')}`,
cyan: `\x1b[36m${args.join(' ')}`,
white: `\x1b[37m${args.join(' ')}`,
bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`,
});
/* eslint-disable no-await-in-loop */
for (const pkg of pkgs) {
if (regex[`${pkg}`].length === 0) {
console.log(
colorize(`skipping ${pkg} no affected components found`).bgBlue,
);
} else if (regex[`${pkg}`] === 'full test') {
console.log(colorize(`Running full visual tests for ${pkg}`).bgBlue);
await execCommand(`cd packages/${pkg} && yarn loki:test`);
} else {
console.log(colorize(`Running fuselageSnap for ${pkg}`).bgBlue);
await execCommand(
`cd packages/${pkg} && yarn loki:test --storiesFilter="${regex[pkg]}"`,
);
}
}
}
run();
16 changes: 16 additions & 0 deletions tools/fuselageSnap-local/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"private": true,
"name": "fuselagesnap-local",
"scripts": {
"lint": "lint .",
"lint-and-fix": "lint-and-fix"
},
"type": "module",
"bin": "bin/index.js",
"devDependencies": {
"eslint": "^9.29.0",
"lint-all": "workspace:~",
"prettier": "~3.5.3"
},
"version": "0.0.1"
}
16 changes: 16 additions & 0 deletions tools/fuselageSnap-local/src/git_local.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { execa } from 'execa';

export async function getChangedFileLocal(headCommit) {
// `git --no-pager diff --name-only --no-relative ${baseCommit} ${headCommit}`
const { stdout } = await execa('git', [
'--no-pager',
'diff',
'--name-only',
'--no-relative',
headCommit,
]);
const changedFile = stdout.split('\n');
// console.log(headCommit);
// see the changed files using the sha
return changedFile;
}
13 changes: 13 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5548,6 +5548,7 @@ __metadata:
eslint-plugin-react-hooks: "npm:~5.2.0"
eslint-plugin-storybook: "npm:~9.0.11"
execa: "npm:~9.6.0"
fuselagesnap-local: "workspace:~"
globals: "npm:~16.2.0"
husky: "npm:~9.1.7"
hygen: "npm:~6.2.11"
Expand Down Expand Up @@ -12661,6 +12662,18 @@ __metadata:
languageName: node
linkType: hard

"fuselagesnap-local@workspace:tools/fuselageSnap-local, fuselagesnap-local@workspace:~":
version: 0.0.0-use.local
resolution: "fuselagesnap-local@workspace:tools/fuselageSnap-local"
dependencies:
eslint: "npm:^9.29.0"
lint-all: "workspace:~"
prettier: "npm:~3.5.3"
bin:
fuselagesnap-local: bin/index.js
languageName: unknown
linkType: soft

"fuselagesnap@workspace:.github/actions/loki/fuselageSnap":
version: 0.0.0-use.local
resolution: "fuselagesnap@workspace:.github/actions/loki/fuselageSnap"
Expand Down
Loading