const deepFreeze = require('deep-freeze-strict'); const { loadBuildTypesConfig } = require('../../lib/build-type'); const { createRemoveFencedCodeTransform, removeFencedCode, } = require('./remove-fenced-code'); const transformUtils = require('./utils'); jest.mock('./utils', () => ({ lintTransformedFile: jest.fn(), })); // The test data is just strings. We get it from a function at the end of this // file because it takes up a lot of lines and is very distracting. const testData = getTestData(); const MAIN_BUILD = 'build-main'; const FLASK_BUILD = 'build-flask'; const getMinimalFencedCode = (params = FLASK_BUILD) => `///: BEGIN:ONLY_INCLUDE_IN(${params}) Conditionally_Included ///: END:ONLY_INCLUDE_IN `; const getFeatures = ({ all, active }) => ({ all: new Set(all), active: new Set(active), }); const buildTypesConfig = loadBuildTypesConfig(); describe('build/transforms/remove-fenced-code', () => { describe('createRemoveFencedCodeTransform', () => { const { lintTransformedFile: lintTransformedFileMock } = transformUtils; const mockJsFileName = 'file.js'; beforeEach(() => { lintTransformedFileMock.mockImplementation(() => Promise.resolve()); }); it('returns a PassThrough stream for files with ignored extensions', async () => { const fileContent = '"Valid JSON content"\n'; const stream = createRemoveFencedCodeTransform( getFeatures({ active: [MAIN_BUILD], all: [MAIN_BUILD], }), )('file.json'); let streamOutput = ''; await new Promise((resolve) => { stream.on('data', (data) => { streamOutput = streamOutput.concat(data.toString('utf8')); }); stream.on('end', () => { expect(streamOutput).toStrictEqual(fileContent); expect(lintTransformedFileMock).not.toHaveBeenCalled(); resolve(); }); stream.write(Buffer.from(fileContent)); setTimeout(() => stream.end()); }); }); it('transforms a file read as a single chunk', async () => { const filePrefix = '// A comment\n'; const fileContent = filePrefix.concat(getMinimalFencedCode()); const stream = createRemoveFencedCodeTransform( getFeatures({ active: [MAIN_BUILD], all: [MAIN_BUILD, FLASK_BUILD], }), )(mockJsFileName); let streamOutput = ''; await new Promise((resolve) => { stream.on('data', (data) => { streamOutput = streamOutput.concat(data.toString('utf8')); }); stream.on('end', () => { expect(streamOutput).toStrictEqual(filePrefix); expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); expect(lintTransformedFileMock).toHaveBeenCalledWith( filePrefix, mockJsFileName, ); resolve(); }); stream.end(fileContent); }); }); it('transforms a file read as multiple chunks', async () => { const filePrefix = '// A comment\n'; const chunks = filePrefix .concat(getMinimalFencedCode()) .split('\n') // The final element in the split array is the empty string, which is // useful for calling .join, but undesirable here. .filter((line) => line !== '') .map((line) => `${line}\n`); const stream = createRemoveFencedCodeTransform( getFeatures({ active: [MAIN_BUILD], all: [MAIN_BUILD, FLASK_BUILD], }), )(mockJsFileName); let streamOutput = ''; await new Promise((resolve) => { stream.on('data', (data) => { streamOutput = streamOutput.concat(data.toString('utf8')); }); stream.on('end', () => { expect(streamOutput).toStrictEqual(filePrefix); expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); expect(lintTransformedFileMock).toHaveBeenCalledWith( filePrefix, mockJsFileName, ); resolve(); }); chunks.forEach((chunk) => stream.write(chunk)); setTimeout(() => stream.end()); }); }); it('handles file with fences that is unmodified by the transform', async () => { const fileContent = getMinimalFencedCode(MAIN_BUILD); const stream = createRemoveFencedCodeTransform( getFeatures({ active: [MAIN_BUILD], all: [MAIN_BUILD], }), )(mockJsFileName); let streamOutput = ''; await new Promise((resolve) => { stream.on('data', (data) => { streamOutput = streamOutput.concat(data.toString('utf8')); }); stream.on('end', () => { expect(streamOutput).toStrictEqual(fileContent); expect(lintTransformedFileMock).not.toHaveBeenCalled(); resolve(); }); stream.end(fileContent); }); }); it('skips linting for transformed file if shouldLintTransformedFiles is false', async () => { const filePrefix = '// A comment\n'; const fileContent = filePrefix.concat(getMinimalFencedCode()); const stream = createRemoveFencedCodeTransform( getFeatures({ all: [MAIN_BUILD, FLASK_BUILD], active: [MAIN_BUILD] }), false, )(mockJsFileName); let streamOutput = ''; await new Promise((resolve) => { stream.on('data', (data) => { streamOutput = streamOutput.concat(data.toString('utf8')); }); stream.on('end', () => { expect(streamOutput).toStrictEqual(filePrefix); expect(lintTransformedFileMock).not.toHaveBeenCalled(); resolve(); }); stream.end(fileContent); }); }); it('handles error during code fence removal or parsing', async () => { const fileContent = getMinimalFencedCode().concat( '///: END:ONLY_INCLUDE_IN', ); const stream = createRemoveFencedCodeTransform( getFeatures({ all: [MAIN_BUILD, FLASK_BUILD], active: [MAIN_BUILD] }), )(mockJsFileName); await new Promise((resolve) => { stream.on('error', (error) => { expect(error.message).toStrictEqual( expect.stringContaining( 'A valid fence consists of two fence lines, but the file contains an uneven number, "3", of fence lines.', ), ); expect(lintTransformedFileMock).toHaveBeenCalledTimes(0); resolve(); }); stream.end(fileContent); }); }); it('handles transformed file lint failure', async () => { lintTransformedFileMock.mockImplementationOnce(() => Promise.reject(new Error('lint failure')), ); const filePrefix = '// A comment\n'; const fileContent = filePrefix.concat(getMinimalFencedCode()); const stream = createRemoveFencedCodeTransform( getFeatures({ all: [FLASK_BUILD], active: [] }), )(mockJsFileName); await new Promise((resolve) => { stream.on('error', (error) => { expect(error).toStrictEqual(new Error('lint failure')); expect(lintTransformedFileMock).toHaveBeenCalledTimes(1); expect(lintTransformedFileMock).toHaveBeenCalledWith( filePrefix, mockJsFileName, ); resolve(); }); stream.end(fileContent); }); }); }); describe('removeFencedCode', () => { const mockFileName = 'file.js'; // Valid inputs ['main', 'flask', 'beta'].forEach((buildType) => { const active = buildTypesConfig.buildTypes[buildType].features; const all = Object.keys(buildTypesConfig.features); const features = getFeatures({ all, active }); it(`transforms file with fences for build type "${buildType}"`, () => { expect( removeFencedCode( mockFileName, features, testData.validInputs.withFences, ), ).toStrictEqual(testData.validOutputs[buildType]); expect( removeFencedCode( mockFileName, features, testData.validInputs.extraContentWithFences, ), ).toStrictEqual(testData.validOutputsWithExtraContent[buildType]); // Ensure that the minimal input template is in fact valid const minimalInput = getMinimalFencedCode(`build-${buildType}`); expect( removeFencedCode(mockFileName, features, minimalInput), ).toStrictEqual([minimalInput, false]); }); it(`does not modify file without fences for build type "${buildType}"`, () => { expect( removeFencedCode( mockFileName, features, testData.validInputs.withoutFences, ), ).toStrictEqual([testData.validInputs.withoutFences, false]); expect( removeFencedCode( mockFileName, features, testData.validInputs.extraContentWithoutFences, ), ).toStrictEqual([ testData.validInputs.extraContentWithoutFences, false, ]); }); }); // This is an edge case for the splicing function it('transforms file with two fence lines', () => { expect( removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD, MAIN_BUILD], }), getMinimalFencedCode(MAIN_BUILD), ), ).toStrictEqual(['', true]); }); it('ignores sentinels preceded by non-whitespace', () => { const validBeginDirective = '///: BEGIN:ONLY_INCLUDE_IN(build-flask)\n'; const ignoredLines = [ `a ${validBeginDirective}`, `2 ${validBeginDirective}`, `@ ${validBeginDirective}`, ]; ignoredLines.forEach((ignoredLine) => { // These inputs will be transformed expect( removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD, MAIN_BUILD], }), getMinimalFencedCode(MAIN_BUILD).concat(ignoredLine), ), ).toStrictEqual([ignoredLine, true]); const modifiedInputWithoutFences = testData.validInputs.withoutFences.concat(ignoredLine); // These inputs will not be transformed expect( removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), modifiedInputWithoutFences, ), ).toStrictEqual([modifiedInputWithoutFences, false]); }); }); // Invalid inputs it('rejects empty fences', () => { const jsComment = '// A comment\n'; const emptyFence = getMinimalFencedCode() .split('\n') .filter((line) => line.startsWith('///:')) .map((line) => `${line}\n`) .join(''); const emptyFenceWithPrefix = jsComment.concat(emptyFence); const emptyFenceWithSuffix = emptyFence.concat(jsComment); const emptyFenceSurrounded = emptyFenceWithPrefix.concat(jsComment); const inputs = [ emptyFence, emptyFenceWithPrefix, emptyFenceWithSuffix, emptyFenceSurrounded, ]; inputs.forEach((input) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), input, ), ).toThrow( `Empty fence found in file "${mockFileName}":\n${emptyFence}`, ); }); }); it('rejects sentinels not followed by a single space and a multi-character alphabetical string', () => { // Matches the sentinel and terminus component of the first line // beginning with "///: TERMINUS" const fenceSentinelAndTerminusRegex = /^\/\/\/: \w+/mu; const replacements = [ '///:BEGIN', '///:XBEGIN', '///:_BEGIN', '///:B', '///:_', '///: ', '///: B', '///:', ]; replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().replace( fenceSentinelAndTerminusRegex, replacement, ), ), ).toThrow( /Fence sentinel must be followed by a single space and an alphabetical string of two or more characters.$/u, ); }); }); it('rejects malformed BEGIN directives', () => { // This is the first line of the minimal input template const directiveString = '///: BEGIN:ONLY_INCLUDE_IN(build-flask)'; const replacements = [ // Invalid terminus '///: BE_GIN:BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: BE6IN:BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: BEGIN7:BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: BeGIN:ONLY_INCLUDE_IN(build-flask)', '///: BE3:BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: BEG-IN:BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: BEG N:BEGIN:ONLY_INCLUDE_IN(build-flask)', // Invalid commands '///: BEGIN:ONLY-INCLUDE_IN(flask)', '///: BEGIN:ONLY_INCLUDE:IN(flask)', '///: BEGIN:ONL6_INCLUDE_IN(flask)', '///: BEGIN:ONLY_IN@LUDE_IN(flask)', '///: BEGIN:ONLy_INCLUDE_IN(build-flask)', '///: BEGIN:ONLY INCLUDE_IN(flask)', // Invalid parameters '///: BEGIN:ONLY_INCLUDE_IN(,flask)', '///: BEGIN:ONLY_INCLUDE_IN(build-flask,)', '///: BEGIN:ONLY_INCLUDE_IN(build-flask,,main)', '///: BEGIN:ONLY_INCLUDE_IN(,)', '///: BEGIN:ONLY_INCLUDE_IN()', '///: BEGIN:ONLY_INCLUDE_IN( )', '///: BEGIN:ONLY_INCLUDE_IN(build-flask]', '///: BEGIN:ONLY_INCLUDE_IN[flask)', '///: BEGIN:ONLY_INCLUDE_IN(build-flask.main)', '///: BEGIN:ONLY_INCLUDE_IN(build-flask,@)', '///: BEGIN:ONLY_INCLUDE_IN(fla k)', // Stuff after the directive '///: BEGIN:ONLY_INCLUDE_IN(build-flask) A', '///: BEGIN:ONLY_INCLUDE_IN(build-flask) 9', '///: BEGIN:ONLY_INCLUDE_IN(build-flask)A', '///: BEGIN:ONLY_INCLUDE_IN(build-flask)9', '///: BEGIN:ONLY_INCLUDE_IN(build-flask)_', '///: BEGIN:ONLY_INCLUDE_IN(build-flask))', ]; replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().replace(directiveString, replacement), ), ).toThrow( new RegExp( `${replacement.replace( /([()[\]])/gu, '\\$1', )}":\nFailed to parse fence directive.$`, 'u', ), ); }); }); it('rejects malformed END directives', () => { // This is the last line of the minimal input template const directiveString = '///: END:ONLY_INCLUDE_IN'; const replacements = [ // Invalid terminus '///: ENx:ONLY_INCLUDE_IN', '///: EN3:ONLY_INCLUDE_IN', '///: EN_:ONLY_INCLUDE_IN', '///: EN :ONLY_INCLUDE_IN', '///: EN::ONLY_INCLUDE_IN', // Invalid commands '///: END:ONLY-INCLUDE_IN', '///: END::ONLY_INCLUDE_IN', '///: END:ONLY_INCLUDE:IN', '///: END:ONL6_INCLUDE_IN', '///: END:ONLY_IN@LUDE_IN', '///: END:ONLy_INCLUDE_IN', '///: END:ONLY INCLUDE_IN', // Stuff after the directive '///: END:ONLY_INCLUDE_IN A', '///: END:ONLY_INCLUDE_IN 9', '///: END:ONLY_INCLUDE_IN _', ]; replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().replace(directiveString, replacement), ), ).toThrow( new RegExp( `${replacement}":\nFailed to parse fence directive.$`, 'u', ), ); }); }); it('rejects files with uneven number of fence lines', () => { const additions = [ '///: BEGIN:ONLY_INCLUDE_IN(build-flask)', '///: END:ONLY_INCLUDE_IN', ]; additions.forEach((addition) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().concat(addition), ), ).toThrow( /A valid fence consists of two fence lines, but the file contains an uneven number, "3", of fence lines.$/u, ); }); }); it('rejects invalid terminuses', () => { const testCases = [ ['BEGIN', ['KAPLAR', 'FLASK', 'FOO']], ['END', ['KAPLAR', 'FOO', 'BAR']], ]; testCases.forEach(([validTerminus, replacements]) => { replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().replace(validTerminus, replacement), ), ).toThrow( new RegExp( `Line contains invalid directive terminus "${replacement}".$`, 'u', ), ); }); }); }); it('rejects invalid commands', () => { const testCases = [ [/ONLY_INCLUDE_IN\(/mu, ['ONLY_KEEP_IN(', 'FLASK(', 'FOO(']], [/ONLY_INCLUDE_IN$/mu, ['ONLY_KEEP_IN', 'FLASK', 'FOO']], ]; testCases.forEach(([validCommand, replacements]) => { replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode().replace(validCommand, replacement), ), ).toThrow( new RegExp( `Line contains invalid directive command "${replacement.replace( '(', '', )}".$`, 'u', ), ); }); }); }); it('rejects invalid command parameters', () => { const testCases = [ [ 'bar', ['bar', 'build-flask,bar', 'build-flask,build-beta,build-main,bar'], ], [ 'Foo', ['Foo', 'build-flask,Foo', 'build-flask,build-beta,build-main,Foo'], ], [ 'b3ta', [ 'b3ta', 'build-flask,b3ta', 'build-flask,build-beta,build-main,b3ta', ], ], [ 'bEta', [ 'bEta', 'build-flask,bEta', 'build-flask,build-beta,build-main,bEta', ], ], ]; testCases.forEach(([invalidParam, replacements]) => { replacements.forEach((replacement) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD, MAIN_BUILD, 'build-beta'], }), getMinimalFencedCode(replacement), ), ).toThrow( new RegExp( `"${invalidParam}" is not a declared build feature.$`, 'u', ), ); }); }); // Should fail for empty params expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), getMinimalFencedCode('').replace('()', ''), ), ).toThrow(/No params specified.$/u); }); it('rejects directive pairs with wrong terminus order', () => { // We need more than one directive pair for this test const input = getMinimalFencedCode().concat( getMinimalFencedCode('build-beta'), ); const expectedBeginError = 'The first directive of a pair must be a "BEGIN" directive.'; const expectedEndError = 'The second directive of a pair must be an "END" directive.'; const testCases = [ [ 'BEGIN:ONLY_INCLUDE_IN(build-flask)', 'END:ONLY_INCLUDE_IN', expectedBeginError, ], [ /END:ONLY_INCLUDE_IN/mu, 'BEGIN:ONLY_INCLUDE_IN(build-main)', expectedEndError, ], [ 'BEGIN:ONLY_INCLUDE_IN(build-beta)', 'END:ONLY_INCLUDE_IN', expectedBeginError, ], ]; testCases.forEach(([target, replacement, expectedError]) => { expect(() => removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), input.replace(target, replacement), ), ).toThrow(expectedError); }); }); it('ignores files with inline source maps', () => { // This is so that there isn't an unnecessary second execution of // removeFencedCode with a transpiled version of the same file const input = getTestData().validInputs.extraContentWithFences.concat( '\n//# sourceMappingURL=as32e32wcwc2234f2ew32cnin4243f4nv9nsdoivnxzoivnd', ); expect( removeFencedCode( mockFileName, getFeatures({ active: [FLASK_BUILD], all: [FLASK_BUILD] }), input, ), ).toStrictEqual([input, false]); }); // We can't do this until there's more than one command it.todo('rejects directive pairs with mismatched commands'); }); }); function getTestData() { const data = { validInputs: { withFences: ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN `, extraContentWithFences: ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included `, withoutFences: ` Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, extraContentWithoutFences: ` Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, }, validOutputs: { beta: [ ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, true, ], flask: [ ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN `, false, ], mmi: [ ` Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, true, ], }, validOutputsWithExtraContent: { beta: [ ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, true, ], flask: [ ` ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask,build-beta,desktop) Conditionally_Included Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included ///: BEGIN:ONLY_INCLUDE_IN(build-flask) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN ///: BEGIN:ONLY_INCLUDE_IN(desktop) Conditionally_Included Conditionally_Included ///: END:ONLY_INCLUDE_IN Always_Included Always_Included Always_Included `, false, ], mmi: [ ` Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included Always_Included `, true, ], }, }; data.validOutputs.desktop = [data.validInputs.withFences, false]; data.validOutputs.main = [data.validInputs.withoutFences, true]; data.validOutputsWithExtraContent.desktop = [ data.validInputs.extraContentWithFences, false, ]; data.validOutputsWithExtraContent.main = [ data.validInputs.extraContentWithoutFences, true, ]; return deepFreeze(data); }