mirror of
https://github.com/facebook/lexical.git
synced 2025-05-17 06:59:17 +08:00
213 lines
6.0 KiB
JavaScript
213 lines
6.0 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
*/
|
|
// @ts-check
|
|
'use strict';
|
|
|
|
const path = require('node:path');
|
|
const fs = require('fs-extra');
|
|
const npmToWwwName = require('../www/npmToWwwName');
|
|
|
|
/**
|
|
* @typedef {Object} ModuleBuildDefinition
|
|
* @property {string} outputFileName
|
|
* @property {string} sourceFileName
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} PackageBuildDefinition
|
|
* @property {Array<ModuleBuildDefinition>} modules
|
|
* @property {string} name
|
|
* @property {string} outputPath
|
|
* @property {string} packageName
|
|
* @property {string} sourcePath
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Object} ModuleExportEntry
|
|
* @property {string} name
|
|
* @property {string} sourceFileName
|
|
*/
|
|
|
|
/**
|
|
* @typedef {Record<'types' | 'development' | 'production' | 'node' | 'default', string>} ImportCondition
|
|
* @typedef {Record<'types' | 'development' | 'production' | 'default', string>} RequireCondition
|
|
* @typedef {readonly [string, { import: ImportCondition; require: RequireCondition }]} NpmModuleExportEntry
|
|
*/
|
|
|
|
/**
|
|
*
|
|
* @param {string} wwwName
|
|
* @returns {string} An easier to read name ('Lexical' -> 'Lexical Core', 'LexicalRichText' -> 'Lexical Rich Text')
|
|
*/
|
|
function readableName(wwwName) {
|
|
return wwwName === 'Lexical'
|
|
? 'Lexical Core'
|
|
: wwwName.replace(/([A-Z])/g, ' $1').trim();
|
|
}
|
|
|
|
/**
|
|
* Metadata abstraction for a package.json file
|
|
*/
|
|
class PackageMetadata {
|
|
/** @type {string} the path to the package.json file */
|
|
packageJsonPath;
|
|
/** @type {Record<string, any>} the parsed package.json */
|
|
packageJson;
|
|
|
|
/**
|
|
* @param {string} packageJsonPath the path to the package.json file
|
|
*/
|
|
constructor(packageJsonPath) {
|
|
this.packageJsonPath = packageJsonPath;
|
|
this.packageJson = fs.readJsonSync(packageJsonPath);
|
|
}
|
|
|
|
/**
|
|
* @param {...string} paths to resolve in this package's directory
|
|
* @returns {string} Resolve a path in this package's directory
|
|
*/
|
|
resolve(...paths) {
|
|
return path.resolve(path.dirname(this.packageJsonPath), ...paths);
|
|
}
|
|
|
|
/**
|
|
* @returns {string} the directory name of the package, e.g. 'lexical-rich-text'
|
|
*/
|
|
getDirectoryName() {
|
|
return path.basename(path.dirname(this.packageJsonPath));
|
|
}
|
|
|
|
/**
|
|
* @returns {string} the npm name of the package, e.g. '@lexical/rich-text'
|
|
*/
|
|
getNpmName() {
|
|
return this.packageJson.name;
|
|
}
|
|
|
|
/**
|
|
* @returns {boolean} whether the package is marked private (not published to npm)
|
|
*/
|
|
isPrivate() {
|
|
return !!this.packageJson.private;
|
|
}
|
|
|
|
/**
|
|
* Get an array of (fully qualified) exported module names and their
|
|
* corresponding export map. Ignores the backwards compatibility '.js'
|
|
* exports and replaces /^.[/]?/ with the npm name of the package.
|
|
*
|
|
* E.g. [['lexical', {...}]] or [['@lexical/react/LexicalComposer', {...}]
|
|
*
|
|
* @returns {Array<NpmModuleExportEntry>}
|
|
*/
|
|
getNormalizedNpmModuleExportEntries() {
|
|
// It doesn't make much sense to do this for private modules
|
|
if (this.isPrivate()) {
|
|
throw new Error('This should only be called on public packages');
|
|
}
|
|
// All our packages should have exports if update-version has been run
|
|
if (!this.packageJson.exports) {
|
|
throw new Error(
|
|
'This package should have exports, try `npm run update-version` first',
|
|
);
|
|
}
|
|
/** @type {Array<NpmModuleExportEntry>} */
|
|
const entries = [];
|
|
for (const [key, value] of Object.entries(this.packageJson.exports)) {
|
|
if (key.endsWith('.js')) {
|
|
continue;
|
|
}
|
|
entries.push([`${this.getNpmName()}${key.replace(/^./, '')}`, value]);
|
|
}
|
|
return entries.sort((a, b) => a[0].localeCompare(b[0]));
|
|
}
|
|
|
|
/**
|
|
* @returns {Array<string>} the npm module names that this package exports
|
|
*/
|
|
getExportedNpmModuleNames() {
|
|
return this.getNormalizedNpmModuleExportEntries().map(([name]) => name);
|
|
}
|
|
|
|
/**
|
|
* The entries of npm module names to their .tsx? source files
|
|
*
|
|
* @returns {Array<ModuleExportEntry>}
|
|
*/
|
|
getExportedNpmModuleEntries() {
|
|
const npmName = this.getNpmName();
|
|
return this.getExportedNpmModuleNames().map((name) => {
|
|
const outputFileName = npmToWwwName(name);
|
|
const sourceBaseName = name === npmName ? 'index' : outputFileName;
|
|
const sourceCandidates = ['.ts', '.tsx'].map(
|
|
(ext) => sourceBaseName + ext,
|
|
);
|
|
const sourceFileName = sourceCandidates.find((fn) =>
|
|
fs.existsSync(this.resolve('src', fn)),
|
|
);
|
|
if (!sourceFileName) {
|
|
throw new Error(
|
|
`Could not find source file for ${name} at packages/${this.getDirectoryName()}/src/${
|
|
sourceCandidates[0]
|
|
}?`,
|
|
);
|
|
}
|
|
return {name, sourceFileName};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The map of import module names to their .tsx? source files
|
|
* (for private modules such as shared)
|
|
*
|
|
* @returns {Array<ModuleExportEntry>}
|
|
*/
|
|
getPrivateModuleEntries() {
|
|
const npmName = this.getNpmName();
|
|
const entries = [];
|
|
for (const sourceFileName of fs.readdirSync(this.resolve('src'))) {
|
|
const m = /^([^.]+)\.tsx?$/.exec(sourceFileName);
|
|
if (m) {
|
|
entries.push({
|
|
name: m[1] === 'index' ? npmName : `${npmName}/${m[1]}`,
|
|
sourceFileName,
|
|
});
|
|
}
|
|
}
|
|
return entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
}
|
|
|
|
/**
|
|
* @returns {PackageBuildDefinition}
|
|
*/
|
|
getPackageBuildDefinition() {
|
|
const npmName = this.getNpmName();
|
|
return {
|
|
modules: this.getExportedNpmModuleEntries().map(
|
|
({name, sourceFileName}) => ({
|
|
outputFileName: npmToWwwName(name),
|
|
sourceFileName,
|
|
}),
|
|
),
|
|
name: readableName(npmToWwwName(npmName)),
|
|
outputPath: this.resolve('dist/'),
|
|
packageName: this.getDirectoryName(),
|
|
sourcePath: this.resolve('src/'),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Writes this.packageJson back to this.packageJsonPath
|
|
*/
|
|
writeSync() {
|
|
fs.writeJsonSync(this.packageJsonPath, this.packageJson, {spaces: 2});
|
|
}
|
|
}
|
|
|
|
exports.PackageMetadata = PackageMetadata;
|