Skip to content

Commit

Permalink
fix(build): move error-codes generation to transform-error-messages b…
Browse files Browse the repository at this point in the history
…abel plugin and always run with build-release
  • Loading branch information
etrepum committed May 2, 2024
1 parent a346764 commit b5338b9
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 136 deletions.
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@
"build": "node scripts/build.js",
"build-prod": "npm run clean && npm run build -- --prod",
"build-playground-prod": "npm run build-prod && npm run build-prod --prefix packages/lexical-playground",
"build-release": "npm run build-prod -- --release",
"build-release": "npm run build-prod -- --release --codes",
"build-www": "npm run clean && npm run build -- --www && npm run build -- --www --prod && npm run prepare-www",
"build-types": "tsc -p ./tsconfig.build.json && node ./scripts/validate-tsc-types.js",
"clean": "node scripts/clean.js",
"extract-codes": "node scripts/build.js --codes",
"flow": "node ./scripts/check-flow-types.js",
"tsc": "tsc",
"tsc-extension": "npm run compile -w @lexical/devtools",
Expand Down
11 changes: 6 additions & 5 deletions packages/lexical-website/docs/maintainers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ This runs all of the pre-release steps and will let you inspect the artifacts
that would be uploaded to npm. Each public package will have a npm directory, e.g.
`packages/lexical/npm` that contains those artifacts.

This will also update scripts/error-codes/codes.json, the mapping of
production error codes to error messages. It's imperative to commit the result
of this before tagging a release.

### npm run ci-check

Check flow, TypeScript, prettier and eslint for issues. A good command to run
Expand Down Expand Up @@ -227,11 +231,8 @@ Run eslint
### npm run increment-version

Increment the monorepo version. Make sure to run `npm run update-packages`
after this.

### npm run extract-codes

Extract error codes for the production build. Essential to run before a release.
(to update all examples and sub-packages) and `npm run prepare-release`
(to update `scripts/error-codes/codes.json`) after this.

### npm run changelog

Expand Down
17 changes: 1 addition & 16 deletions scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const nodeResolve = require('@rollup/plugin-node-resolve').default;
const commonjs = require('@rollup/plugin-commonjs');
const replace = require('@rollup/plugin-replace');
const json = require('@rollup/plugin-json');
const extractErrorCodes = require('./error-codes/extract-errors');
const alias = require('@rollup/plugin-alias');
const compiler = require('@ampproject/rollup-plugin-closure-compiler');
const terser = require('@rollup/plugin-terser');
Expand Down Expand Up @@ -103,12 +102,6 @@ const externals = [
'y-websocket',
].sort();

const errorCodeOpts = {
errorMapFilePath: 'scripts/error-codes/codes.json',
};

const findAndRecordErrorCodes = extractErrorCodes(errorCodeOpts);

const strictWWWMappings = {};

// Add quotes around mappings to make them more strict.
Expand Down Expand Up @@ -175,14 +168,6 @@ async function build(name, inputFile, outputPath, outputFile, isProd, format) {
{find: 'shared', replacement: path.resolve('packages/shared/src')},
],
}),
// Extract error codes from invariant() messages into a file.
{
transform(source) {
// eslint-disable-next-line no-unused-expressions
extractCodes && findAndRecordErrorCodes(source);
return source;
},
},
nodeResolve({
extensions,
}),
Expand All @@ -195,7 +180,7 @@ async function build(name, inputFile, outputPath, outputFile, isProd, format) {
plugins: [
[
require('./error-codes/transform-error-messages'),
{noMinify: !isProd},
{extractCodes, noMinify: !isProd},
],
'@babel/plugin-transform-optional-catch-binding',
],
Expand Down
96 changes: 96 additions & 0 deletions scripts/error-codes/ErrorMap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* 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.
*
*/
'use strict';
// @ts-check

/**
* Data structure to manage reading from and writing to codes.json
*/
class ErrorMap {
/**
* The map of error code numbers (as String(number)) to the error messages
*
* @type {Record<string, string>}
*/
errorMap;
/**
* The map of error messages to the error code numbers (as integers)
*
* @type {Record<string, number}
*/
inverseErrorMap = {};
/**
* The largest known error code presently in the errorMap
* @type {number}
*/
maxId = -1;
/**
* true if the errorMap has been updated but not yet flushed
*
* @type {boolean}
*/
dirty = false;

/**
* @param {Record<string, string>} errorMap typically the result of `JSON.parse(fs.readFileSync('codes.json', 'utf8'))`
* @param {(errorMap: Record<string, string>) => void} flushErrorMap the callback to persist the errorMap back to disk
*/
constructor(errorMap, flushErrorMap) {
this.errorMap = errorMap;
this.flushErrorMap = flushErrorMap;
for (const k in this.errorMap) {
const id = parseInt(k, 10);
this.inverseErrorMap[this.errorMap[k]] = id;
this.maxId = id > this.maxId ? id : this.maxId;
}
}

/**
* Fetch the error code for a given error message. If the error message is
* present in the errorMap, it will return the corresponding numeric code.
*
* If the message is not present, and extractCodes is not true, it will
* return false.
*
* Otherwise, it will generate a new error code and queue a microtask to
* flush it back to disk (so multiple updates can be batched together).
*
* @param {string} message the error message
* @param {boolean} extractCodes true if we are also writing to codes.json
* @returns {number | undefined}
*/
getOrAddToErrorMap(message, extractCodes) {
let id = this.inverseErrorMap[message];
if (extractCodes && typeof id === 'undefined') {
id = ++this.maxId;
this.inverseErrorMap[message] = id;
this.errorMap[`${id}`] = message;
if (!this.dirty) {
queueMicrotask(this.flush.bind(this));
this.dirty = true;
}
}
return id;
}

/**
* If dirty is true, this will call flushErrorMap with the current errorMap
* and reset dirty to false.
*
* Normally this is automatically queued to a microtask as necessary, but
* it may be called manually in test scenarios.
*/
flush() {
if (this.dirty) {
this.flushErrorMap(this.errorMap);
this.dirty = false;
}
}
}

module.exports = ErrorMap;
105 changes: 105 additions & 0 deletions scripts/error-codes/__tests__/unit/ErrorMap.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/**
* 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 ErrorMap = require('../../ErrorMap');

/** @returns {Promise<void>} */
function waitTick() {
return new Promise((resolve) => queueMicrotask(resolve));
}

describe('ErrorMap', () => {
[
{initialMessages: []},
{
initialMessages: ['known message', 'another known message'],
},
].forEach(({name, initialMessages}) => {
const initialMap = Object.fromEntries(
initialMessages.map((message, i) => [`${i}`, message]),
);
describe(`with ${initialMessages.length} message(s)`, () => {
test('does not insert unless extractCodes is true', async () => {
const flush = jest.fn();
const errorMap = new ErrorMap(initialMap, flush);
expect(errorMap.getOrAddToErrorMap('unknown message', false)).toBe(
undefined,
);
await waitTick();
expect(flush).not.toBeCalled();
expect(Object.keys(errorMap.errorMap).length).toEqual(
initialMessages.length,
);
});
if (initialMessages.length > 0) {
test('looks up existing messages', async () => {
const flush = jest.fn();
const errorMap = new ErrorMap(initialMap, flush);
initialMessages.forEach((msg, i) => {
expect(errorMap.getOrAddToErrorMap(msg, false)).toBe(i);
});
expect(errorMap.dirty).toBe(false);
initialMessages.forEach((msg, i) => {
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(i);
});
expect(errorMap.dirty).toBe(false);
await waitTick();
expect(flush).not.toBeCalled();
});
}
test('inserts with extractCodes true', async () => {
const flush = jest.fn();
const errorMap = new ErrorMap(initialMap, flush);
const msg = 'unknown message';
const beforeSize = initialMessages.length;
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(beforeSize);
expect(Object.keys(errorMap.errorMap).length).toEqual(1 + beforeSize);
expect(Object.keys(errorMap.inverseErrorMap).length).toEqual(
1 + beforeSize,
);
expect(errorMap.errorMap[beforeSize]).toBe(msg);
expect(errorMap.inverseErrorMap[msg]).toBe(beforeSize);
expect(errorMap.maxId).toBe(beforeSize);
expect(flush).not.toBeCalled();
expect(errorMap.dirty).toBe(true);
await waitTick();
expect(errorMap.dirty).toBe(false);
expect(flush).toBeCalledWith({...initialMap, [`${beforeSize}`]: msg});
});
test('inserts two messages with extractCodes true', async () => {
const flush = jest.fn();
const errorMap = new ErrorMap(initialMap, flush);
const msgs = ['unknown message', 'another unknown message'];
msgs.forEach((msg, i) => {
const beforeSize = i + initialMessages.length;
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(beforeSize);
expect(Object.keys(errorMap.errorMap).length).toEqual(1 + beforeSize);
expect(Object.keys(errorMap.inverseErrorMap).length).toEqual(
1 + beforeSize,
);
expect(errorMap.errorMap[beforeSize]).toBe(msg);
expect(errorMap.inverseErrorMap[msg]).toBe(beforeSize);
expect(errorMap.maxId).toBe(beforeSize);
expect(flush).not.toBeCalled();
});
expect(errorMap.dirty).toBe(true);
await waitTick();
expect(errorMap.dirty).toBe(false);
expect(flush).toBeCalledTimes(1);
expect(flush).toBeCalledWith({
...initialMap,
...Object.fromEntries(
msgs.map((msg, i) => [`${initialMessages.length + i}`, msg]),
),
});
});
});
});
});
104 changes: 0 additions & 104 deletions scripts/error-codes/extract-errors.js

This file was deleted.

Loading

0 comments on commit b5338b9

Please sign in to comment.