feature: add no-trigger-core-import (#1175)

* feature: add no-trigger-core-import

* fix: handle aliases and limit to exported modules

* fix: use longest export map name

* fix: support export type and interface

* fix: simplify message when export map is needed

* feature: fine grained modules for core

* fix: add prerequisite lint plugins

* fix: why do I have to fix these components again???

* fix: tests

* Add eslint config and vscode settings

* Create yellow-roses-arrive.md

* Remove the vscode linting

This is temporary so no one fixes these until we merge the remix branch

---------

Co-authored-by: Eric Allam <eric@trigger.dev>
Co-authored-by: Eric Allam <eallam@icloud.com>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
This commit is contained in:
Jacob Paris
2024-07-11 17:14:55 +03:00
committed by GitHub
parent 7fdedf3137
commit d934feb02a
17 changed files with 1569 additions and 193 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@trigger.dev/core": patch
---
Add more package exports that can be used from the web app
+33 -10
View File
@@ -1,13 +1,36 @@
{
"extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node", "prettier"],
"rules": {
"@typescript-eslint/strict-boolean-expressions": [
"error",
{
"allowNullableBoolean": true,
"allowNullableString": true,
"allowNullableNumber": true
"plugins": [
"@trigger.dev/eslint-plugin",
"react-hooks",
"@typescript-eslint/eslint-plugin",
"import"
],
"parser": "@typescript-eslint/parser",
"overrides": [
{
"files": ["*.ts", "*.tsx"],
"rules": {
// Autofixes imports from "@trigger.dev/core" to fine grained modules
"@trigger.dev/no-trigger-core-import": "error",
// Normalize `import type {}` and `import { type }`
"@typescript-eslint/consistent-type-imports": [
"warn",
{
// the "type" annotation can get tangled and cause syntax errors
// during some autofixes, so easier to just turn it off
"prefer": "type-imports",
"disallowTypeAnnotations": true,
"fixStyle": "inline-type-imports"
}
],
// no-trigger-core-import splits imports into multiple lines
// this one merges them back into a single line
// if they still import from the same module
"import/no-duplicates": ["warn", { "prefer-inline": true }],
// lots of undeclared vars, enable this rule if you want to clean them up
"turbo/no-undeclared-env-vars": "off"
}
]
}
}
],
"ignorePatterns": ["seed.js", "seedCloud.ts", "populate.js"]
}
@@ -8,12 +8,8 @@ const Alert = AlertDialogPrimitive.Root;
const AlertTrigger = AlertDialogPrimitive.Trigger;
const AlertPortal = ({
className,
children,
...props
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal className={cn(className)} {...props}>
const AlertPortal = ({ children, ...props }: AlertDialogPrimitive.AlertDialogPortalProps) => (
<AlertDialogPrimitive.Portal {...props}>
<div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
{children}
</div>
@@ -10,8 +10,8 @@ const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
const DialogPortal = ({ children, ...props }: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
</div>
@@ -28,8 +28,8 @@ interface SheetPortalProps
extends SheetPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
const SheetPortal = ({ position, className, children, ...props }: SheetPortalProps) => (
<SheetPrimitive.Portal className={cn(className)} {...props}>
const SheetPortal = ({ position, children, ...props }: SheetPortalProps) => (
<SheetPrimitive.Portal {...props}>
<div className={portalVariants({ position })}>{children}</div>
</SheetPrimitive.Portal>
);
+4
View File
@@ -188,6 +188,7 @@
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
"@total-typescript/ts-reset": "^0.4.2",
"@trigger.dev/eslint-plugin": "workspace:*",
"@types/bcryptjs": "^2.4.2",
"@types/compression": "^1.7.2",
"@types/eslint": "^8.4.6",
@@ -221,6 +222,9 @@
"esbuild": "^0.15.10",
"eslint": "^8.24.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-turbo": "^2.0.4",
"npm-run-all": "^4.1.5",
"postcss-import": "^16.0.1",
"postcss-loader": "^8.1.1",
+1
View File
@@ -53,6 +53,7 @@
"@types/node": "18.17.1",
"autoprefixer": "^10.4.12",
"eslint-config-custom": "workspace:*",
"eslint-plugin-turbo": "^2.0.4",
"prettier": "^3.0.0",
"tsx": "^3.7.1",
"turbo": "^1.10.3",
+136 -16
View File
@@ -21,6 +21,70 @@
"require": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./eventFilterMatches": {
"import": {
"types": "./dist/eventFilterMatches.d.mts",
"default": "./dist/eventFilterMatches.mjs"
},
"require": "./dist/eventFilterMatches.js",
"types": "./dist/eventFilterMatches.d.ts"
},
"./replacements": {
"import": {
"types": "./dist/replacements.d.mts",
"default": "./dist/replacements.mjs"
},
"require": "./dist/replacements.js",
"types": "./dist/replacements.d.ts"
},
"./requestFilterMatches": {
"import": {
"types": "./dist/requestFilterMatches.d.mts",
"default": "./dist/requestFilterMatches.mjs"
},
"require": "./dist/requestFilterMatches.js",
"types": "./dist/requestFilterMatches.d.ts"
},
"./retry": {
"import": {
"types": "./dist/retry.d.mts",
"default": "./dist/retry.mjs"
},
"require": "./dist/retry.js",
"types": "./dist/retry.d.ts"
},
"./utils": {
"import": {
"types": "./dist/utils.d.mts",
"default": "./dist/utils.mjs"
},
"require": "./dist/utils.js",
"types": "./dist/utils.d.ts"
},
"./schemas": {
"import": {
"types": "./dist/schemas/index.d.mts",
"default": "./dist/schemas/index.mjs"
},
"require": "./dist/schemas/index.js",
"types": "./dist/schemas/index.d.ts"
},
"./types": {
"import": {
"types": "./dist/types.d.mts",
"default": "./dist/types.mjs"
},
"require": "./dist/types.js",
"types": "./dist/types.d.ts"
},
"./versions": {
"import": {
"types": "./dist/versions.d.mts",
"default": "./dist/versions.mjs"
},
"require": "./dist/versions.js",
"types": "./dist/versions.d.ts"
},
"./v3": {
"import": {
"types": "./dist/v3/index.d.mts",
@@ -29,6 +93,22 @@
"require": "./dist/v3/index.js",
"types": "./dist/v3/index.d.ts"
},
"./v3/errors": {
"import": {
"types": "./dist/v3/errors.d.mts",
"default": "./dist/v3/errors.mjs"
},
"require": "./dist/v3/errors.js",
"types": "./dist/v3/errors.d.ts"
},
"./v3/logger-api": {
"import": {
"types": "./dist/v3/logger-api.d.mts",
"default": "./dist/v3/logger-api.mjs"
},
"require": "./dist/v3/logger-api.js",
"types": "./dist/v3/logger-api.d.ts"
},
"./v3/otel": {
"import": {
"types": "./dist/v3/otel/index.d.mts",
@@ -37,6 +117,62 @@
"require": "./dist/v3/otel/index.js",
"types": "./dist/v3/otel/index.d.ts"
},
"./v3/semanticInternalAttributes": {
"import": {
"types": "./dist/v3/semanticInternalAttributes.d.mts",
"default": "./dist/v3/semanticInternalAttributes.mjs"
},
"require": "./dist/v3/semanticInternalAttributes.js",
"types": "./dist/v3/semanticInternalAttributes.d.ts"
},
"./v3/utils/durations": {
"import": {
"types": "./dist/v3/utils/durations.d.mts",
"default": "./dist/v3/utils/durations.mjs"
},
"require": "./dist/v3/utils/durations.js",
"types": "./dist/v3/utils/durations.d.ts"
},
"./v3/utils/flattenAttributes": {
"import": {
"types": "./dist/v3/utils/flattenAttributes.d.mts",
"default": "./dist/v3/utils/flattenAttributes.mjs"
},
"require": "./dist/v3/utils/flattenAttributes.js",
"types": "./dist/v3/utils/flattenAttributes.d.ts"
},
"./v3/utils/ioSerialization": {
"import": {
"types": "./dist/v3/utils/ioSerialization.d.mts",
"default": "./dist/v3/utils/ioSerialization.mjs"
},
"require": "./dist/v3/utils/ioSerialization.js",
"types": "./dist/v3/utils/ioSerialization.d.ts"
},
"./v3/utils/omit": {
"import": {
"types": "./dist/v3/utils/omit.d.mts",
"default": "./dist/v3/utils/omit.mjs"
},
"require": "./dist/v3/utils/omit.js",
"types": "./dist/v3/utils/omit.d.ts"
},
"./v3/utils/retries": {
"import": {
"types": "./dist/v3/utils/retries.d.mts",
"default": "./dist/v3/utils/retries.mjs"
},
"require": "./dist/v3/utils/retries.js",
"types": "./dist/v3/utils/retries.d.ts"
},
"./v3/utils/structuredLogger": {
"import": {
"types": "./dist/v3/utils/structuredLogger.d.mts",
"default": "./dist/v3/utils/structuredLogger.mjs"
},
"require": "./dist/v3/utils/structuredLogger.js",
"types": "./dist/v3/utils/structuredLogger.d.ts"
},
"./v3/zodfetch": {
"import": {
"types": "./dist/v3/zodfetch.d.mts",
@@ -77,22 +213,6 @@
"require": "./dist/v3/zodIpc.js",
"types": "./dist/v3/zodIpc.d.ts"
},
"./v3/utils/structuredLogger": {
"import": {
"types": "./dist/v3/utils/structuredLogger.d.mts",
"default": "./dist/v3/utils/structuredLogger.mjs"
},
"require": "./dist/v3/utils/structuredLogger.js",
"types": "./dist/v3/utils/structuredLogger.d.ts"
},
"./v3/utils/durations": {
"import": {
"types": "./dist/v3/utils/durations.d.mts",
"default": "./dist/v3/utils/durations.mjs"
},
"require": "./dist/v3/utils/durations.js",
"types": "./dist/v3/utils/durations.d.ts"
},
"./v3/utils/timers": {
"import": {
"types": "./dist/v3/utils/timers.d.mts",
+1 -27
View File
@@ -6,30 +6,4 @@ export * from "./replacements";
export * from "./searchParams";
export * from "./eventFilterMatches";
export * from "./requestFilterMatches";
export const API_VERSIONS = {
LAZY_LOADED_CACHED_TASKS: "2023-09-29",
SERIALIZED_TASK_OUTPUT: "2023-11-01",
} as const;
export const PLATFORM_FEATURES = {
yieldExecution: API_VERSIONS.LAZY_LOADED_CACHED_TASKS,
lazyLoadedCachedTasks: API_VERSIONS.LAZY_LOADED_CACHED_TASKS,
};
export function supportsFeature<TFeatureName extends keyof typeof PLATFORM_FEATURES>(
featureName: TFeatureName,
version: string
): boolean {
if (version === "unversioned" || version === "unknown") {
return false;
}
const supportedVersion = PLATFORM_FEATURES[featureName];
if (!supportedVersion) {
return false;
}
return version >= supportedVersion;
}
export * from "./versions";
+26
View File
@@ -0,0 +1,26 @@
export const API_VERSIONS = {
LAZY_LOADED_CACHED_TASKS: "2023-09-29",
SERIALIZED_TASK_OUTPUT: "2023-11-01",
} as const;
export const PLATFORM_FEATURES = {
yieldExecution: API_VERSIONS.LAZY_LOADED_CACHED_TASKS,
lazyLoadedCachedTasks: API_VERSIONS.LAZY_LOADED_CACHED_TASKS,
};
export function supportsFeature<TFeatureName extends keyof typeof PLATFORM_FEATURES>(
featureName: TFeatureName,
version: string
): boolean {
if (version === "unversioned" || version === "unknown") {
return false;
}
const supportedVersion = PLATFORM_FEATURES[featureName];
if (!supportedVersion) {
return false;
}
return version >= supportedVersion;
}
+23 -8
View File
@@ -4,21 +4,36 @@ export default defineConfig({
...packageOptions,
config: "tsconfig.build.json",
entry: [
"./src/eventFilterMatches.ts",
"./src/index.ts",
"./src/replacements.ts",
"./src/requestFilterMatches.ts",
"./src/retry.ts",
"./src/schemas/index.ts",
"./src/types.ts",
"./src/utils.ts",
"./src/versions.ts",
"./src/v3/dev/index.ts",
"./src/v3/errors.ts",
"./src/v3/index.ts",
"./src/v3/logger-api.ts",
"./src/v3/otel/index.ts",
"./src/v3/prod/index.ts",
"./src/v3/schemas/index.ts",
"./src/v3/semanticInternalAttributes.ts",
"./src/v3/utils/durations.ts",
"./src/v3/utils/flattenAttributes.ts",
"./src/v3/utils/ioSerialization.ts",
"./src/v3/utils/omit.ts",
"./src/v3/utils/retries.ts",
"./src/v3/utils/structuredLogger.ts",
"./src/v3/workers/index.ts",
"./src/v3/zodfetch.ts",
"./src/v3/zodIpc.ts",
"./src/v3/zodMessageHandler.ts",
"./src/v3/zodNamespace.ts",
"./src/v3/zodSocket.ts",
"./src/v3/zodIpc.ts",
"./src/v3/utils/structuredLogger.ts",
"./src/v3/utils/durations.ts",
"./src/v3/utils/timers.ts",
"./src/v3/dev/index.ts",
"./src/v3/prod/index.ts",
"./src/v3/workers/index.ts",
"./src/v3/zodfetch.ts",
"./src/v3/schemas/index.ts",
],
external: ["node:timers/promises"],
});
@@ -0,0 +1,69 @@
# Prevent importing from `@trigger.dev/core` directly (`trigger-dev/no-trigger-core-import`)
<!-- end auto-generated rule header -->
Due to [this Remix bug](https://github.com/remix-run/remix/issues/9597), the web app is very sensitive to importing server-side code when it's bundling for the client side. If a route imports from a barrel file that ALSO exports server-side code, it will break the webapp's client side navigation and the page simply refreshes. This only happens during development.
## Rule Details
This rule prevents importing from `@trigger.dev/core` and `@trigger.dev/core/v3` directly, which are barrel files that export server-side code.
It forces direct imports from the most specific file that exports a particular module and will autocorrect it.
Examples of **incorrect** code for this rule:
```ts
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
```
Examples of **correct** code for this rule:
```ts
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
import { parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
```
## When Not To Use It
This rule prevents issues when client-side navigating to routes that import from `@trigger.dev/core` or `@trigger.dev/core/v3`. If there will be no client-side navigation during development, this rule is not needed.
If [this bug](https://github.com/remix-run/remix/issues/9597) is fixed, this rule can be removed.
## Workflow
This rule runs in several steps
```ts
import { ScheduledTaskPayload, parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3";
```
First it splits Core imports into one line per import
```ts
import { ScheduledTaskPayload } from "@trigger.dev/core/v3";
import { parsePacket } from "@trigger.dev/core/v3";
import { prettyPrintPacket } from "@trigger.dev/core/v3";
```
Then refines each down to their most specific exported file
```ts
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas/api";
import { parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";
import { prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
```
if that exported file is downstream of an allowed barrel file (set to the schemas folders right now), it returns the export from the barrel instead
```ts
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
import { parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";
import { prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
```
then the normal lint plugin for merging multiple imports from the same file will run and merge any that are the same
```ts
import { ScheduledTaskPayload } from "@trigger.dev/core/v3/schemas";
import { parsePacket, prettyPrintPacket } from "@trigger.dev/core/v3/utils/ioSerialization";
```
@@ -0,0 +1,294 @@
/**
* @fileoverview Prevent importing from `@trigger.dev/core` directly
*/
"use strict";
const fs = require("fs");
const path = require("path");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const tsconfigPaths = require("tsconfig-paths");
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
const blockedImportSources = ["@trigger.dev/core", "@trigger.dev/core/v3"];
const allowedBarrelFiles = getAllowedBarrelFiles();
function getAllowedBarrelFiles() {
const packageJsonPath = path.resolve(
process.cwd().replace("apps/webapp", "").replace("packages/eslint-plugin", ""),
"packages/core/package.json"
);
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
const exports = packageJson.exports;
let allowedFiles = [];
for (let key in exports) {
if (exports.hasOwnProperty(key)) {
key = key.replace(/^\.\/?/, "");
if (key === "package.json") continue;
allowedFiles.push(["@trigger.dev/core", key].filter(Boolean).join("/"));
}
}
// sort by length so that longest path is first
allowedFiles.sort((a, b) => b.length - a.length);
// Filter out the blocked import sources
return allowedFiles.filter((file) => !blockedImportSources.includes(file));
}
function pathToCorePath(filePath) {
const baseName = "@trigger.dev/core";
const relativePath = filePath
.split("packages/core/src/")[1]
.replace(/\\/g, "/")
.replace("/index.ts", "");
return `${baseName}/${relativePath}`;
}
function resolveSpecifier(importSource, specifier, context) {
const corePath = resolveModulePath(importSource);
const coreDir = path.dirname(corePath);
const resolvedPath = resolveExport(coreDir, specifier);
if (resolvedPath) {
for (const allowedBarrelFile of allowedBarrelFiles) {
if (pathToCorePath(resolvedPath).startsWith(allowedBarrelFile)) {
return allowedBarrelFile;
}
}
return pathToCorePath(resolvedPath);
}
return null;
function resolveExport(fileOrDir, specifier) {
const filePath = fs.lstatSync(fileOrDir).isDirectory()
? path.join(fileOrDir, "index.ts")
: fileOrDir;
if (!fs.existsSync(filePath)) return null;
const code = fs.readFileSync(filePath, "utf8");
const ast = parser.parse(code, { sourceType: "module", plugins: ["typescript"] });
let foundPath = null;
traverse(ast, {
ExportNamedDeclaration({ node }) {
if (node.declaration) {
if (
// export const foo = 'bar';
(node.declaration.type === "VariableDeclaration" &&
node.declaration.declarations.some((decl) => decl.id.name === specifier)) ||
// export function foo() {}
(node.declaration.type === "FunctionDeclaration" &&
node.declaration.id.name === specifier) ||
// export class Foo {}
(node.declaration.type === "ClassDeclaration" &&
node.declaration.id.name === specifier) ||
// export type Foo = {};
(node.declaration.type === "TSTypeAliasDeclaration" &&
node.declaration.id.name === specifier) ||
// export interface Foo {}
(node.declaration.type === "TSInterfaceDeclaration" &&
node.declaration.id.name === specifier)
) {
foundPath = filePath;
return;
}
} else if (node.specifiers) {
for (const exportSpecifier of node.specifiers) {
if (exportSpecifier.exported.name === specifier) {
const sourcePath = node.source.value;
const dir = fs.lstatSync(fileOrDir).isDirectory()
? fileOrDir
: path.dirname(fileOrDir);
const resolvedSourcePath = tryResolveSourcePath(dir, sourcePath);
if (resolvedSourcePath) {
const resolvedExport = resolveExport(resolvedSourcePath, specifier);
if (resolvedExport) {
foundPath = resolvedExport;
return;
}
}
}
}
}
},
ExportAllDeclaration({ node }) {
const sourcePath = node.source.value;
const dir = fs.lstatSync(fileOrDir).isDirectory() ? fileOrDir : path.dirname(fileOrDir);
const resolvedSourcePath = tryResolveSourcePath(dir, sourcePath);
if (resolvedSourcePath) {
const resolvedExport = resolveExport(resolvedSourcePath, specifier);
if (resolvedExport) {
foundPath = resolvedExport;
}
}
},
});
return foundPath ? foundPath : null;
}
}
function tryResolveSourcePath(baseDir, sourcePath) {
const possibleExtensions = ["", ".ts", ".tsx", ".js", ".jsx"];
const possibleIndexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
for (const ext of possibleExtensions) {
const fullPath = path.resolve(baseDir, `${sourcePath}${ext}`);
if (fs.existsSync(fullPath)) {
return fullPath;
}
}
const dirPath = path.resolve(baseDir, sourcePath);
if (fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()) {
for (const indexFile of possibleIndexFiles) {
const indexPath = path.join(dirPath, indexFile);
if (fs.existsSync(indexPath)) {
return indexPath;
}
}
}
return null;
}
// Configure tsconfig-paths
function resolveModulePath(sourcePath) {
const cwd = path.resolve(process.cwd());
const tsconfigPath = path.resolve(cwd, "tsconfig.json");
const { absoluteBaseUrl, paths } = tsconfigPaths.loadConfig(tsconfigPath);
if (!absoluteBaseUrl || !paths) {
throw new Error("Could not load tsconfig paths");
}
const matchPath = tsconfigPaths.createMatchPath(absoluteBaseUrl, paths);
let resolvedPath = matchPath(sourcePath, undefined, undefined, [".ts", ".tsx", ".js", ".jsx"]);
if (resolvedPath) {
if (fs.existsSync(resolvedPath) && fs.lstatSync(resolvedPath).isDirectory()) {
const indexResolvedPath = path.join(resolvedPath, "index.ts");
if (fs.existsSync(indexResolvedPath)) {
resolvedPath = indexResolvedPath;
}
}
return path.resolve(resolvedPath);
}
return path.resolve(sourcePath);
}
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
type: "problem",
docs: {
description: "Prevent importing from `@trigger.dev/core` or `@trigger.dev/core/v3` directly",
recommended: true,
url: null,
},
fixable: "code",
schema: [],
messages: {
noTriggerCoreImportFixable: "Use specific import from '{{resolvedPath}}'",
noTriggerCoreImportNeedsExport:
"Should export from {{resolvedPath}} but not covered by @trigger.dev/core/package.json#exports",
noTriggerCoreImport:
"Cannot import from {{importSource}} but no specific import is available",
},
},
create(context) {
return {
ImportDeclaration(node) {
const importSource = node.source.value;
if (blockedImportSources.includes(importSource)) {
const specifierFixes = node.specifiers
.map((specifier) => {
const resolvedPath = resolveSpecifier(importSource, specifier.imported.name, context);
if (
resolvedPath &&
!blockedImportSources.includes(resolvedPath.replace("/index.ts", ""))
) {
return {
original: context.getSourceCode().getText(specifier),
path: resolvedPath,
};
}
return null;
})
.filter(Boolean);
if (specifierFixes.length > 0) {
const fixes = specifierFixes
.map((fix) => {
return `import { ${fix.original.replace("type ", "")} } from '${fix.path}';`;
})
.join("\n");
if (specifierFixes.every((fix) => allowedBarrelFiles.includes(fix.path))) {
context.report({
node,
messageId: "noTriggerCoreImportFixable",
data: {
importSource,
name: node.specifiers.map((spec) => spec.local.name).join(", "),
resolvedPath: Array.from(new Set(specifierFixes.map((fix) => fix.path))).join(
", "
),
},
fix(fixer) {
return fixer.replaceText(node, fixes);
},
});
} else {
context.report({
node,
messageId: "noTriggerCoreImportNeedsExport",
data: {
importSource,
name: node.specifiers.map((spec) => spec.local.name).join(", "),
resolvedPath: Array.from(
new Set(
specifierFixes
.filter((fix) => !allowedBarrelFiles.includes(fix.path))
.map((fix) => fix.path)
)
).join(", "),
},
});
}
} else {
context.report({
node,
messageId: "noTriggerCoreImport",
data: {
importSource,
},
});
}
}
},
};
},
};
+5 -1
View File
@@ -18,9 +18,13 @@
"update:eslint-docs": "eslint-doc-generator"
},
"dependencies": {
"requireindex": "^1.2.0"
"requireindex": "^1.2.0",
"tsconfig-paths": "^4.2.0"
},
"devDependencies": {
"@babel/parser": "^7.24.7",
"@babel/traverse": "^7.24.7",
"@types/babel__traverse": "^7.20.6",
"eslint": "^8.19.0",
"eslint-doc-generator": "^1.0.0",
"eslint-plugin-eslint-plugin": "^5.0.0",
@@ -0,0 +1,77 @@
/**
* @fileoverview Prevent importing from `@trigger.dev/core` directly
* @author
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const rule = require("../../../lib/rules/no-trigger-core-import"),
RuleTester = require("eslint").RuleTester;
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
const ruleTester = new RuleTester({
parserOptions: {
sourceType: "module",
ecmaVersion: 2020,
},
});
ruleTester.run("no-trigger-core-import", rule, {
valid: [
{
code: `import { conditionallyImportPacket, parsePacket } from "@trigger.dev/core/v3/utils/ioSerialization";`,
},
],
invalid: [
{
code: `import { parsePacket } from '@trigger.dev/core/v3';`,
output: `import { parsePacket } from '@trigger.dev/core/v3/utils/ioSerialization';`,
errors: [
{
messageId: "noTriggerCoreImportFixable",
},
],
},
{
code: `import { CreateBackgroundWorkerRequestBody, TaskResource } from '@trigger.dev/core/v3';`,
output: `import { CreateBackgroundWorkerRequestBody } from '@trigger.dev/core/v3/schemas';
import { TaskResource } from '@trigger.dev/core/v3/schemas';`,
errors: [
{
messageId: "noTriggerCoreImportFixable",
},
],
},
{
code: `import { CreateBackgroundWorkerRequestBody, stringifyIO } from '@trigger.dev/core/v3';`,
output: `import { CreateBackgroundWorkerRequestBody } from '@trigger.dev/core/v3/schemas';
import { stringifyIO } from '@trigger.dev/core/v3/utils/ioSerialization';`,
errors: [
{
messageId: "noTriggerCoreImportFixable",
},
],
},
{
code: `import {
isExceptionSpanEvent,
ExceptionEventProperties,
SpanEvent as OtelSpanEvent,
} from "@trigger.dev/core/v3";`,
output: `import { isExceptionSpanEvent } from '@trigger.dev/core/v3/schemas';
import { ExceptionEventProperties } from '@trigger.dev/core/v3/schemas';
import { SpanEvent as OtelSpanEvent } from '@trigger.dev/core/v3/schemas';`,
errors: [
{
messageId: "noTriggerCoreImportFixable",
},
],
},
],
});
+41
View File
@@ -0,0 +1,41 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"types": ["vitest/globals"],
"lib": ["ES2019"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "NodeNext",
"module": "NodeNext",
"resolveJsonModule": true,
"target": "ES2019",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"baseUrl": ".",
// Need paths for the no-trigger-core-import plugin
"paths": {
"~/*": ["./app/*"],
"@/*": ["./*"],
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
"@trigger.dev/core": ["../../packages/core/src/index"],
"@trigger.dev/core/*": ["../../packages/core/src/*"],
"@trigger.dev/core-backend": ["../../packages/core-backend/src/index"],
"@trigger.dev/core-backend/*": ["../../packages/core-backend/src/*"],
"@trigger.dev/database": ["../../packages/database/src/index"],
"@trigger.dev/database/*": ["../../packages/database/src/*"],
"@trigger.dev/yalt": ["../../packages/yalt/src/index"],
"@trigger.dev/yalt/*": ["../../packages/yalt/src/*"],
"@trigger.dev/otlp-importer": ["../../packages/otlp-importer/src/index"],
"@trigger.dev/otlp-importer/*": ["../../packages/otlp-importer/src/*"],
"emails": ["../../packages/emails/src/index"],
"emails/*": ["../../packages/emails/src/*"]
},
"noEmit": true
}
}
+848 -121
View File
File diff suppressed because it is too large Load Diff