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

feat(workspace-plugin): add generateApi functionality/flag to build executor #32976

Merged
merged 3 commits into from
Oct 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions tools/workspace-plugin/STYLE-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Generators

Generators should live in the `tools/generators` folder. [Learn more about Nx generators](https://nx.dev/generators/workspace-generators).
Generators live in the `tools/workspace-plugin/src/generators` folder. [Learn more about Nx generators](https://nx.dev/generators/workspace-generators).

### Scaffolding

Expand Down Expand Up @@ -52,7 +52,7 @@ Integration tests for the generator as a whole.

TypeScript interface that matches `schema.json`. You can generate this from the json file by running:

- `npx json-schema-to-typescript -i tools/generators/<generator-name>/schema.json -o tools/generators/<generator-name>/schema.ts --additionalProperties false`
- `npx json-schema-to-typescript@latest -i tools/workspace-plugin/src/generators/<name>/schema.json -o tools/workspace-plugin/src/generators/<name>/schema.d.ts --additionalProperties false`

**`schema.json`**

Expand Down Expand Up @@ -95,12 +95,20 @@ Migrations follow same rules as [Generators](#Generators) as they behave the sam

## Executors

Executors should live in the `tools/executors` folder. [Learn more about Nx executors](https://nx.dev/executors/using-builders).
Executors live in the `tools/workspace-plugin/src/executors` folder. [Learn more about Nx executors](https://nx.dev/executors/using-builders).

### Scaffolding

TBA
**`schema.d.ts`**

TypeScript interface that matches `schema.json`. You can generate this from the json file by running:

- `npx json-schema-to-typescript@latest -i tools/workspace-plugin/src/executors/<name>/schema.json -o tools/workspace-plugin/src/executors/<name>/schema.d.ts --additionalProperties false`

**`schema.json`**

Provides a description of the generator, available options, validation information, and default values. This is processed by nx cli when invoking generator to provide argument validations/processing/prompts.

### Bootstrap new executor

TBA
`yarn nx g @nx/plugin:executor --directory tools/workspace-plugin/src/executors/<name-of-executor>`
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/dist/out-tsc/index.d.ts",
"docModel": {
"enabled": false
},
"apiReport": {
"enabled": true,
"reportFileName": "api.md"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/dist/index.d.ts"
},
"tsdocMetadata": {
"enabled": false
},
"messages": {
"extractorMessageReporting": {
"ae-forgotten-export": {
"logLevel": "none",
"addToApiReportFile": false
},
"ae-missing-release-tag": {
"logLevel": "none"
},
"ae-unresolved-link": {
"logLevel": "none"
},
"ae-internal-missing-underscore": {
"logLevel": "none",
"addToApiReportFile": false
}
}
},
"newlineKind": "os"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## API Report File for "proj"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

// @public (undocumented)
export function greeter(greeting: string, user: User): string;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"name": "proj"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"moduleResolution": "Node",
"target": "ES2019",
"skipLibCheck": true,
"pretty": true
},
"include": [],
"files": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./dist/out-tsc",
"declaration": true
},
"include": ["src/*.ts"]
}
48 changes: 46 additions & 2 deletions tools/workspace-plugin/src/executors/build/executor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const options: BuildExecutorSchema = {
glob: '*.md',
},
],
generateApi: true,
clean: true,
};

Expand Down Expand Up @@ -73,6 +74,15 @@ const measureEndMock = measureEnd as jest.Mock;

describe('Build Executor', () => {
it('can run', async () => {
// mute api extractor - START
jest.spyOn(console, 'warn').mockImplementation(() => {
return;
});
jest.spyOn(console, 'log').mockImplementation(() => {
return;
});
// mute api extractor - END

const loggerLogSpy = jest.spyOn(logger, 'log').mockImplementation(() => {
return;
});
Expand Down Expand Up @@ -122,12 +132,14 @@ describe('Build Executor', () => {
},
]);

expect(measureStartMock).toHaveBeenCalledTimes(1);
expect(measureEndMock).toHaveBeenCalledTimes(1);
expect(measureStartMock).toHaveBeenCalledTimes(2);
Hotell marked this conversation as resolved.
Show resolved Hide resolved
expect(measureEndMock).toHaveBeenCalledTimes(2);

// =====================
// assert build Assets
// =====================
expect(existsSync(join(workspaceRoot, 'libs/proj/etc', 'api.md'))).toBe(true);
expect(existsSync(join(workspaceRoot, 'libs/proj/dist', 'index.d.ts'))).toBe(true);
expect(existsSync(join(workspaceRoot, 'libs/proj/dist/assets', 'spec.md'))).toBe(true);
expect(readdirSync(join(workspaceRoot, 'libs/proj/lib'))).toEqual([
'greeter.js',
Expand All @@ -146,6 +158,38 @@ describe('Build Executor', () => {
'index.js.map',
]);

// ====================================
// assert generateAPI output based on settings
// ====================================
expect(readFileSync(join(workspaceRoot, 'libs/proj/dist/index.d.ts'), 'utf-8')).toMatchInlineSnapshot(`
"export declare function greeter(greeting: string, user: User): string;

declare type User = {
name: string;
hometown?: {
name: string;
};
};

export { }
"
`);
expect(readFileSync(join(workspaceRoot, 'libs/proj/etc/api.md'), 'utf-8')).toMatchInlineSnapshot(`
"## API Report File for \\"proj\\"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

\`\`\`ts

// @public (undocumented)
export function greeter(greeting: string, user: User): string;

// (No @packageDocumentation comment for this package)

\`\`\`
"
`);

// ====================================
// assert swc output based on settings
// ====================================
Expand Down
27 changes: 15 additions & 12 deletions tools/workspace-plugin/src/executors/build/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,28 @@ import { compileSwc } from './lib/swc';
import { compileWithGriffelStylesAOT, hasStylesFilesToProcess } from './lib/babel';
import { assetGlobsToFiles, copyAssets } from './lib/assets';
import { cleanOutput } from './lib/clean';
import { NormalizedOptions, normalizeOptions, processAsyncQueue } from './lib/shared';
import { NormalizedOptions, normalizeOptions, processAsyncQueue, runInParallel, runSerially } from './lib/shared';

import { measureEnd, measureStart } from '../../utils';
import generateApiExecutor from '../generate-api/executor';

import { type BuildExecutorSchema } from './schema';

const runExecutor: PromiseExecutor<BuildExecutorSchema> = async (schema, context) => {
measureStart('BuildExecutor');

const options = normalizeOptions(schema, context);
const assetFiles = assetGlobsToFiles(options.assets ?? [], context.root, options.outputPathRoot);

const success = await runBuild(options, context);
const success = await runSerially(
Hotell marked this conversation as resolved.
Show resolved Hide resolved
() => cleanOutput(options, assetFiles),
() =>
runInParallel(
() => runBuild(options, context),
() => (options.generateApi ? generateApiExecutor({}, context).then(res => res.success) : Promise.resolve(true)),
),
() => copyAssets(assetFiles),
);

measureEnd('BuildExecutor');

Expand All @@ -26,21 +36,14 @@ export default runExecutor;

// ===========

async function runBuild(options: NormalizedOptions, context: ExecutorContext): Promise<boolean> {
const assetFiles = assetGlobsToFiles(options.assets ?? [], context.root, options.outputPathRoot);

const cleanResult = await cleanOutput(options, assetFiles);
if (!cleanResult) {
return false;
}

async function runBuild(options: NormalizedOptions, _context: ExecutorContext): Promise<boolean> {
if (hasStylesFilesToProcess(options)) {
return compileWithGriffelStylesAOT(options, () => copyAssets(assetFiles));
return compileWithGriffelStylesAOT(options);
}

const compilationQueue = options.moduleOutput.map(outputConfig => {
return compileSwc(outputConfig, options);
});

return processAsyncQueue(compilationQueue, () => copyAssets(assetFiles));
return processAsyncQueue(compilationQueue);
}
6 changes: 3 additions & 3 deletions tools/workspace-plugin/src/executors/build/lib/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function hasStylesFilesToProcess(normalizedOptions: NormalizedOptions) {
return files.length > 0;
}

export async function compileWithGriffelStylesAOT(options: NormalizedOptions, successCallback: () => Promise<boolean>) {
export async function compileWithGriffelStylesAOT(options: NormalizedOptions) {
Hotell marked this conversation as resolved.
Show resolved Hide resolved
const { esmConfig, restOfConfigs } = options.moduleOutput.reduce<{
esmConfig: NormalizedOptions['moduleOutput'][number] | null;
restOfConfigs: NormalizedOptions['moduleOutput'];
Expand All @@ -47,7 +47,7 @@ export async function compileWithGriffelStylesAOT(options: NormalizedOptions, su
const compilationQueue = restOfConfigs.map(outputConfig => {
return compileSwc(outputConfig, options);
});
return processAsyncQueue(compilationQueue, successCallback);
return processAsyncQueue(compilationQueue);
}

await compileSwc(esmConfig, options);
Expand All @@ -65,7 +65,7 @@ export async function compileWithGriffelStylesAOT(options: NormalizedOptions, su
});
});

return processAsyncQueue(compilationQueue, successCallback);
return processAsyncQueue(compilationQueue);
}

async function babel(esmModuleOutput: NormalizedOptions['moduleOutput'][number], normalizedOptions: NormalizedOptions) {
Expand Down
2 changes: 1 addition & 1 deletion tools/workspace-plugin/src/executors/build/lib/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { type NormalizedOptions, processAsyncQueue } from './shared';

export async function cleanOutput(options: NormalizedOptions, assetFiles: Array<{ input: string; output: string }>) {
if (!options.clean) {
return;
return true;
Hotell marked this conversation as resolved.
Show resolved Hide resolved
}

const swcOutputPaths = options.moduleOutput.map(outputConfig => {
Expand Down
30 changes: 28 additions & 2 deletions tools/workspace-plugin/src/executors/build/lib/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,35 @@ import { join } from 'node:path';

import { type BuildExecutorSchema } from '../schema';

export async function processAsyncQueue(value: Promise<unknown>[], successCallback?: () => Promise<boolean>) {
type Tasks = () => Promise<boolean>;

export async function runInParallel(...tasks: Tasks[]): Promise<boolean> {
const processes = tasks.map(task => task());

return Promise.all(processes)
.then(() => {
return true;
})
.catch(err => {
logger.error(err);
return false;
});
}

export async function runSerially(...tasks: Tasks[]): Promise<boolean> {
for (const task of tasks) {
const result = await task();
if (!result) {
return false;
}
}
return true;
}

export async function processAsyncQueue(value: Promise<unknown>[]): Promise<boolean> {
return Promise.all(value)
.then(() => {
return successCallback ? successCallback() : true;
return true;
})
.catch(err => {
logger.error(err);
Expand All @@ -17,6 +42,7 @@ export async function processAsyncQueue(value: Promise<unknown>[], successCallba
export interface NormalizedOptions extends ReturnType<typeof normalizeOptions> {}
export function normalizeOptions(schema: BuildExecutorSchema, context: ExecutorContext) {
const defaults = {
generateApi: true,
clean: true,
};
const project = context.projectsConfigurations!.projects[context.projectName!];
Expand Down
7 changes: 6 additions & 1 deletion tools/workspace-plugin/src/executors/build/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable */
/**
* This file was automatically generated by json-schema-to-typescript.
* DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
* and run json-schema-to-typescript to regenerate this file.
*/

/**
* Builds TS files with SWC to specified JS module type, transforms *.styles.* files via Babel(Griffel) and copies provided Assets.
* Transpiles TS files with SWC to specified JS module type, Generates rolluped .d.ts + api.md, transforms *.styles.* files via Babel(Griffel) and copies provided Assets.
*/
export interface BuildExecutorSchema {
/**
Expand All @@ -29,6 +30,10 @@ export interface BuildExecutorSchema {
*/
outputPath: string;
}[];
/**
* Generate rolluped 'd.ts' bundle including 'api.md' that provides project public API
*/
generateApi?: boolean;
/**
* List of static assets.
*/
Expand Down
7 changes: 6 additions & 1 deletion tools/workspace-plugin/src/executors/build/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/schema",
"version": 2,
"title": "Build executor",
"description": "Builds TS files with SWC to specified JS module type, transforms *.styles.* files via Babel(Griffel) and copies provided Assets.",
"description": "Transpiles TS files with SWC to specified JS module type, Generates rolluped .d.ts + api.md, transforms *.styles.* files via Babel(Griffel) and copies provided Assets.",
"type": "object",
"properties": {
"sourceRoot": {
Expand All @@ -28,6 +28,11 @@
"required": ["module", "outputPath"]
}
},
"generateApi": {
"type": "boolean",
"description": "Generate rolluped 'd.ts' bundle including 'api.md' that provides project public API",
"default": true
},
"assets": {
"type": "array",
"description": "List of static assets.",
Expand Down
Loading