Skip to content

Commit

Permalink
Merge pull request #16288 from ckeditor/add-exports-validator
Browse files Browse the repository at this point in the history
Internal: Added a new script to verify that all packages are exported in the `ckeditor5` package.
  • Loading branch information
filipsobol committed Apr 26, 2024
2 parents 5088d61 + 546b1e5 commit e05a850
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 4 deletions.
4 changes: 4 additions & 0 deletions .circleci/template.yml
Expand Up @@ -132,6 +132,10 @@ jobs:
when: always
name: Check if all CSS files from the "ckeditor5-theme-lark" are imported in "index.css" file
command: yarn run check-theme-lark-imports
- run:
when: always
name: Check if all packages are exported in the "ckeditor5" package
command: yarn run check-exports

cke5_coverage:
machine: true
Expand Down
5 changes: 4 additions & 1 deletion package.json
Expand Up @@ -121,6 +121,7 @@
"@ocular-d/vale-bin": "^2.29.1",
"@webspellchecker/wproofreader-ckeditor5": "^2.0.1",
"@wiris/mathtype-ckeditor5": "^7.24.0",
"acorn": "^8.11.3",
"assert": "^2.0.0",
"babel-standalone": "^6.26.0",
"chalk": "^4.1.0",
Expand All @@ -129,6 +130,7 @@
"date-fns": "^2.30.0",
"eslint": "^7.19.0",
"eslint-config-ckeditor5": "^5.3.0",
"estree-walker": "^3.0.3",
"fs-extra": "^11.1.1",
"glob": "^10.2.5",
"http-server": "^14.1.1",
Expand Down Expand Up @@ -207,7 +209,8 @@
"collect-svg-icons": "node scripts/collect-svg-icons.js",
"check-dependencies": "ckeditor5-dev-dependency-checker",
"check-dependencies:versions-match": "node ./scripts/ci/check-dependencies-versions-match.js",
"check-theme-lark-imports": "node ./scripts/check-theme-lark-imports.js"
"check-theme-lark-imports": "node ./scripts/check-theme-lark-imports.js",
"check-exports": "node ./scripts/check-exports.mjs --input=ckeditor5"
},
"lint-staged": {
"**/*.{js,ts,mjs}": [
Expand Down
151 changes: 151 additions & 0 deletions scripts/check-exports.mjs
@@ -0,0 +1,151 @@
/**
* @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved.
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/

/* eslint-env node */

import fs from 'fs';
import { parseArgs } from 'util';
import { createRequire } from 'module';
import chalk from 'chalk';
import upath from 'upath';
import { parse } from 'acorn';
import { walk } from 'estree-walker';
import { globSync } from 'glob';
import constants from './release/utils/constants.js';

const require = createRequire( import.meta.url );

/**
* List of paths to the allowed `input` packages.
*/
const paths = {
'ckeditor5': constants.CKEDITOR5_INDEX,
'ckeditor5-premium-features': constants.CKEDITOR5_PREMIUM_FEATURES_INDEX
};

/**
* List of packages that are not meant to be exported in the main entry files.
*/
const exceptions = [
...Object.keys( paths ),

// Core packages.
'@ckeditor/ckeditor5-theme-lark',
'@ckeditor/ckeditor5-build-multi-root',
'@ckeditor/ckeditor5-build-inline',
'@ckeditor/ckeditor5-build-decoupled-document',
'@ckeditor/ckeditor5-build-classic',
'@ckeditor/ckeditor5-build-balloon-block',
'@ckeditor/ckeditor5-build-balloon',

// Commercial packages.
'@ckeditor/ckeditor5-operations-compressor',
'ckeditor5-collaboration'
];

/**
* Parse CLI arguments.
*/
const { values } = parseArgs( {
options: {
/*
* Name of the package to check. Valid options:
* - `ckeditor5`.
* - `ckeditor5-premium-features`.
*/
'input': { type: 'string' }
},

// Skip `node ./scripts/check-exports.mjs`.
args: process.argv.slice( 2 ),

// Fail when unknown argument is used.
strict: true
} );

/**
* Resolve paths based on the `input` value.
*/
const inputPath = paths[ values.input ];

if ( !inputPath ) {
throw new Error( `Invalid input value: ${ values.input }` );
}

/**
* Get names of all packages in the `packages` directory.
*/
const globPath = upath.join( process.cwd(), constants.PACKAGES_DIRECTORY, '*', 'package.json' );

const packages = globSync( globPath )
.map( path => require( path ) )
.map( pkg => pkg.name );

/**
* Parse the main entry file.
*/
const ast = parse( fs.readFileSync( inputPath, 'utf8' ), {
sourceType: 'module',
ecmaVersion: 'latest'
} );

const exports = [];

/**
* Walk through the AST and collect names of all exported packages.
*/
walk( ast, {
enter( node ) {
if ( node.type !== 'ExportAllDeclaration' && node.type !== 'ExportNamedDeclaration' ) {
return;
}

const path = node.source.value;

if ( path.startsWith( '.' ) || path.startsWith( '/' ) ) {
return;
}

const packageName = path
.split( '/' )
.slice( 0, path.startsWith( '@' ) ? 2 : 1 )
.join( '/' );

exports.push( packageName );
}
} );

/**
* Names of the packages that are not exported in the main entry file.
*/
const missingExports = packages.filter( name => !exports.includes( name ) && !exceptions.includes( name ) );

/**
* Names of the packages that are not present in the packages directory, but are exported in the main entry file.
*/
const missingPackages = exports.filter( name => !packages.includes( name ) );

if ( !missingExports.length && !missingPackages.length ) {
console.log( chalk.green( 'All packages are exported in the main entry file.' ) );
process.exit( 0 );
}

if ( missingExports.length ) {
console.log(
chalk.red.bold( `The following packages are not exported from the "${ values.input }" package:` )
);

missingExports.forEach( pkg => console.log( chalk.red( ` - ${ pkg }` ) ) );
}

if ( missingPackages.length ) {
console.log(
chalk.red.bold( `The following exports in the "${ values.input }" package are not present in the "packages" directory:` )
);

missingPackages.forEach( pkg => console.log( chalk.red( ` - ${ pkg }` ) ) );
}

process.exit( 1 );
21 changes: 18 additions & 3 deletions scripts/release/utils/constants.js
Expand Up @@ -8,11 +8,26 @@
'use strict';

const upath = require( 'upath' );

const PACKAGES_DIRECTORY = 'packages';

const RELEASE_DIRECTORY = 'release';

const CKEDITOR5_ROOT_PATH = upath.join( __dirname, '..', '..', '..' );

const CKEDITOR5_COMMERCIAL_PATH = upath.resolve( CKEDITOR5_ROOT_PATH, 'external', 'ckeditor5-commercial' );

const CKEDITOR5_INDEX = upath.join( CKEDITOR5_ROOT_PATH, 'src', 'index.ts' );

const CKEDITOR5_PREMIUM_FEATURES_INDEX = upath.join(
CKEDITOR5_COMMERCIAL_PATH, PACKAGES_DIRECTORY, 'ckeditor5-premium-features', 'src', 'index.ts'
);

module.exports = {
PACKAGES_DIRECTORY: 'packages',
RELEASE_DIRECTORY: 'release',
PACKAGES_DIRECTORY,
RELEASE_DIRECTORY,
CKEDITOR5_ROOT_PATH,
CKEDITOR5_COMMERCIAL_PATH: upath.resolve( CKEDITOR5_ROOT_PATH, 'external', 'ckeditor5-commercial' )
CKEDITOR5_COMMERCIAL_PATH,
CKEDITOR5_INDEX,
CKEDITOR5_PREMIUM_FEATURES_INDEX
};

0 comments on commit e05a850

Please sign in to comment.