Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
kawikabader committed Jan 31, 2025
1 parent fc37336 commit 0d21098
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 139 deletions.
19 changes: 5 additions & 14 deletions packages/gasket-plugin-lint/lib/code-styles.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
/* eslint-disable complexity,max-statements */
const { devDependencies } = require('../package.json');

/**
Expand All @@ -19,6 +18,7 @@ const godaddy = {
const hasNext = pkg.has('dependencies', 'next');

let configName = 'godaddy';

if (hasReact && hasFlow) {
configName = 'godaddy-react-flow';
} else if (hasReact) {
Expand All @@ -27,25 +27,15 @@ const godaddy = {
configName = 'godaddy-flow';
}

// limit the supported version ranges
const versionRanges = {
'godaddy': '^7.1.1',
'godaddy-react': '^9.1.0',
'godaddy-flow': '^6.0.2',
'godaddy-react-flow': '^6.0.2',
'@godaddy/eslint-plugin-react-intl': '^1.3.0',
'stylelint-config-godaddy': '^0.6.0'
};

pkg.add(
'devDependencies',
await gatherDevDeps(`eslint-config-${configName}@${versionRanges[configName]}`)
await gatherDevDeps(`eslint-config-${configName}`)
);
pkg.add('eslintConfig', { extends: [configName] });

if (hasReactIntl) {
const pluginName = '@godaddy/eslint-plugin-react-intl';
const deps = await gatherDevDeps(`${pluginName}@${versionRanges[pluginName]}`);
const deps = await gatherDevDeps(`${pluginName}`);
// only add the plugin to avoid stomping config version
pkg.add('devDependencies', {
[pluginName]: deps[pluginName]
Expand All @@ -60,7 +50,7 @@ const godaddy = {

if (addStylelint) {
const stylelintName = 'stylelint-config-godaddy';
pkg.add('devDependencies', await gatherDevDeps(`${stylelintName}@${versionRanges[stylelintName]}`));
pkg.add('devDependencies', await gatherDevDeps(`${stylelintName}`));
pkg.add('stylelint', { extends: [stylelintName] });
}

Expand Down Expand Up @@ -134,6 +124,7 @@ const airbnb = {
const hasNext = pkg.has('dependencies', 'next');

let configName = 'airbnb-base';

if (hasReact) configName = 'airbnb';

pkg.add(
Expand Down
2 changes: 1 addition & 1 deletion packages/gasket-plugin-lint/lib/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ async function create(gasket, context) {
codeStyle || ((eslintConfig || stylelintConfig) && 'other') || 'none';

if (selectedCodeStyle !== 'none') {
const gatherDevDeps = makeGatherDevDeps(context);
const gatherDevDeps = makeGatherDevDeps();
const runScriptStr = makeRunScriptStr(context);
const utils = { gatherDevDeps, runScriptStr };

Expand Down
16 changes: 9 additions & 7 deletions packages/gasket-plugin-lint/lib/internal.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import type { CreateContext } from 'create-gasket-app';

export interface Utils {
gatherDevDeps(pkgName: string): Promise<Record<string, string>>;
runScriptStr(script: string): string;
export function gatherDevDeps(pkgName: string): Record<string, string>;
export function runScriptStr(script: string): string;
export function safeRunScript(name: string): Promise<void>;
export function makeGatherDevDeps(): typeof gatherDevDeps;
export function makeRunScriptStr(context: CreateContext): typeof runScriptStr;
export function makeSafeRunScript(context: CreateContext, runScript): typeof safeRunScript;

type Utils = {
gatherDevDeps: typeof gatherDevDeps, runScriptStr?: typeof runScriptStr
}

export interface CodeStyle {
name?: string;
allowStylelint?: boolean;
create?: (context: CreateContext, utils: Utils) => Promise<void>;
}

export interface LintUtils {
makeGatherDevDeps(context: CreateContext): Promise<string[]>;
}
168 changes: 100 additions & 68 deletions packages/gasket-plugin-lint/lib/utils.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,132 @@
/// <reference types="create-gasket-app" />

const semver = require('semver');
const reName = /^(@?[\w/-]+)@?(.*)/;

/**
* Makes a function to look up package dependencies the current create context
* @param {import('create-gasket-app').CreateContext} context - Create context
* @returns {function(string): Promise<Record<string, string>>} gatherDevDeps
* Creates a function that retrieves the development dependencies for a specified package.
*
* This function ensures that only valid package names are processed and throws an error
* if the package name is invalid or missing from the predefined dependencies list.
* @type {import('./internal').makeGatherDevDeps}
*/
function makeGatherDevDeps(context) {
const { pkgManager } = context;
function makeGatherDevDeps() {
var eslintVersion = '^8.57.1';
var babelCore = { '@babel/core': '>=7' };
var eslintBaseDeps = { eslint: eslintVersion };
var eslintImport = { 'eslint-plugin-import': '^2.27.5' };

var dependencies = {
'eslint-config-godaddy': { ...eslintBaseDeps, 'eslint-config-godaddy': '^7.1.1' },
'eslint-config-godaddy-react': { ...eslintBaseDeps, 'eslint-config-godaddy-react': '^9.1.0', ...babelCore },
'eslint-config-godaddy-flow': { ...eslintBaseDeps, 'eslint-config-godaddy-flow': '^6.0.2', ...babelCore },
'eslint-config-godaddy-react-flow': { ...eslintBaseDeps, 'eslint-config-godaddy-react-flow': '^6.0.2', ...babelCore },
'@godaddy/eslint-plugin-react-intl': { ...eslintBaseDeps, '@godaddy/eslint-plugin-react-intl': '^1.3.0' },
'stylelint-config-godaddy': { 'stylelint-config-godaddy': '^0.6.0', 'stylelint': '^15' },
'eslint-config-standard': {
...eslintBaseDeps,
'eslint-config-standard': '^17.1.0',
...eslintImport,
'eslint-plugin-n': '^16.0.0',
'eslint-plugin-promise': '^6.1.1'
},
'eslint-config-airbnb': {
...eslintBaseDeps,
'eslint-config-airbnb': '^19.0.4',
...eslintImport,
'eslint-plugin-jsx-a11y': '^6.6.1',
'eslint-plugin-react': '^7.32.2',
'eslint-plugin-react-hooks': '^4.6.0'
},
'eslint-config-airbnb-base': { ...eslintBaseDeps, 'eslint-config-airbnb-base': '^15.0.0', ...eslintImport },
'eslint-config-next': { ...eslintBaseDeps, 'eslint-config-next': '^13.2.1', 'typescript': '^5.2.2' },
'stylelint-config-airbnb': {
'stylelint-config-airbnb': '^0.0.0',
'stylelint': '^8.0.0',
'stylelint-order': '^0.7.0',
'stylelint-scss': '^1.2.1'
}
};

/**
* Looks up the latest version of specific package if not set, and gets info
* on its peerDependencies. These, along with the original package w/ version
* will be returned, which can then be used to add to the apps
* devDependencies.
* @param {string} rawName - Name of package with option version
* @returns {Promise<Record<string, string>>} dependencies
* Retrieves the dependencies for a given package name.
* @type {import('./internal').gatherDevDeps}
*/
return async function gatherDevDeps(rawName) {
const [, parsedName, parsedVersion] = reName.exec(rawName);

let version = parsedVersion
? semver.minVersion(parsedVersion).version
: null;
function gatherDevDeps(name) {
if (typeof name !== 'string' || !name.trim()) {
console.error(`Invalid package name: ${JSON.stringify(name)}`);
throw new TypeError('Package name must be a non-empty string.');
}

if (!version) {
version = (await pkgManager.info([parsedName, 'version'])).data;
if (!(name in dependencies)) {
console.error(`Package not found: ${name}`);
throw new ReferenceError(`No dependency information found for package: ${name}`);
}

const full = `${parsedName}@${version.trim()}`;
const peerDeps = (await pkgManager.info([full, 'peerDependencies'])).data;
return dependencies[name];
}

return {
[parsedName]: parsedVersion || `^${version}`,
...(peerDeps || {})
};
};
return gatherDevDeps;
}

/**
* Makes a function to generate a package script string under the current create
* context
* @param {import("create-gasket-app").CreateContext} context - Create context
* @returns {function(string): string} runScriptStr
* Creates a function to generate the correct package script execution command.
*
* This function returns a script command formatted for either npm or yarn, depending
* on the package manager used in the given context.
* @type {import('./internal').makeRunScriptStr}
*/
function makeRunScriptStr(context) {
const { packageManager } = context;
const runCmd = packageManager === 'npm' ? 'npm run' : packageManager;
var runCmd = context.packageManager === 'npm' ? 'npm run' : context.packageManager;

/**
* Accepts a script name and adds `npm run` or `yarn`. If extra flags are
* needed, use the extra `--` option following npm format, which will be
* removed when the packageManager is yarn.
* @see https://docs.npmjs.com/cli/run-script
* @param {string} script - Name of script to run
* @returns {string} modifed script
* Formats the script command for execution.
* @type {import('./internal').runScriptStr}
*/
return function runScriptStr(script) {
let str = [runCmd, script].join(' ');

if (runCmd === 'yarn') {
str = str.replace(' -- ', ' ');
function runScriptStr(script) {
if (typeof script !== 'string' || !script.trim()) {
console.error(`Invalid script name: ${JSON.stringify(script)}`);
throw new TypeError('Script name must be a non-empty string.');
}

return str;
};
return runCmd === 'yarn' ? (runCmd + ' ' + script).replace(' -- ', ' ') : runCmd + ' ' + script;
}

return runScriptStr;
}

/**
* Makes a function to run scripts safely under the current create context
* @param {import("create-gasket-app").CreateContext} context - Create context
* @param {Function} runScript - Script runner util
* @returns {Function} safeRunScript
* Creates a function to safely execute package scripts, ensuring errors do not
* cause a complete failure during project setup.
*
* This function checks whether a script exists in the `package.json` scripts section
* before attempting to run it. Errors are logged and stored in the warnings array
* instead of halting execution.
* @type {import('./internal').makeSafeRunScript}
*/
function makeSafeRunScript(context, runScript) {
const { pkg, warnings } = context;

/**
* Runs lint scripts safely by catching errors showing warnings in the create
* report. We do not want to fail a create for these, because lint
* configurations are fragile in nature due to combination of style choices
* and generated content.
* @param {string} name - package.json script to run
* @returns {Promise<void>} promise
* Runs a script safely without stopping execution on errors.
* @type {import('./internal').safeRunScript}
*/
return async function safeRunScript(name) {
if (pkg.has('scripts', name)) {
try {
await runScript(name);
} catch (e) {
warnings.push(`Errors encountered running script: '${name}'`);
}
async function safeRunScript(name) {
if (typeof name !== 'string' || !name.trim()) {
console.error(`Invalid script name: ${JSON.stringify(name)}`);
throw new TypeError('Script name must be a non-empty string.');
}
};

if (!context.pkg.has('scripts', name)) {
console.warn(`Script '${name}' not found in package.json.`);

return;
}

try {
await runScript(name);
} catch (error) {
console.error(`Error running script '${name}':`, error);
context.warnings.push(`Errors encountered running script: '${name}'`);
}
}

return safeRunScript;
}

module.exports = {
Expand Down
1 change: 1 addition & 0 deletions packages/gasket-plugin-lint/test/code-styles.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('code styles', () => {
utils = {
gatherDevDeps: jest.fn().mockImplementation(dep => {
const depName = /^@?[^@]+/.exec(dep)[0];

return Promise.resolve({
[depName]: '*',
example: 'latest'
Expand Down
16 changes: 14 additions & 2 deletions packages/gasket-plugin-lint/test/create.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const mockMakeGatherDevDeps = jest.fn().mockImplementation(() => jest.fn());
const mockMakeRunScriptStr = jest.fn().mockImplementation(() => jest.fn());

const mockCodeStyles = {
godaddy: { create: jest.fn() },
standard: { create: jest.fn() },
Expand All @@ -18,6 +19,7 @@ jest.mock('../lib/utils', () => {
makeRunScriptStr: mockMakeRunScriptStr
};
});

jest.mock('../lib/code-styles', () => mockCodeStyles);

const createHook = require('../lib').hooks.create;
Expand All @@ -38,12 +40,12 @@ describe('create hook', function () {

it('makes a gatherDevDeps function for utils', async () => {
await createHookHandler(gasket, context);
expect(mockMakeGatherDevDeps).toHaveBeenCalledWith(context);
expect(mockMakeGatherDevDeps).toHaveBeenCalled();
});

it('makes a runScriptStr function for utils', async () => {
await createHookHandler(gasket, context);
expect(mockMakeGatherDevDeps).toHaveBeenCalledWith(context);
expect(mockMakeRunScriptStr).toHaveBeenCalled();
});

it('executes create for selected code style', async () => {
Expand Down Expand Up @@ -82,4 +84,14 @@ describe('create hook', function () {
expect(mockCodeStyles.other.create).toHaveBeenCalled();
expect(mockCodeStyles.common.create).toHaveBeenCalled();
});

it('throws error if makeGatherDevDeps fails', async () => {
mockMakeGatherDevDeps.mockImplementation(() => {
throw new Error('Failed to create gatherDevDeps');
});

await expect(createHookHandler(gasket, context)).rejects.toThrow(
'Failed to create gatherDevDeps'
);
});
});
Loading

0 comments on commit 0d21098

Please sign in to comment.