$().${widgetName}('instance');\n`
+ );
+ })
+ .join(''),
+ )
+ .join('\n');
+
+ return content;
+}
+
+export function buildPublicModulesCheckContent(
+ modules: ModuleMetadata[],
+ packageDir: string,
+): string {
+ let content = "import $ from 'jquery';\n";
+
+ content += modules
+ .map((moduleMeta) => {
+ const modulePath = `'./npm/${packageDir}/${moduleMeta.name}'`;
+ if (!moduleMeta.exports) {
+ return `import ${modulePath};`;
+ }
+
+ return Object.keys(moduleMeta.exports)
+ .map((name) => {
+ const exportEntry = moduleMeta.exports![name];
+ const uniqueIdentifier = moduleMeta.name
+ .replace(/\./g, '_')
+ .split('/')
+ .concat([name])
+ .join('__');
+ const importIdentifier =
+ name === 'default' ? uniqueIdentifier : `{ ${name} as ${uniqueIdentifier} }`;
+ const importStatement = `import ${importIdentifier} from ${modulePath};`;
+ const widgetName = widgetNameByPath(exportEntry.path);
+
+ if (exportEntry.isWidget && widgetName) {
+ return `$('').${widgetName}();\n${importStatement}`;
+ }
+
+ return importStatement;
+ })
+ .join('\n');
+ })
+ .join('\n');
+
+ return content;
+}
diff --git a/packages/nx-infra-plugin/src/executors/check-declarations/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/check-declarations/executor.e2e.spec.ts
new file mode 100644
index 000000000000..4e8d0de74a47
--- /dev/null
+++ b/packages/nx-infra-plugin/src/executors/check-declarations/executor.e2e.spec.ts
@@ -0,0 +1,105 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import { logger } from '@nx/devkit';
+import executor from './executor';
+import { buildJqueryCheckContent, widgetNameByPath } from './declaration-check-content';
+import { toTripleSlashReferencePath } from './check-declarations.impl';
+import { CheckDeclarationsExecutorSchema } from './schema';
+import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils';
+import { writeFileText } from '../../utils';
+
+describe('declaration-check-content', () => {
+ it('should resolve widget name from global path', () => {
+ expect(widgetNameByPath('ui.dxButton')).toBe('dxButton');
+ expect(widgetNameByPath('ui.dxDataGrid.extra')).toBe('');
+ });
+
+ it('should compute triple-slash path relative to entry directory', () => {
+ const projectRoot = path.join('C:', 'proj');
+ const entryDir = path.join(projectRoot, 'artifacts');
+ const bundleFile = path.join(projectRoot, 'artifacts', 'ts', 'dx.all.d.ts');
+
+ expect(toTripleSlashReferencePath(entryDir, bundleFile)).toBe('./ts/dx.all.d.ts');
+ });
+
+ it('should emit jquery widget usage for public widget exports', () => {
+ const content = buildJqueryCheckContent('./ts/dx.all.d.ts', [
+ {
+ name: 'ui/button',
+ exports: { default: { path: 'ui.dxButton', isWidget: true } },
+ },
+ {
+ name: 'internal',
+ isInternal: true,
+ exports: { default: { path: 'ui.dxInternal', isWidget: true } },
+ },
+ ]);
+
+ expect(content).toContain("/// ");
+ expect(content).toContain('$().dxButton();');
+ expect(content).not.toContain('dxInternal');
+ });
+});
+
+const PLUGIN_TYPESCRIPT = path.dirname(
+ require.resolve('typescript/package.json', { paths: [__dirname] }),
+);
+
+describe('CheckDeclarationsExecutor E2E', () => {
+ let tempDir: string;
+ let context = createMockContext();
+ let projectDir: string;
+ let errorSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ tempDir = createTempDir('nx-check-declarations-e2e-');
+ context = createMockContext({ root: tempDir });
+ projectDir = path.join(tempDir, 'packages', 'test-lib');
+ fs.mkdirSync(path.join(projectDir, 'js'), { recursive: true });
+ fs.mkdirSync(path.join(projectDir, 'node_modules', '@types', 'jquery'), { recursive: true });
+ fs.writeFileSync(
+ path.join(projectDir, 'node_modules', '@types', 'jquery', 'index.d.ts'),
+ 'interface JQuery { empty(): JQuery; }\ninterface JQueryStatic { (selector: string): JQuery; }\ndeclare const $: JQueryStatic;\nexport = $;\n',
+ );
+ errorSpy = jest.spyOn(logger, 'error').mockImplementation(() => undefined);
+ });
+
+ afterEach(() => {
+ errorSpy.mockRestore();
+ cleanupTempDir(tempDir);
+ });
+
+ it('should pass modules mode for valid declaration files', async () => {
+ await writeFileText(
+ path.join(projectDir, 'js', 'sample.d.ts'),
+ 'declare namespace DevExpress { export interface Sample { value: number; } }\n',
+ );
+
+ const options: CheckDeclarationsExecutorSchema = {
+ mode: 'modules',
+ modulesPattern: './js/**/*.d.ts',
+ typescriptModule: PLUGIN_TYPESCRIPT,
+ compilerOptions: { types: [] },
+ };
+
+ const result = await executor(options, context);
+ expect(result.success).toBe(true);
+ });
+
+ it('should fail modules mode when declarations contain type errors', async () => {
+ await writeFileText(
+ path.join(projectDir, 'js', 'broken.d.ts'),
+ 'declare const broken: UnknownType;\n',
+ );
+
+ const options: CheckDeclarationsExecutorSchema = {
+ mode: 'modules',
+ modulesPattern: './js/**/*.d.ts',
+ typescriptModule: PLUGIN_TYPESCRIPT,
+ };
+
+ const result = await executor(options, context);
+ expect(result.success).toBe(false);
+ expect(errorSpy).toHaveBeenCalled();
+ });
+});
diff --git a/packages/nx-infra-plugin/src/executors/check-declarations/executor.ts b/packages/nx-infra-plugin/src/executors/check-declarations/executor.ts
new file mode 100644
index 000000000000..3703d073b5c3
--- /dev/null
+++ b/packages/nx-infra-plugin/src/executors/check-declarations/executor.ts
@@ -0,0 +1,13 @@
+export { default } from './check-declarations.impl';
+export {
+ buildDefaultCompilerOptions,
+ resolvePathFromProjectRoot,
+ resolveTypeScript,
+ runDeclarationsTypeCheck,
+ toTripleSlashReferencePath,
+} from './check-declarations.impl';
+export {
+ buildJqueryCheckContent,
+ buildPublicModulesCheckContent,
+ widgetNameByPath,
+} from './declaration-check-content';
diff --git a/packages/nx-infra-plugin/src/executors/check-declarations/schema.json b/packages/nx-infra-plugin/src/executors/check-declarations/schema.json
new file mode 100644
index 000000000000..50186dd10ff8
--- /dev/null
+++ b/packages/nx-infra-plugin/src/executors/check-declarations/schema.json
@@ -0,0 +1,50 @@
+{
+ "$schema": "https://json-schema.org/schema",
+ "title": "Check Declarations Executor Schema",
+ "description": "Type-check DevExtreme declaration files (gulp validate-ts parity)",
+ "type": "object",
+ "properties": {
+ "mode": {
+ "type": "string",
+ "enum": ["jquery", "bundle", "modules", "public-modules"],
+ "description": "Which declaration check to run."
+ },
+ "modulesMetadataFile": {
+ "type": "string",
+ "description": "Path to modules_metadata.json (relative to project root)."
+ },
+ "tsBundleFile": {
+ "type": "string",
+ "description": "Bundle .d.ts for jquery check (path relative to project root; embedded in /// relative to the generated entry file)."
+ },
+ "bundleArtifactPath": {
+ "type": "string",
+ "description": "Built bundle .d.ts for bundle check (relative to project root)."
+ },
+ "modulesPattern": {
+ "type": "string",
+ "description": "Glob for module .d.ts files (relative to project root)."
+ },
+ "entryOutputDir": {
+ "type": "string",
+ "description": "Directory for generated check entry files (relative to project root)."
+ },
+ "npmPackageDir": {
+ "type": "string",
+ "description": "Npm package folder name under artifacts/npm (e.g. devextreme)."
+ },
+ "internalPackage": {
+ "type": "boolean",
+ "description": "Use devextreme-internal package dir for public-modules imports."
+ },
+ "typescriptModule": {
+ "type": "string",
+ "description": "TypeScript package to load (e.g. typescript-min), resolved from project root."
+ },
+ "compilerOptions": {
+ "type": "object",
+ "description": "Extra compiler options merged into the default check profile."
+ }
+ },
+ "required": ["mode"]
+}
diff --git a/packages/nx-infra-plugin/src/executors/check-declarations/schema.ts b/packages/nx-infra-plugin/src/executors/check-declarations/schema.ts
new file mode 100644
index 000000000000..4eb8d22681de
--- /dev/null
+++ b/packages/nx-infra-plugin/src/executors/check-declarations/schema.ts
@@ -0,0 +1,14 @@
+export type CheckDeclarationsMode = 'jquery' | 'bundle' | 'modules' | 'public-modules';
+
+export interface CheckDeclarationsExecutorSchema {
+ mode: CheckDeclarationsMode;
+ modulesMetadataFile?: string;
+ tsBundleFile?: string;
+ bundleArtifactPath?: string;
+ modulesPattern?: string;
+ entryOutputDir?: string;
+ npmPackageDir?: string;
+ internalPackage?: boolean;
+ typescriptModule?: string;
+ compilerOptions?: Record;
+}
diff --git a/packages/nx-infra-plugin/src/executors/dts-modules/dts-modules.impl.ts b/packages/nx-infra-plugin/src/executors/dts-modules/dts-modules.impl.ts
index 0773815325e3..f401e3ce49d7 100644
--- a/packages/nx-infra-plugin/src/executors/dts-modules/dts-modules.impl.ts
+++ b/packages/nx-infra-plugin/src/executors/dts-modules/dts-modules.impl.ts
@@ -12,6 +12,9 @@ import type { PackageJson } from '../../utils/types';
import { DtsModulesExecutorSchema } from './schema';
const BUNDLES_PREFIX = 'bundles/';
+const DIST_PREFIX = 'dist/';
+const LICENSE_PREFIX = 'license/';
+const LICENSE_EXCLUDED_PREFIXES = [BUNDLES_PREFIX, DIST_PREFIX, LICENSE_PREFIX] as const;
const BACKSLASH_REGEX = /\\/g;
const FORWARD_SLASH = '/';
@@ -31,6 +34,10 @@ function toRelativePosix(baseDir: string, filePath: string): string {
return path.relative(baseDir, filePath).replace(BACKSLASH_REGEX, FORWARD_SLASH);
}
+function isExcludedFromModuleLicense(relativePath: string): boolean {
+ return LICENSE_EXCLUDED_PREFIXES.some((prefix) => relativePath.startsWith(prefix));
+}
+
export default createExecutor({
name: 'DtsModules',
resolve: async (options, { projectRoot }) => {
@@ -70,6 +77,10 @@ export default createExecutor({
path.resolve(resolved.outputDir, relative),
);
+ const dtsFilesForLicense = dtsFiles.filter(
+ (filePath) => !isExcludedFromModuleLicense(toRelativePosix(resolved.outputDir, filePath)),
+ );
+
const bannerInputs = {
pkg: resolved.pkg,
templatePath: resolved.templatePath,
@@ -80,7 +91,7 @@ export default createExecutor({
await Promise.all([
applyLicenseHeadersToFiles({
...bannerInputs,
- files: dtsFiles,
+ files: dtsFilesForLicense,
baseDir: resolved.outputDir,
filenameMode: 'relative',
}),