mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-23 02:10:12 +01:00
345ed9f6f2
The build type (i.e. the distribution) is now included in the Sentry environment during setup, for all builds except the "main" build. This will allow us to track Flask and beta errors separately from other errors. A constant was created for the build types. The equivalent constant in our build scripts was updated to match it more closely, for consistency. We can't use the same constant in both places because our shared constants are in modules that use ES6 exports, and our build script does not yet support ES6 exports. The singular `BuildType` was used rather than `BuildTypes` to match our naming conventions elsewhere for enums. We name them like classes or types, rather than like a collection. Relates to #11896
664 lines
19 KiB
JavaScript
664 lines
19 KiB
JavaScript
const deepFreeze = require('deep-freeze-strict');
|
|
const { BuildType } = require('../utils');
|
|
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 getMinimalFencedCode = (params = 'flask') =>
|
|
`///: BEGIN:ONLY_INCLUDE_IN(${params})
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
`;
|
|
|
|
describe('build/transforms/remove-fenced-code', () => {
|
|
describe('createRemoveFencedCodeTransform', () => {
|
|
const { lintTransformedFile: lintTransformedFileMock } = transformUtils;
|
|
const mockJsFileName = 'file.js';
|
|
|
|
beforeEach(() => {
|
|
lintTransformedFileMock.mockImplementation(() => Promise.resolve());
|
|
});
|
|
|
|
it('rejects invalid build types', () => {
|
|
expect(() => createRemoveFencedCodeTransform('foobar')).toThrow(
|
|
/received unrecognized build type "foobar".$/u,
|
|
);
|
|
});
|
|
|
|
it('returns a PassThrough stream for files with ignored extensions', async () => {
|
|
const fileContent = '"Valid JSON content"\n';
|
|
const stream = createRemoveFencedCodeTransform('main')('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('main')(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('main')(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');
|
|
|
|
const stream = createRemoveFencedCodeTransform('main')(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(
|
|
'main',
|
|
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 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('main')(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
|
|
Object.keys(BuildType).forEach((buildType) => {
|
|
it(`transforms file with fences for build type "${buildType}"`, () => {
|
|
expect(
|
|
removeFencedCode(
|
|
mockFileName,
|
|
buildType,
|
|
testData.validInputs.withFences,
|
|
),
|
|
).toStrictEqual(testData.validOutputs[buildType]);
|
|
|
|
// Ensure that the minimal input template is in fact valid
|
|
const minimalInput = getMinimalFencedCode(buildType);
|
|
expect(
|
|
removeFencedCode(mockFileName, buildType, minimalInput),
|
|
).toStrictEqual([minimalInput, false]);
|
|
});
|
|
|
|
it(`does not modify file without fences for build type "${buildType}"`, () => {
|
|
expect(
|
|
removeFencedCode(
|
|
mockFileName,
|
|
buildType,
|
|
testData.validInputs.withoutFences,
|
|
),
|
|
).toStrictEqual([testData.validInputs.withoutFences, false]);
|
|
});
|
|
});
|
|
|
|
// This is an edge case for the splicing function
|
|
it('transforms file with two fence lines', () => {
|
|
expect(
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
getMinimalFencedCode('main'),
|
|
),
|
|
).toStrictEqual(['', true]);
|
|
});
|
|
|
|
it('ignores sentinels preceded by non-whitespace', () => {
|
|
const validBeginDirective = '///: BEGIN:ONLY_INCLUDE_IN(flask)\n';
|
|
const ignoredLines = [
|
|
`a ${validBeginDirective}`,
|
|
`2 ${validBeginDirective}`,
|
|
`@ ${validBeginDirective}`,
|
|
];
|
|
|
|
ignoredLines.forEach((ignoredLine) => {
|
|
// These inputs will be transformed
|
|
expect(
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
getMinimalFencedCode('main').concat(ignoredLine),
|
|
),
|
|
).toStrictEqual([ignoredLine, true]);
|
|
|
|
const modifiedInputWithoutFences = testData.validInputs.withoutFences.concat(
|
|
ignoredLine,
|
|
);
|
|
|
|
// These inputs will not be transformed
|
|
expect(
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
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, BuildType.flask, 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,
|
|
BuildType.flask,
|
|
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(flask)';
|
|
|
|
const replacements = [
|
|
// Invalid terminus
|
|
'///: BE_GIN:ONLY_INCLUDE_IN(flask)',
|
|
'///: BE6IN:ONLY_INCLUDE_IN(flask)',
|
|
'///: BEGIN7:ONLY_INCLUDE_IN(flask)',
|
|
'///: BeGIN:ONLY_INCLUDE_IN(flask)',
|
|
'///: BE3:ONLY_INCLUDE_IN(flask)',
|
|
'///: BEG-IN:ONLY_INCLUDE_IN(flask)',
|
|
'///: BEG N:ONLY_INCLUDE_IN(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(flask)',
|
|
'///: BEGIN:ONLY INCLUDE_IN(flask)',
|
|
|
|
// Invalid parameters
|
|
'///: BEGIN:ONLY_INCLUDE_IN(,flask)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask,)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask,,main)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(,)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN()',
|
|
'///: BEGIN:ONLY_INCLUDE_IN( )',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask]',
|
|
'///: BEGIN:ONLY_INCLUDE_IN[flask)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask.main)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask,@)',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(fla k)',
|
|
|
|
// Stuff after the directive
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask) A',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask) 9',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask)A',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask)9',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask)_',
|
|
'///: BEGIN:ONLY_INCLUDE_IN(flask))',
|
|
];
|
|
|
|
replacements.forEach((replacement) => {
|
|
expect(() =>
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
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,
|
|
BuildType.flask,
|
|
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(flask)',
|
|
'///: END:ONLY_INCLUDE_IN',
|
|
];
|
|
additions.forEach((addition) => {
|
|
expect(() =>
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
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,
|
|
BuildType.flask,
|
|
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,
|
|
BuildType.flask,
|
|
getMinimalFencedCode().replace(validCommand, replacement),
|
|
),
|
|
).toThrow(
|
|
new RegExp(
|
|
`Line contains invalid directive command "${replacement.replace(
|
|
'(',
|
|
'',
|
|
)}".$`,
|
|
'u',
|
|
),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
it('rejects invalid command parameters', () => {
|
|
const testCases = [
|
|
['bar', ['bar', 'flask,bar', 'flask,beta,main,bar']],
|
|
['Foo', ['Foo', 'flask,Foo', 'flask,beta,main,Foo']],
|
|
['b3ta', ['b3ta', 'flask,b3ta', 'flask,beta,main,b3ta']],
|
|
['bEta', ['bEta', 'flask,bEta', 'flask,beta,main,bEta']],
|
|
];
|
|
|
|
testCases.forEach(([invalidParam, replacements]) => {
|
|
replacements.forEach((replacement) => {
|
|
expect(() =>
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
getMinimalFencedCode(replacement),
|
|
),
|
|
).toThrow(
|
|
new RegExp(`"${invalidParam}" is not a valid build type.$`, 'u'),
|
|
);
|
|
});
|
|
});
|
|
|
|
// Should fail for empty params
|
|
expect(() =>
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
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('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(flask)',
|
|
'END:ONLY_INCLUDE_IN',
|
|
expectedBeginError,
|
|
],
|
|
[
|
|
/END:ONLY_INCLUDE_IN/mu,
|
|
'BEGIN:ONLY_INCLUDE_IN(main)',
|
|
expectedEndError,
|
|
],
|
|
[
|
|
'BEGIN:ONLY_INCLUDE_IN(beta)',
|
|
'END:ONLY_INCLUDE_IN',
|
|
expectedBeginError,
|
|
],
|
|
];
|
|
|
|
testCases.forEach(([target, replacement, expectedError]) => {
|
|
expect(() =>
|
|
removeFencedCode(
|
|
mockFileName,
|
|
BuildType.flask,
|
|
input.replace(target, replacement),
|
|
),
|
|
).toThrow(expectedError);
|
|
});
|
|
});
|
|
|
|
// 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(flask,beta)
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
///: BEGIN:ONLY_INCLUDE_IN(flask,beta)
|
|
Conditionally_Included
|
|
|
|
Conditionally_Included
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
Always_Included
|
|
|
|
Always_Included
|
|
Always_Included
|
|
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
|
|
|
Conditionally_Included
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
|
|
///: BEGIN:ONLY_INCLUDE_IN(flask)
|
|
Conditionally_Included
|
|
Conditionally_Included
|
|
|
|
///: END:ONLY_INCLUDE_IN
|
|
`,
|
|
|
|
withoutFences: `
|
|
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(flask,beta)
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
///: BEGIN:ONLY_INCLUDE_IN(flask,beta)
|
|
Conditionally_Included
|
|
|
|
Conditionally_Included
|
|
Conditionally_Included
|
|
///: END:ONLY_INCLUDE_IN
|
|
Always_Included
|
|
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
Always_Included
|
|
|
|
`,
|
|
true,
|
|
],
|
|
},
|
|
};
|
|
|
|
data.validOutputs.flask = [data.validInputs.withFences, false];
|
|
data.validOutputs.main = [data.validInputs.withoutFences, true];
|
|
return deepFreeze(data);
|
|
}
|