diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 39851ee..986528c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -4,7 +4,7 @@ ## Local Package Publishing -When iterating on the `@nrwl/nx-workshop-e2e` package, it's often helpful to test out a published +When iterating on the `@nrwl/nx-react-workshop-e2e` package, it's often helpful to test out a published version of the package locally. This can be done by starting the local registry, `verdaccio`, and using the `nx release` command. To start the local registry, run the `local-registry` target for the root workspace project: @@ -13,7 +13,7 @@ To start the local registry, run the `local-registry` target for the root worksp nx local-registry ``` -In another terminal, you can then trigger deployment to that registry by running the `release` target for the `nx-workshop-e2e` project: +In another terminal, you can then trigger deployment to that registry by running the `release` target for the `nx-react-workshop-e2e` project: ```bash nx release version prerelease && nx release publish @@ -22,7 +22,7 @@ nx release version prerelease && nx release publish nx release-dev nx-react-workshop ``` -Once your dev version has been published, you can then update the `nx-workshop-e2e` package in your consuming project to the version you just published. +Once your dev version has been published, you can then update the `nx-react-workshop-e2e` package in your consuming project to the version you just published. Since verdaccio configures itself in the `~/.npmrc` file, you can simply install using your standard package manager commands: @@ -31,3 +31,38 @@ npm add --save-dev @nrwl/nx-react-workshop@latest # or alternatively with yarn yarn add -D @nrwl/nx-react-workshop@latest ``` + +## Updating workshop content + +The workshop content is stored in the `docs` directory. The content is written in markdown and is organized by lab. Each lab has its own directory with a `LAB.md` and `SOLUTION.md`. + +When updating: + +1. Use the migration generator to jump to the lab before the being updating + + ```sh + nx generate @nrwl/nx-react-workshop:complete-labs --from=1 --to= + nx migrate --run-migrations=migrations.json --verbose + ``` + +2. Manually run through the lab and verify: + + - The instructions in LAB.md and language reflect the current state of the nx + - The contents of SOLUTION.md match 1:1 with the steps in the lab + - Any screenshots or prompt examples accurately reflect the output from the lab + +3. Verify the completion migration reflects the steps outlined in the solution + + ```sh + # After manually following the lab in step #2 + + # git commit + git add . && git commit -m 'manually run through completion steps' + + # reset repo state and migrate to end of lab: + nx generate @nrwl/nx-react-workshop:complete-labs --from=1 --to= + nx migrate --run-migrations=migrations.json --verbose + + # Verify there are no differences + git status # should show no changes + ``` diff --git a/apps/nx-workshop-e2e/tests/nx-workshop.spec.ts b/apps/nx-workshop-e2e/tests/nx-workshop.spec.ts index 6b43e12..331c133 100644 --- a/apps/nx-workshop-e2e/tests/nx-workshop.spec.ts +++ b/apps/nx-workshop-e2e/tests/nx-workshop.spec.ts @@ -1,31 +1,52 @@ import { execSync, ExecSyncOptionsWithStringEncoding } from 'child_process'; import { join, dirname } from 'path'; import { mkdirSync, rmSync } from 'fs'; +import { copy } from 'fs-extra'; +import { compareSync, Result as DirCompareResult } from 'dir-compare'; +import { stripIndents } from '@nx/devkit'; // TODO: Update to 22 once all labs are updated/fixed // const LAB_COUNT = 22; -const LAB_COUNT = 3; -const LABS_TO_TEST = Array.from({ length: LAB_COUNT }, (_, i) => ({ - labNumber: i + 1, +const LAB_COUNT = 2; + +// We iterate from lab 2 to LAB_COUNT since lab 1 is a special case that resets the project +// which we test explicitly at the end of the test suite +const LABS_TO_TEST = Array.from({ length: LAB_COUNT - 1 }, (_, i) => ({ + labNumber: i + 2, })); describe('nx-react-workshop', () => { let projectDirectory: string; + let emptyProjectDirectory: string; - beforeAll(() => { + beforeAll(async () => { projectDirectory = createTestProject(); // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nrwl/nx-react-workshop@e2e`, { + execSync(`npm install --save-dev @nrwl/nx-react-workshop@e2e`, { cwd: projectDirectory, stdio: 'inherit', env: process.env, }); - }); + + // A noop to verify generator works and setup project in an "initial state" + execSync(`nx generate @nrwl/nx-react-workshop:complete-labs --lab=0`, { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + }); + + // Make a copy of the initial project state so we can compare against it later + emptyProjectDirectory = join(process.cwd(), 'tmp', 'empty-project'); + await copy(projectDirectory, emptyProjectDirectory, { + filter: (src) => !src.includes('node_modules'), + }); + }, 120000); afterAll(() => { // Cleanup the test project + rmSync(emptyProjectDirectory, { recursive: true, force: true }); try { rmSync(projectDirectory, { recursive: true, force: true }); } catch { @@ -66,10 +87,41 @@ describe('nx-react-workshop', () => { ); } ); + + // NOTE: this test assumes that the current test project is in the final lab completed state + it('complete-lab-1 migration should match an empty create-nx-workspace', () => { + // Reset the test project to it's initial state using the "complete-lab-1" migration + runNxCommand( + `generate @nrwl/nx-react-workshop:complete-labs --lab=1 --option=${option}`, + projectDirectory + ); + runNxCommand( + 'migrate --run-migrations=migrations.json --verbose', + projectDirectory + ); + + const result = compareSync(projectDirectory, emptyProjectDirectory, { + excludeFilter: [ + '*.env', + '.git', + '.DS_Store', + '.nx', + '.vscode', + 'dist', + 'node_modules', + 'tmp', + ].join(','), + }); + + if (!result.same) { + logProjectDifferences(result); + } + + expect(result.same).toBeTruthy(); + }); }); - // TODO: fix this as a part of updating lab 2 - it.todo('should support migrating from one version to another'); /*, () => { + it('should support migrating from one version to another', () => { runNxCommand( `generate @nrwl/nx-react-workshop:complete-labs --from=1 --to=${LAB_COUNT}`, projectDirectory @@ -83,7 +135,7 @@ describe('nx-react-workshop', () => { projectDirectory ); runNxCommand('run-many --target=lint --parallel=false', projectDirectory); - }); */ + }); }); }); @@ -91,8 +143,7 @@ describe('nx-react-workshop', () => { * Creates a test project with create-nx-workspace and installs the plugin * @returns The directory where the test project was created */ -function createTestProject() { - const projectName = 'test-project'; +function createTestProject(projectName = 'test-project') { const projectDirectory = join(process.cwd(), 'tmp', projectName); // Ensure projectDirectory is empty @@ -122,3 +173,30 @@ function runNxCommand(command: string, projectDirectory: string): string { return execSync(`npx nx ${command}`, execSyncOptions); } + +function logProjectDifferences(result: DirCompareResult) { + console.error( + stripIndents` + Empty project and reset project do not match: + %s + `, + result.diffSet + .filter(({ state }) => state !== 'equal') + .map((dif) => { + switch (dif.state) { + case 'distinct': + return ` ${dif.name1}: expected migration to reset this files content`; + + case 'left': + return ` ${dif.name1}: expected migration to delete this path`; + + case 'right': + return ` ${dif.name1} expected migration to retain this path`; + + default: + throw new Error(`Unexpected diff state: ${dif.state}`); + } + }) + .join('\n') + ); +} diff --git a/docs/assets/lab1_directory-structure.png b/docs/assets/lab1_directory-structure.png index 539492b..ad79ca8 100644 Binary files a/docs/assets/lab1_directory-structure.png and b/docs/assets/lab1_directory-structure.png differ diff --git a/docs/lab1/LAB.md b/docs/lab1/LAB.md index 2227b96..15aaf15 100644 --- a/docs/lab1/LAB.md +++ b/docs/lab1/LAB.md @@ -10,19 +10,19 @@
File structure - lab7 file structure + lab7 file structure
## 🏋️‍♀️ Steps: 1. Generate an empty Nx workspace for a fictional company called "The Board Game Hoard" -
2. The workspace name should be `bg-hoard` -
-3. Make sure you select `None` as Stack (we will create apps manually), an `Integrated` workspace layout and `No to NxCloud` when asked -
+3. When prompted, select: + - `None` as Stack (we will create apps manually) + - An `Integrated` workspace layout + - And `skip` configuring a `CI Provider` and `Remote Caching` for now (we'll set those up in a later lab) --- diff --git a/docs/lab1/SOLUTION.md b/docs/lab1/SOLUTION.md index 8f1495a..f9d9b57 100644 --- a/docs/lab1/SOLUTION.md +++ b/docs/lab1/SOLUTION.md @@ -1,5 +1,5 @@ ##### To create a new Nx workspace: ```shell -npx create-nx-workspace bg-hoard --preset=empty --no-nx-cloud +npx create-nx-workspace bg-hoard --preset=apps --nx-cloud=skip ``` diff --git a/libs/nx-react-workshop/src/migrations/complete-lab-1/complete-lab-1.ts b/libs/nx-react-workshop/src/migrations/complete-lab-1/complete-lab-1.ts index 6326611..5b53d43 100644 --- a/libs/nx-react-workshop/src/migrations/complete-lab-1/complete-lab-1.ts +++ b/libs/nx-react-workshop/src/migrations/complete-lab-1/complete-lab-1.ts @@ -45,10 +45,21 @@ export default async function update(tree: Tree) { return json; }); + // Lab 2 + [ + '.eslintignore', + '.eslintrc.json', + 'jest.config.ts', + 'jest.preset.js', + ].forEach((file) => tree.delete(file)); + // Lab 13 tree.delete('tools/generators/util-lib'); + // Lab 14 tree.delete('tools/generators/update-scope-schema'); + tree.delete('.husky'); + // Lab 15 tree.delete('.github/workflows/ci.yml'); // Lab 19 @@ -64,11 +75,57 @@ export default async function update(tree: Tree) { tree.delete('tools/generators/add-deploy-target'); // Lab 21 tree.delete('.github/workflows/deploy.yml'); - // Set npmScope to bg-hoard + + // Reset nx.json to default updateJson(tree, 'nx.json', (json) => { - json.npmScope = 'bg-hoard'; - return json; + const newJson = { + $schema: './node_modules/nx/schemas/nx-schema.json', + namedInputs: { + default: ['{projectRoot}/**/*', 'sharedGlobals'], + production: ['default'], + sharedGlobals: [], + }, + }; + + // Keep nxCloudAccessToken if they connected their repo to Nx Cloud + if (json.nxCloudAccessToken) { + newJson['nxCloudAccessToken'] = json.nxCloudAccessToken; + } + + return newJson; }); + + // Reset package.json to default + updateJson( + tree, + 'package.json', + ({ name, version, license, dependencies, devDependencies }) => { + const packagesToKeep = [ + '@nx/js', + '@nx/workspace', + 'nx', + '@nrwl/nx-react-workshop', + ]; + + const filterDependencies = (d: Record = {}) => + Object.fromEntries( + Object.entries(d).filter(([packageName]) => + packagesToKeep.includes(packageName) + ) + ); + + return { + name, + version, + license, + scripts: {}, + private: true, + dependencies: filterDependencies(dependencies), + devDependencies: filterDependencies(devDependencies), + }; + } + ); + await formatFiles(tree); return () => { installPackagesTask(tree); diff --git a/package.json b/package.json index a83b08e..9b93e1c 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,12 @@ "@swc-node/register": "~1.9.1", "@swc/core": "1.5.7", "@swc/helpers": "~0.5.11", + "@types/fs-extra": "^11.0.4", "@types/jest": "29.5.12", "@types/node": "18.19.9", "@typescript-eslint/eslint-plugin": "7.18.0", "@typescript-eslint/parser": "7.18.0", + "dir-compare": "^5.0.0", "dotenv": "10.0.0", "eslint": "8.57.0", "eslint-config-prettier": "9.0.0", diff --git a/yarn.lock b/yarn.lock index 9a0a394..2af2ef9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6027,10 +6027,12 @@ __metadata: "@swc-node/register": "npm:~1.9.1" "@swc/core": "npm:1.5.7" "@swc/helpers": "npm:~0.5.11" + "@types/fs-extra": "npm:^11.0.4" "@types/jest": "npm:29.5.12" "@types/node": "npm:18.19.9" "@typescript-eslint/eslint-plugin": "npm:7.18.0" "@typescript-eslint/parser": "npm:7.18.0" + dir-compare: "npm:^5.0.0" dotenv: "npm:10.0.0" eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.0.0" @@ -7988,6 +7990,16 @@ __metadata: languageName: node linkType: hard +"@types/fs-extra@npm:^11.0.4": + version: 11.0.4 + resolution: "@types/fs-extra@npm:11.0.4" + dependencies: + "@types/jsonfile": "npm:*" + "@types/node": "npm:*" + checksum: 9e34f9b24ea464f3c0b18c3f8a82aefc36dc524cc720fc2b886e5465abc66486ff4e439ea3fb2c0acebf91f6d3f74e514f9983b1f02d4243706bdbb7511796ad + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.6 resolution: "@types/graceful-fs@npm:4.1.6" @@ -8087,6 +8099,15 @@ __metadata: languageName: node linkType: hard +"@types/jsonfile@npm:*": + version: 6.1.4 + resolution: "@types/jsonfile@npm:6.1.4" + dependencies: + "@types/node": "npm:*" + checksum: b12d068b021e4078f6ac4441353965769be87acf15326173e2aea9f3bf8ead41bd0ad29421df5bbeb0123ec3fc02eb0a734481d52903704a1454a1845896b9eb + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.175": version: 4.14.200 resolution: "@types/lodash@npm:4.14.200" @@ -12758,6 +12779,16 @@ __metadata: languageName: node linkType: hard +"dir-compare@npm:^5.0.0": + version: 5.0.0 + resolution: "dir-compare@npm:5.0.0" + dependencies: + minimatch: "npm:^3.0.5" + p-limit: "npm:^3.1.0 " + checksum: 4ed7d55ae10acdc301daa06b8ff6db48f011c4851d66a9c25288ac3bafeb90ed6714e2cc2f39ce5e9539005a3cea8fbf675a2359335366a59c9366c963cb66e5 + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -19998,7 +20029,7 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0, p-limit@npm:^3.1.0 ": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: