Skip to content

Commit

Permalink
feat(workspace-plugin): add generateApi functionality/flag to build…
Browse files Browse the repository at this point in the history
… executor (microsoft#32976)
  • Loading branch information
Hotell authored Oct 10, 2024
1 parent 95ec5c9 commit de0eb04
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 27 deletions.
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);
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(
() => 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) {
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;
}

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

0 comments on commit de0eb04

Please sign in to comment.