umberto 4.3.0 → 4.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,18 @@
1
1
  Changelog
2
2
  =========
3
3
 
4
+ ## [4.4.0-alpha.1](https://github.com/cksource/umberto/compare/v4.4.0-alpha.0...v4.4.0-alpha.1) (2024-09-24)
5
+
6
+ Internal changes only (updated dependencies, documentation, etc.).
7
+
8
+
9
+ ## [4.4.0-alpha.0](https://github.com/cksource/umberto/compare/v4.3.0...v4.4.0-alpha.0) (2024-09-20)
10
+
11
+ ### Features
12
+
13
+ * Added support for loading hooks (`beforeHexo` and `afterHexo`) and `scripts` in ESM format in Umberto. Closes [#1214](https://github.com/cksource/umberto/issues/1214). ([commit](https://github.com/cksource/umberto/commit/2849bef1e59108f863ea0a64314ab980a7f543b6))
14
+
15
+
4
16
  ## [4.3.0](https://github.com/cksource/umberto/compare/v4.2.6...v4.3.0) (2024-09-16)
5
17
 
6
18
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "umberto",
3
- "version": "4.3.0",
3
+ "version": "4.4.0-alpha.1",
4
4
  "description": "CKSource Documentation builder",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -17,7 +17,7 @@
17
17
  "@babel/core": "^7.18.10",
18
18
  "@babel/polyfill": "^7.12.1",
19
19
  "@babel/preset-env": "^7.18.10",
20
- "@ckeditor/jsdoc-plugins": "^32.0.1",
20
+ "@ckeditor/jsdoc-plugins": "^43.0.0",
21
21
  "babel-loader": "^8.2.5",
22
22
  "chalk": "^4.1.0",
23
23
  "cheerio": "^1.0.0",
@@ -25,7 +25,7 @@
25
25
  "del": "^6.1.1",
26
26
  "element-closest": "^3.0.2",
27
27
  "escape-string-regexp": "^4.0.0",
28
- "fs-extra": "^10.1.0",
28
+ "fs-extra": "^11.0.0",
29
29
  "fuse.js": "^6.6.2",
30
30
  "glob": "^7.2.3",
31
31
  "hexo": "^6.2.0",
@@ -54,7 +54,7 @@
54
54
  "tree-model": "^1.0.7",
55
55
  "upath": "^2.0.1",
56
56
  "vnu-jar": "^21.10.12",
57
- "webpack": "^5.74.0"
57
+ "webpack": "^5.94.0"
58
58
  },
59
59
  "engines": {
60
60
  "node": ">=18.0.0"
@@ -68,7 +68,7 @@
68
68
  "url": "https://github.com/cksource/umberto.git"
69
69
  },
70
70
  "lint-staged": {
71
- "**/*.js": [
71
+ "**/*.{js,mjs,cjs}": [
72
72
  "eslint --quiet"
73
73
  ]
74
74
  },
@@ -9,7 +9,7 @@ const upath = require( 'upath' );
9
9
  const fs = require( 'fs' );
10
10
 
11
11
  const EXEC_REGEXP = /\\?{@exec ([^}]+)\\?}/g;
12
- const PATH_REGEXP = /[\w.\\/-]+\.js$/;
12
+ const PATH_REGEXP = /[\w.\\/-]+\.c?js$/;
13
13
 
14
14
  /**
15
15
  * Replaces the `{@exec path/to/function.js}` expression with the module results.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @license Copyright (c) 2017-2024, CKSource Holding sp. z o.o. All rights reserved.
3
+ * For licensing, see LICENSE.md.
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const upath = require( 'upath' );
9
+ const { pathToFileURL } = require( 'url' );
10
+
11
+ /**
12
+ * Loads the specified module using `import()` call.
13
+ *
14
+ * @param {String} modulePath
15
+ * @returns {Promise.<Function>}
16
+ */
17
+ module.exports = async function importModule( modulePath ) {
18
+ const modulePathAsFileURL = getPathAsFileURL( modulePath );
19
+
20
+ return ( await import( modulePathAsFileURL ) ).default;
21
+ };
22
+
23
+ function getPathAsFileURL( modulePath ) {
24
+ const extension = upath.extname( modulePath );
25
+
26
+ if ( extension === '.js' || extension === '.mjs' || extension === '.cjs' ) {
27
+ return pathToFileURL( modulePath );
28
+ }
29
+
30
+ return pathToFileURL( modulePath + '.js' );
31
+ }
package/src/index.js CHANGED
@@ -47,25 +47,27 @@ module.exports = {
47
47
  * @returns {Promise}
48
48
  */
49
49
  buildSingleProject( options ) {
50
- const pConfig = getProjectConfig( process.cwd(), options );
51
50
  const timer = process.hrtime();
52
51
 
53
- return buildDocumentation( Object.assign(
54
- options,
55
- {
56
- mainConfig: {
57
- projects: [ '.' ],
58
- docsearch: pConfig.docsearch,
59
- googleoptimize: pConfig.googleoptimize,
60
- googletagmanager: pConfig.googletagmanager,
61
- googleanalytics: pConfig.googleanalytics,
62
- feedbackWidget: pConfig.feedbackWidget,
63
- isSingleProject: true,
64
- canonicalUrlBeginning: pConfig.canonicalUrlBeginning,
65
- vwo: pConfig.vwo
66
- }
67
- }
68
- ) )
52
+ return getProjectConfig( process.cwd(), options )
53
+ .then( pConfig => {
54
+ return buildDocumentation( Object.assign(
55
+ options,
56
+ {
57
+ mainConfig: {
58
+ projects: [ '.' ],
59
+ docsearch: pConfig.docsearch,
60
+ googleoptimize: pConfig.googleoptimize,
61
+ googletagmanager: pConfig.googletagmanager,
62
+ googleanalytics: pConfig.googleanalytics,
63
+ feedbackWidget: pConfig.feedbackWidget,
64
+ isSingleProject: true,
65
+ canonicalUrlBeginning: pConfig.canonicalUrlBeginning,
66
+ vwo: pConfig.vwo
67
+ }
68
+ }
69
+ ) );
70
+ } )
69
71
  .then( hexoManager => postBuild( options, hexoManager ) )
70
72
  .then( hexoManager => {
71
73
  const time = process.hrtime( timer );
@@ -127,7 +127,11 @@ module.exports = options => {
127
127
  variables: mainConfig.variables
128
128
  } );
129
129
  } )
130
- .then( () => executeHooks( getProjectConfigs( rootPath, projectPaths ), 'beforeHexo' ) )
130
+ .then( async () => {
131
+ const configs = await getProjectConfigs( rootPath, projectPaths );
132
+
133
+ return executeHooks( configs, 'beforeHexo' );
134
+ } )
131
135
  .then( () => {
132
136
  return buildProjects( rootPath, projectPaths, {
133
137
  skipApi,
@@ -155,12 +159,12 @@ module.exports = options => {
155
159
  .then( () => copyProjectIcons( hexoManager.hexo.projectGlobals, hexoManager.getPublicDir() ) )
156
160
  // A workaround for API guides when API generation is skipped.
157
161
  // Used for a local build (dev==true) to reuse existing API doc files.
158
- .then( () => {
162
+ .then( async () => {
159
163
  if ( !dev ) {
160
164
  return Promise.resolve();
161
165
  }
162
166
 
163
- const projectConfigs = getProjectConfigs( rootPath, projectPaths, {
167
+ const projectConfigs = await getProjectConfigs( rootPath, projectPaths, {
164
168
  skipLiveSnippets
165
169
  } );
166
170
  const buildDir = hexoManager.getPublicDir();
@@ -192,8 +196,8 @@ module.exports = options => {
192
196
  return Promise.resolve();
193
197
  } )
194
198
  // Links validation.
195
- .then( () => {
196
- const projectConfigs = getProjectConfigs( rootPath, projectPaths, {
199
+ .then( async () => {
200
+ const projectConfigs = await getProjectConfigs( rootPath, projectPaths, {
197
201
  skipLiveSnippets
198
202
  } );
199
203
 
@@ -219,10 +223,10 @@ module.exports = options => {
219
223
  } );
220
224
  } )
221
225
  // Create sitemap.xml file.
222
- .then( () => {
226
+ .then( async () => {
223
227
  let hostname;
224
228
  let dst = '';
225
- const projectConfigs = getProjectConfigs( rootPath, projectPaths );
229
+ const projectConfigs = await getProjectConfigs( rootPath, projectPaths );
226
230
  const config = mainConfig.isSingleProject && projectConfigs && projectConfigs.length > 0 ?
227
231
  projectConfigs[ 0 ] : {};
228
232
  const sitemapConfig = mainConfig.sitemap ? mainConfig.sitemap : config.sitemap;
@@ -275,7 +279,12 @@ module.exports = options => {
275
279
  extraUrlSettings
276
280
  } );
277
281
  } )
278
- .then( () => executeHooks( getProjectConfigs( rootPath, projectPaths ), 'afterHexo' ) )
282
+
283
+ .then( async () => {
284
+ const configs = await getProjectConfigs( rootPath, projectPaths );
285
+
286
+ return executeHooks( configs, 'afterHexo' );
287
+ } )
279
288
  .then( () => hexoManager );
280
289
  };
281
290
 
@@ -291,8 +300,8 @@ module.exports = options => {
291
300
  * @param {Boolean} options.skipGuides Whether to skip processing guides.
292
301
  * @return {Promise}
293
302
  */
294
- function buildProjects( rootPath, projectPaths, options = {} ) {
295
- const projectConfigs = getProjectConfigs( rootPath, projectPaths, options );
303
+ async function buildProjects( rootPath, projectPaths, options = {} ) {
304
+ const projectConfigs = await getProjectConfigs( rootPath, projectPaths, options );
296
305
  const promises = [];
297
306
 
298
307
  // If the `guides` (non-empty array) or `skipGuides` options are specified, disable the `{@link...}` validator.
@@ -416,19 +425,22 @@ function buildProjects( rootPath, projectPaths, options = {} ) {
416
425
  }
417
426
 
418
427
  // Gets every project's umberto.json config file.
419
- function getProjectConfigs( rootPath, projectPaths, options = {} ) {
420
- return projectPaths.map( pPath => {
428
+ async function getProjectConfigs( rootPath, projectPaths, options = {} ) {
429
+ const promises = projectPaths.map( async pPath => {
421
430
  const projectRootPath = upath.join( rootPath, pPath );
422
431
 
423
432
  return Object.assign(
424
- getProjectConfig( projectRootPath, {
425
- skipLiveSnippets: options.skipLiveSnippets
426
- } ),
433
+ {},
427
434
  {
435
+ ...await getProjectConfig( projectRootPath, {
436
+ skipLiveSnippets: options.skipLiveSnippets
437
+ } ),
428
438
  projectRootPath
429
439
  }
430
440
  );
431
441
  } );
442
+
443
+ return Promise.all( promises );
432
444
  }
433
445
 
434
446
  // Renders API docs HTML. API docs are rendered separate from hexo so hexo filters and helpers are not available.
@@ -11,19 +11,20 @@ const upath = require( 'upath' );
11
11
  const fs = require( 'fs' );
12
12
  const chalk = require( 'chalk' );
13
13
 
14
- module.exports = ( {
14
+ module.exports = async ( {
15
15
  rootPath,
16
16
  isSingleProject = false
17
17
  } ) => {
18
18
  const configs = [];
19
19
 
20
20
  if ( isSingleProject ) {
21
- configs.push( getProjectConfig( rootPath, { skipLiveSnippets: true } ) );
21
+ configs.push( await getProjectConfig( rootPath, { skipLiveSnippets: true } ) );
22
22
  } else {
23
23
  const projectPaths = getMainConfig( rootPath ).projects;
24
- projectPaths.forEach( projectPath => {
25
- configs.push( getProjectConfig( upath.join( rootPath, projectPath ), { skipLiveSnippets: true } ) );
24
+ const promises = projectPaths.map( projectPath => {
25
+ return getProjectConfig( upath.join( rootPath, projectPath ), { skipLiveSnippets: true } );
26
26
  } );
27
+ configs.push( ...await Promise.all( promises ) );
27
28
  }
28
29
 
29
30
  configs
@@ -32,6 +33,8 @@ module.exports = ( {
32
33
  createSymbolicLink( config );
33
34
  } );
34
35
 
36
+ return Promise.resolve();
37
+
35
38
  function createSymbolicLink( config ) {
36
39
  const destinationPath = upath.join( rootPath, 'build', 'docs', config.slug, 'latest' );
37
40
 
@@ -6,6 +6,7 @@
6
6
  'use strict';
7
7
 
8
8
  const upath = require( 'upath' );
9
+ const importModule = require( '../helpers/import-module' );
9
10
 
10
11
  /**
11
12
  * This function executes all hooks of the specified type for each config passed in the configs array.
@@ -22,42 +23,47 @@ const upath = require( 'upath' );
22
23
  * }
23
24
  *
24
25
  * @param {Array} configs Array of all the configurations that might contain hooks to execute.
25
- * @param {String} hookName Name of a hook to execute, either 'beforeHexo' or 'afterHexo'.
26
+ * @param {'beforeHexo'|'afterHexo'} hookName Name of a hook to execute.
26
27
  * @returns {Promise}
27
28
  */
28
- module.exports = function executeHooks( configs, hookName ) {
29
- let promise = Promise.resolve();
29
+ module.exports = async function executeHooks( configs, hookName ) {
30
+ try {
31
+ for ( const config of configs ) {
32
+ if ( !config.hooks || !config.hooks[ hookName ] ) {
33
+ continue;
34
+ }
30
35
 
31
- for ( const config of configs ) {
32
- if ( !config.hooks || !config.hooks[ hookName ] ) {
33
- continue;
36
+ for ( const scriptPath of config.hooks[ hookName ] ) {
37
+ await processSingleHook( upath.dirname( config.__configPath ), scriptPath );
38
+ }
34
39
  }
35
40
 
36
- for ( const scriptPath of config.hooks[ hookName ] ) {
37
- const callbackAbsolutePath = upath.join( upath.dirname( config.__configPath ), scriptPath );
38
-
39
- promise = promise
40
- .then( () => {
41
- // The `callback` may be synchronous function. Let's wrap it in the promise chain.
42
- return new Promise( resolve => {
43
- const callback = require( callbackAbsolutePath );
44
-
45
- return resolve( callback() );
46
- } ).catch( error => {
47
- // Store a path to the executed file.
48
- error._filePath = scriptPath;
49
-
50
- // Rejecting the promise allows catching it later.
51
- return Promise.reject( error );
52
- } );
53
- } );
54
- }
55
- }
56
-
57
- return promise.catch( error => {
41
+ return Promise.resolve();
42
+ } catch ( error ) {
58
43
  console.error( `The "${ hookName }" hook pointing to the "${ error._filePath }" file ended with an error.`, error.message );
59
44
 
60
45
  // Re-throwing the error as we want to break the build process.
61
46
  throw error;
62
- } );
47
+ }
63
48
  };
49
+
50
+ /**
51
+ * @param {String} configPath
52
+ * @param {String} scriptPath
53
+ * @return {Promise}
54
+ */
55
+ async function processSingleHook( configPath, scriptPath ) {
56
+ const callbackAbsolutePath = upath.join( configPath, scriptPath );
57
+
58
+ try {
59
+ const callback = await importModule( callbackAbsolutePath );
60
+
61
+ await callback();
62
+ } catch ( error ) {
63
+ // Store a path to the executed file.
64
+ error._filePath = scriptPath;
65
+
66
+ // Rejecting the promise allows catching it later.
67
+ return Promise.reject( error );
68
+ }
69
+ }
@@ -8,6 +8,7 @@
8
8
  const upath = require( 'upath' );
9
9
  const fs = require( 'fs' );
10
10
  const glob = require( 'glob' );
11
+ const importModule = require( '../helpers/import-module' );
11
12
 
12
13
  const cache = new Map();
13
14
 
@@ -17,9 +18,9 @@ const cache = new Map();
17
18
  * @param {String} rootPath
18
19
  * @param {Object} [options={}]
19
20
  * @param {Boolean} [options.skipLiveSnippets=false] If set to `true`, the `snippetAdapter` module will not be added to the configuration.
20
- * @returns {Object}
21
+ * @returns {Promise}
21
22
  */
22
- module.exports = ( rootPath, options = {} ) => {
23
+ module.exports = async ( rootPath, options = {} ) => {
23
24
  // Let's normalize the root path to fix mixed slashes and backslashes. Root path should contain only slashes.
24
25
  rootPath = rootPath.split( /[\\/]/g ).join( '/' );
25
26
 
@@ -35,7 +36,7 @@ module.exports = ( rootPath, options = {} ) => {
35
36
 
36
37
  // Get the `realImportPath()` function. It displays an import path of a class below the class title.
37
38
  // See: https://ckeditor.com/docs/ckeditor5/latest/api/module_editor-classic_classiceditor-ClassicEditor.html.
38
- loadScriptToConfig( config, {
39
+ await loadScriptToConfig( config, {
39
40
  scriptKey: 'import-path',
40
41
  configKey: 'getRealImportPath',
41
42
  configPath,
@@ -47,7 +48,7 @@ module.exports = ( rootPath, options = {} ) => {
47
48
  } );
48
49
 
49
50
  // Load the `insertChangelog()` script that allows inserting a changelog to a guide.
50
- loadScriptToConfig( config, {
51
+ await loadScriptToConfig( config, {
51
52
  scriptKey: 'insert-changelog',
52
53
  configKey: 'insertChangelog',
53
54
  configPath,
@@ -58,7 +59,7 @@ module.exports = ( rootPath, options = {} ) => {
58
59
 
59
60
  // Load the snippet adapter only if snippets will be built.
60
61
  if ( !options.skipLiveSnippets ) {
61
- loadScriptToConfig( config, {
62
+ await loadScriptToConfig( config, {
62
63
  scriptKey: 'snippet-adapter',
63
64
  configKey: 'snippetAdapter',
64
65
  configPath,
@@ -146,7 +147,7 @@ function getConfigurationFile( configPath ) {
146
147
  * @param {String} options.configPath An absolute path to the configuration file. It is used to obtain a path of the function to load.
147
148
  * @param {Function} options.onError A callback will be executed if the function could not be load.
148
149
  */
149
- function loadScriptToConfig( config, options ) {
150
+ async function loadScriptToConfig( config, options ) {
150
151
  if ( !config.scripts ) {
151
152
  return;
152
153
  }
@@ -160,13 +161,13 @@ function loadScriptToConfig( config, options ) {
160
161
 
161
162
  try {
162
163
  // Tries to load the function from the current working directory.
163
- config[ configKey ] = require( config.scripts[ scriptKey ] );
164
+ config[ configKey ] = await importModule( config.scripts[ scriptKey ] );
164
165
  } catch ( err ) {
165
166
  // If failed, let's resolve the path from the directory where the configuration file is located.
166
167
  const fnPath = upath.join( upath.dirname( options.configPath ), config.scripts[ scriptKey ] );
167
168
 
168
169
  try {
169
- config[ configKey ] = require( fnPath );
170
+ config[ configKey ] = await importModule( fnPath );
170
171
  } catch ( nestedErr ) {
171
172
  // If still fails, let's call the callback and throw an error.
172
173
  options.onError( err );