Skip to content

Latest commit

 

History

History
225 lines (200 loc) · 5.71 KB

SOLUTION.md

File metadata and controls

225 lines (200 loc) · 5.71 KB
Generate a update-scope-schema generator:
nx generate @nx/plugin:generator update-scope-schema --directory libs/internal-plugin/src/generators/update-scope-schema
Change default project
import { formatFiles, Tree, updateJson } from '@nx/devkit';

export default async function (tree: Tree) {
  updateJson(tree, 'nx.json', (json) => ({
    ...json,
    defaultProject: 'api',
  }));
  await formatFiles(tree);
}
Adding New Scope Tags
import {
  Tree,
  updateJson,
  formatFiles,
  ProjectConfiguration,
  getProjects,
} from '@nx/devkit';

function getScopes(projectMap: Map<string, ProjectConfiguration>) {
  const projects: any[] = Array.from(projectMap.values());
  const allScopes: string[] = projects
    .map((project) =>
      project.tags.filter((tag: string) => tag.startsWith('scope:'))
    )
    .reduce((acc, tags) => [...acc, ...tags], [])
    .map((scope: string) => scope.slice(6));
  return Array.from(new Set(allScopes));
}

export default async function (tree: Tree) {
  const scopes = getScopes(getProjects(tree));
  updateJson(
    tree,
    'libs/internal-plugin/src/generators/util-lib/schema.json',
    (schemaJson) => {
      schemaJson.properties.directory['x-prompt'].items = scopes.map(
        (scope) => ({
          value: scope,
          label: scope,
        })
      );
      return schemaJson;
    }
  );
  await formatFiles(tree);
}
Final generator code
import {
  Tree,
  updateJson,
  formatFiles,
  ProjectConfiguration,
  getProjects,
} from '@nx/devkit';

export default async function (tree: Tree) {
  const scopes = getScopes(getProjects(tree));
  updateSchemaJson(tree, scopes);
  updateSchemaInterface(tree, scopes);
  await formatFiles(tree);
}

function getScopes(projectMap: Map<string, ProjectConfiguration>) {
  const projects: any[] = Array.from(projectMap.values());
  const allScopes: string[] = projects
    .map((project) =>
      project.tags.filter((tag: string) => tag.startsWith('scope:'))
    )
    .reduce((acc, tags) => [...acc, ...tags], [])
    .map((scope: string) => scope.slice(6));
  return Array.from(new Set(allScopes));
}

function updateSchemaJson(tree: Tree, scopes: string[]) {
  updateJson(
    tree,
    'libs/internal-plugin/src/generators/util-lib/schema.json',
    (schemaJson) => {
      schemaJson.properties.directory['x-prompt'].items = scopes.map(
        (scope) => ({
          value: scope,
          label: scope,
        })
      );
      return schemaJson;
    }
  );
}

function updateSchemaInterface(tree: Tree, scopes: string[]) {
  const joinScopes = scopes.map((s) => `'${s}'`).join(' | ');
  const interfaceDefinitionFilePath =
    'libs/internal-plugin/src/generators/util-lib/schema.d.ts';
  const newContent = `export interface UtilLibGeneratorSchema {
  name: string;
  directory: ${joinScopes};
}`;
  tree.write(interfaceDefinitionFilePath, newContent);
}
BONUS 1 SOLUTION
function addScopeIfMissing(host: Tree) {
  const projectMap = getProjects(host);
  Array.from(projectMap.keys()).forEach((projectName) => {
    const project = projectMap.get(projectName);
    if (!project.tags.some((tag) => tag.startsWith('scope:'))) {
      const scope = projectName.split('-')[0];
      project.tags.push(`scope:${scope}`);
      updateProjectConfiguration(host, projectName, project);
    }
  });
}
BONUS 2 SOLUTION
{
  "scripts": {
    "postinstall": "husky install",
    "pre-commit": "npx nx g @bg-hoard/internal-plugin:update-scope-schema"
  }
}
BONUS 3 SOLUTION: TESTING
import { readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { libraryGenerator } from '@nx/js/generators';
import { generatorGenerator, pluginGenerator } from '@nx/plugin/generators';
import { readFileSync } from 'fs';
import { join } from 'path';

import { Linter } from '@nx/eslint';
import generator from './generator';

describe('update-scope-schema generator', () => {
  let appTree: Tree;

  beforeEach(async () => {
    appTree = createTreeWithEmptyWorkspace();
    await addUtilLibProject(appTree);
    await libraryGenerator(appTree, { name: 'foo', tags: 'scope:foo' });
    await libraryGenerator(appTree, { name: 'bar', tags: 'scope:bar' });
  });

  it('should adjust the util-lib generator based on existing projects', async () => {
    await generator(appTree);
    const schemaJson = readJson(
      appTree,
      'libs/internal-plugin/src/generators/util-lib/schema.json'
    );
    expect(schemaJson.properties.directory['x-prompt'].items).toEqual([
      {
        value: 'foo',
        label: 'foo',
      },
      {
        value: 'bar',
        label: 'bar',
      },
    ]);
    const schemaInterface = appTree.read(
      'libs/internal-plugin/src/generators/util-lib/schema.d.ts',
      'utf-8'
    );
    expect(schemaInterface).toContain(`export interface UtilLibGeneratorSchema {
  name: string;
  directory: 'foo' | 'bar';
}`);
  });
});

async function addUtilLibProject(tree: Tree) {
  await pluginGenerator(tree, {
    name: 'internal-plugin',
    directory: 'libs/internal-plugin'
    skipTsConfig: false,
    unitTestRunner: 'jest',
    linter: Linter.EsLint,
    compiler: 'tsc',
    skipFormat: false,
    skipLintChecks: false,
    minimal: true,
  });
  await generatorGenerator(tree, {
    name: 'util-lib',
    directory: 'libs/internal-plugin/src/generators/util-lib',
    unitTestRunner: 'jest',
  });
  const filesToCopy = [
    '../util-lib/generator.ts',
    '../util-lib/schema.json',
    '../util-lib/schema.d.ts',
  ];
  for (const file of filesToCopy) {
    tree.write(
      `libs/internal-plugin/src/generators/util-lib/${file}`,
      readFileSync(join(__dirname, file))
    );
  }
}