mirror of
https://github.com/renovatebot/renovate.git
synced 2026-11-01 02:00:10 +00:00
134dc6cd38
As we've recently had a couple of cases where a `devDependency` has been
used in a production file, and CI hasn't caught it, we end up seeing
i.e.
ERROR: unhandledRejection
"err": {
"code": "ERR_MODULE_NOT_FOUND",
"message": "Cannot find package 'common-tags' imported from
/usr/local/renovate/dist/workers/repository/update/pr/index.js"
}
(see 5ee51fdf39 and
1d33143727 for two cases this has happened
with)
One option for caching this is a linting rule, which stops runtime usage
in `lib/` on a dependency in `devDependencies`.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
89 lines
2.9 KiB
TypeScript
89 lines
2.9 KiB
TypeScript
import { RuleTester } from 'oxlint/plugins-dev';
|
|
import rule from './no-dev-dependency-import.ts';
|
|
|
|
RuleTester.describe = describe;
|
|
RuleTester.it = it;
|
|
|
|
const ruleTester = new RuleTester({
|
|
languageOptions: { parserOptions: { lang: 'ts' } },
|
|
});
|
|
|
|
// `vitest` is a real `devDependencies` entry; `commander` is a real
|
|
// `dependencies` entry. Both are read from this repo's package.json.
|
|
ruleTester.run('no-dev-dependency-import', rule, {
|
|
valid: [
|
|
// relative and absolute imports are never package imports
|
|
`import { foo } from './foo.ts';`,
|
|
`import { foo } from '/foo.ts';`,
|
|
// a real production dependency
|
|
`import { Command } from 'commander';`,
|
|
// a devDependency is fine as a type-only import
|
|
`import type { Something } from 'vitest';`,
|
|
// a devDependency is fine when every specifier is type-only
|
|
`import { type Something } from 'vitest';`,
|
|
// a devDependency is fine as a type-only re-export
|
|
`export type { Something } from 'vitest';`,
|
|
`export { type Something } from 'vitest';`,
|
|
// no source
|
|
`export const foo = 1;`,
|
|
`export { foo };`,
|
|
// dynamic import with a non-literal source
|
|
`const p = import('../' + name);`,
|
|
// dynamic import with a non-string literal source
|
|
`const p = import(42);`,
|
|
// a devDependency is fine as a type-only `export *`
|
|
`export type * from 'vitest';`,
|
|
],
|
|
invalid: [
|
|
{
|
|
code: `import { describe } from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
{
|
|
code: `import vitest from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
{
|
|
code: `import * as vitest from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
// side-effect only import is a runtime import
|
|
{
|
|
code: `import 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
// mixed type/value specifiers still produce a runtime import
|
|
{
|
|
code: `import { type Something, describe } from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
// deep import from a devDependency
|
|
{
|
|
code: `import { describe } from 'vitest/dist/foo.js';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
// scoped devDependency
|
|
{
|
|
code: `import { something } from '@types/common-tags/foo.js';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
{
|
|
code: `export { describe } from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
{
|
|
code: `export * from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
// re-export with no specifiers is still a runtime import
|
|
{
|
|
code: `export {} from 'vitest';`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
{
|
|
code: `const p = import('vitest');`,
|
|
errors: [{ messageId: 'noDevDependencyImport' }],
|
|
},
|
|
],
|
|
});
|