Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CLI] Fix create plugin wizard #103

Merged
merged 10 commits into from
Oct 4, 2024
Merged
2 changes: 2 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
// Anything between #imports-injection-marker and #types-injection-marker
// will be updated using the 'yarn cli integrity' command.

/* eslint-disable arca/import-ordering */
// #imports-injection-marker
import type { RumOptions } from '@dd/rum-plugins/types';
import type * as rum from '@dd/rum-plugins';
import type { TelemetryOptions } from '@dd/telemetry-plugins/types';
import type * as telemetry from '@dd/telemetry-plugins';
// #imports-injection-marker
/* eslint-enable arca/import-ordering */

import type { BodyInit } from 'undici-types';
import type { UnpluginContextMeta, UnpluginOptions } from 'unplugin';
Expand Down
4 changes: 4 additions & 0 deletions packages/factory/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,24 @@ import { getInternalPlugins } from '@dd/core/plugins/index';
// eslint-disable-next-line arca/newline-after-import-section
import type { Options, PluginOptions } from '@dd/core/types';

/* eslint-disable arca/import-ordering */
// #imports-injection-marker
import type { OptionsWithRum } from '@dd/rum-plugins/types';
import * as rum from '@dd/rum-plugins';
import type { OptionsWithTelemetry } from '@dd/telemetry-plugins/types';
import * as telemetry from '@dd/telemetry-plugins';
// #imports-injection-marker
/* eslint-enable arca/import-ordering */

import type { UnpluginContextMeta, UnpluginInstance, UnpluginOptions } from 'unplugin';
import { createUnplugin } from 'unplugin';

/* eslint-disable arca/import-ordering */
// #types-export-injection-marker
export type { types as RumTypes } from '@dd/rum-plugins';
export type { types as TelemetryTypes } from '@dd/telemetry-plugins';
// #types-export-injection-marker
/* eslint-enable arca/import-ordering */

export const helpers = {
// Each product should have a unique entry.
Expand Down
1 change: 1 addition & 0 deletions packages/tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@dd/core": "workspace:*",
"@dd/telemetry-plugins": "workspace:*",
"@rollup/plugin-commonjs": "25.0.7",
"clipanion": "4.0.0-rc.3",
"glob": "11.0.0",
"jest": "29.7.0",
"ts-jest": "29.1.2"
Expand Down
11 changes: 11 additions & 0 deletions packages/tests/src/helpers/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,14 @@ export const getComplexBuildOverrides = (

return bundlerOverrides;
};

// Returns a JSON of files with their content.
// To be used with memfs' vol.fromJSON.
export const getMirroredFixtures = (paths: string[], cwd: string) => {
const fsa = jest.requireActual('fs');
const fixtures: Record<string, string> = {};
for (const p of paths) {
fixtures[p] = fsa.readFileSync(path.resolve(cwd, p), 'utf-8');
}
return fixtures;
};
31 changes: 8 additions & 23 deletions packages/tests/src/plugins/rum/sourcemaps/files.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,12 @@
// Copyright 2019-Present Datadog, Inc.

import { getSourcemapsFiles } from '@dd/rum-plugins/sourcemaps/files';
import { vol } from 'memfs';
import path from 'path';

import { getContextMock } from '../../../helpers/mocks';
import { getSourcemapsConfiguration } from '../testHelpers';

jest.mock('fs', () => require('memfs').fs);

const FIXTURES = {
// Adding both .js and .mjs files.
'fixtures/common.js': '',
'fixtures/common.min.js.map': '',
'fixtures/common.min.js': '',
'fixtures/common.mjs': '',
'fixtures/common.min.mjs': '',
'fixtures/common.min.mjs.map': '',
};

describe('RUM Plugin Sourcemaps Files', () => {
beforeEach(() => {
// Emulate some fixtures.
vol.fromJSON(FIXTURES, __dirname);
});

afterEach(() => {
vol.reset();
});

test('It should get sourcemap files.', async () => {
const sourcemaps = getSourcemapsFiles(
getSourcemapsConfiguration({
Expand All @@ -45,7 +23,14 @@ describe('RUM Plugin Sourcemaps Files', () => {
build: {
warnings: [],
errors: [],
outputs: Object.keys(FIXTURES).map((filepath) => ({
outputs: [
'fixtures/common.js',
'fixtures/common.min.js.map',
'fixtures/common.min.js',
'fixtures/common.mjs',
'fixtures/common.min.mjs',
'fixtures/common.min.mjs.map',
].map((filepath) => ({
name: path.basename(filepath),
filepath: path.join(__dirname, filepath),
inputs: [],
Expand Down
183 changes: 183 additions & 0 deletions packages/tests/src/tools/src/commands/create-plugin/ask.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import {
getCodeowners,
getDescription,
getHooksToInclude,
getName,
getTypeOfPlugin,
listHooks,
sanitizeCodeowners,
validateHooks,
} from '@dd/tools/commands/create-plugin/ask';
import { bundlerHookNames, universalHookNames } from '@dd/tools/commands/create-plugin/constants';
import { bundlerHooks, universalHooks } from '@dd/tools/commands/create-plugin/hooks';
import type { AnyHook, TypeOfPlugin } from '@dd/tools/commands/create-plugin/types';
import checkbox from '@inquirer/checkbox';
import input from '@inquirer/input';
import select from '@inquirer/select';

jest.mock('@inquirer/checkbox', () => jest.fn());
jest.mock('@inquirer/input', () => jest.fn());
jest.mock('@inquirer/select', () => jest.fn());

const checkboxMocked = jest.mocked(checkbox);
const inputMocked = jest.mocked(input);
const selectMocked = jest.mocked(select);

describe('ask.ts', () => {
describe('getName', () => {
test.each([
['Testing #1', 'testing-1'],
['Some, Name. With punctuation.', 'some-name-with-punctuation'],
['Âñy Wëîrd Nâmë', 'any-weird-name'],
])('Should slugify "%s" into "%s".', async (nameInput, expectation) => {
const name = await getName(nameInput);
expect(name).toBe(expectation);
});

test('Should ask for a name if none is provided.', async () => {
inputMocked.mockResolvedValueOnce('Testing #1');
const name = await getName();
expect(name).toBe('testing-1');
expect(inputMocked).toHaveBeenCalledTimes(1);
});
});

describe('getDescription', () => {
test('Should return the description if provided.', async () => {
const description = await getDescription('Some description.');
expect(description).toBe('Some description.');
});

test('Should ask for a description if none is provided.', async () => {
inputMocked.mockResolvedValueOnce('Some description.');
const description = await getDescription();
expect(description).toBe('Some description.');
expect(inputMocked).toHaveBeenCalledTimes(1);
});
});

describe('sanitizeCodeowners', () => {
test.each([
['@codeowners-1 @codeowners-2', '@codeowners-1 @codeowners-2'],
['codeowners-1 codeowners-2', '@codeowners-1 @codeowners-2'],
['@codeowners-1 , codeowners-2', '@codeowners-1 @codeowners-2'],
['@codeowners-1,codeowners-2', '@codeowners-1 @codeowners-2'],
])('Should sanitize "%s" into "%s".', (stringInput, expectation) => {
const codeowners = sanitizeCodeowners(stringInput);
expect(codeowners).toBe(expectation);
});
});

describe('getCodeowners', () => {
test('Should return the codeowners if provided.', async () => {
const codeowners = await getCodeowners(['@codeowners-1', '@codeowners-2']);
expect(codeowners).toBe('@codeowners-1 @codeowners-2');
});

test('Should ask for codeowners if none are provided.', async () => {
inputMocked.mockResolvedValueOnce('@codeowners-1 @codeowners-2');
const codeowners = await getCodeowners();
expect(codeowners).toBe('@codeowners-1 @codeowners-2');
expect(inputMocked).toHaveBeenCalledTimes(1);
});

test('Should sanitize the codeowners.', async () => {
const codeowners = await getCodeowners(['codeowners-1', 'codeowners-2']);
expect(codeowners).toBe('@codeowners-1 @codeowners-2');
});
});

describe('listHooks', () => {
test.each([
[bundlerHooks, bundlerHookNames],
[universalHooks, universalHookNames],
])('Should return the hooks in a formated list.', (hooksInput, expectedHookNames) => {
const hooksReturned = listHooks(hooksInput);
expect(hooksReturned).toEqual(
expectedHookNames.map((hook) => ({
name: expect.any(String),
value: hook,
checked: false,
})),
);
});
});

describe('getTypeOfPlugin', () => {
test('Should return the type if provided.', async () => {
const type = await getTypeOfPlugin('universal');
expect(type).toBe('universal');
});

test('Should ask for a type if an invalid one is provided.', async () => {
selectMocked.mockResolvedValueOnce('universal');
// @ts-expect-error We are testing the error case.
const type = await getTypeOfPlugin('invalid');
expect(type).toBe('universal');
expect(selectMocked).toHaveBeenCalledTimes(1);
});

test('Should ask for a type if none is provided.', async () => {
selectMocked.mockResolvedValueOnce('universal');
const type = await getTypeOfPlugin();
expect(type).toBe('universal');
expect(selectMocked).toHaveBeenCalledTimes(1);
});
});

describe('validateHooks', () => {
test.each<[TypeOfPlugin, AnyHook[], string[]]>([
['universal', ['enforce', 'buildStart'], ['enforce', 'buildStart']],
['universal', ['enforce', 'buildStart', 'webpack'], ['enforce', 'buildStart']],
['bundler', ['webpack', 'esbuild'], ['webpack', 'esbuild']],
['bundler', ['enforce', 'buildStart', 'webpack'], ['webpack']],
['bundler', ['enforce', 'buildStart'], []],
])(
'Should return the the expected set of hooks for "%s" plugin.',
(pluginType, hooksInput, expectation) => {
const hooksReturned = validateHooks(pluginType, hooksInput);
expect(hooksReturned).toEqual(expectation);
},
);
});

describe('getHooksToInclude', () => {
test('Should return the hooks if provided.', async () => {
const hooksReturned = await getHooksToInclude('universal', ['enforce', 'buildStart']);
expect(hooksReturned).toEqual(['enforce', 'buildStart']);
});

test('Should filter out invalid hooks when provided.', async () => {
const hooksReturned = await getHooksToInclude('bundler', [
'enforce',
'buildStart',
'webpack',
]);
expect(hooksReturned).toEqual(['webpack']);
});

test('Should ask for hooks if none are provided.', async () => {
checkboxMocked.mockResolvedValueOnce(['enforce', 'buildStart']);
const hooksReturned = await getHooksToInclude('universal');
expect(hooksReturned).toEqual(['enforce', 'buildStart']);
expect(checkboxMocked).toHaveBeenCalledTimes(1);
});

test.each<[TypeOfPlugin, any[]]>([
['universal', listHooks(universalHooks)],
['bundler', listHooks(bundlerHooks)],
])(
'Should offer a specific list of hooks for "%s" plugins.',
async (pluginType, expectedHooks) => {
await getHooksToInclude(pluginType);
expect(checkboxMocked).toHaveBeenCalledWith(
expect.objectContaining({ choices: expectedHooks }),
);
},
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { allHookNames } from '@dd/tools/commands/create-plugin/constants';
import { allHooks, getHookTemplate } from '@dd/tools/commands/create-plugin/hooks';

describe('hooks.ts', () => {
describe('allHooks', () => {
test.each(allHookNames)('Should have a description for %s.', (hookInput) => {
const hook = allHooks[hookInput];
expect(hook).toBeDefined();
expect(hook.name).toBeTruthy();
expect(hook.descriptions).toBeDefined();
expect(hook.descriptions).not.toHaveLength(0);
});
});

describe('getHookTemplate', () => {
test.each(allHookNames)('Should have a template for %s.', (hookInput) => {
const template = getHookTemplate(hookInput);
expect(template).toBeTruthy();
});
});
});
Loading
Loading