diff --git a/.changeset/small-readers-check.md b/.changeset/small-readers-check.md new file mode 100644 index 00000000000..7d3dc296122 --- /dev/null +++ b/.changeset/small-readers-check.md @@ -0,0 +1,7 @@ +--- +'@aws-amplify/integration-tests': patch +'create-amplify': patch +--- + +- Support yarn, pnpm via env var +- Add e2e test against yarn, pnpm diff --git a/.github/workflows/poc-e2e-flow-test.yml b/.github/workflows/poc-e2e-flow-test.yml index 9f1870dc93d..63fd37e8ea8 100644 --- a/.github/workflows/poc-e2e-flow-test.yml +++ b/.github/workflows/poc-e2e-flow-test.yml @@ -5,53 +5,70 @@ name: 'poc-e2e-flow-test' on: # TODO: need to change the trigger push: branches: - - poc/e2e-test + - poc/e2e-create-amplify jobs: - create-amplify-project: + install: strategy: - fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] - node-version: [18] - pkg-manager: [npm, yarn, yarn-stable, pnpm] + os: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.os }} + steps: + - name: Checkout aws-amplify/amplify-cli repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Setup Node.js + uses: ./.github/actions/setup_node + - name: Install or Restore Cache + uses: ./.github/actions/install_with_cache + build: + runs-on: ubuntu-latest + needs: + - install + steps: + - name: Checkout aws-amplify/amplify-cli repo + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Setup Node.js + uses: ./.github/actions/setup_node + - name: Build or Restore Build Cache + uses: ./.github/actions/build_with_cache + run_e2e_tests: + strategy: + # will finish running other test matrices even if one fails + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + pkg-manager: [npm, yarn, pnpm] + node-version: [20] + include: + - os: ubuntu-latest + pkg-manager: yarn-stable + node-version: 19 # TODO: use Node 20 once https://github.com/yarnpkg/berry/pull/5961 is released + - os: macos-latest + pkg-manager: yarn-stable + node-version: 19 # TODO: use Node 20 once https://github.com/yarnpkg/berry/pull/5961 is released + - os: windows-latest + pkg-manager: yarn-stable + node-version: 19 # TODO: use Node 20 once https://github.com/yarnpkg/berry/pull/5961 is released env: - PACKAGE_MANAGER_EXECUTABLE: ${{ matrix.pkg-manager }} # TODO: remove PACKAGE_MANAGER_EXECUTABLE once CLI is able to getPackageManager(). + ACKAGE_MANAGER_EXECUTABLE: ${{ matrix.pkg-manager }} # TODO: remove PACKAGE_MANAGER_EXECUTABLE once CLI is able to getPackageManager(). + runs-on: ${{ matrix.os }} + timeout-minutes: 60 + needs: + - build + permissions: + # these permissions are required for the configure-aws-credentials action to get a JWT from GitHub + id-token: write + contents: read steps: - name: Checkout aws-amplify/amplify-cli repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1. TODO: try only fetch .github/workflow + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Setup Node.js - uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 #4.0.0 + uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # version 3.8.1 with: node-version: ${{ matrix.node-version }} - cache: npm - - name: Local Publish create-amplify - shell: bash - run: npm run install:local && npm run build && npm run vend - - name: ${{matrix.pkg-manager}}-create-amplify-project - if: matrix.pkg-manager != 'yarn-stable' - shell: bash - run: - | # TODO: last step `create amplify` should be replaced by `run e2e` - mkdir -p /tmp/amplify-project; cd /tmp/amplify-project - npm install -g ${{matrix.pkg-manager}} - echo "$(${{matrix.pkg-manager}}) config set registry http://localhost:4873" - ${{matrix.pkg-manager}} config set registry http://localhost:4873 - echo "$(${{matrix.pkg-manager}}) config get registry" - ${{matrix.pkg-manager}} config get registry - ${{matrix.pkg-manager}} create amplify --yes - - - name: yarn-stable-create-amplify-project - if: matrix.pkg-manager == 'yarn-stable' + - name: Restore Build Cache + uses: ./.github/actions/restore_build_cache + - name: Run E2E tests with ${{ matrix.pkg-manager }} shell: bash - run: - | # TODO: last step `create amplify` should be replaced by `run e2e` - mkdir -p /tmp/amplify-project; cd /tmp/amplify-project - corepack enable - echo "yarn set version stable" - yarn set version stable - echo "yarn version $(yarn --version)" - yarn config set unsafeHttpWhitelist localhost - yarn config set npmRegistryServer http://localhost:4873 - PACKAGE_MANAGER_EXECUTABLE=yarn yarn create amplify --yes + run: | + PACKAGE_MANAGER_EXECUTABLE=${{matrix.pkg-manager}} npm run test:dir packages/integration-tests/src/test-e2e/create_amplify.test.ts diff --git a/packages/create-amplify/src/npm_package_manager_controller.ts b/packages/create-amplify/src/npm_package_manager_controller.ts index e5cc8ce4361..546c581de44 100644 --- a/packages/create-amplify/src/npm_package_manager_controller.ts +++ b/packages/create-amplify/src/npm_package_manager_controller.ts @@ -16,8 +16,11 @@ export class NpmPackageManagerController implements PackageManagerController { private readonly projectRoot: string, private readonly execa = _execa ) {} - private readonly executableName = - process.env.PACKAGE_MANAGER_EXECUTABLE || 'npm'; // TODO: replace `process.env.PACKAGE_MANAGER_EXECUTABLE` with `getPackageManagerName()` once the test infra is ready. + private readonly executableName = !process.env.PACKAGE_MANAGER_EXECUTABLE + ? 'npm' + : process.env.PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : process.env.PACKAGE_MANAGER_EXECUTABLE; // TODO: replace `process.env.PACKAGE_MANAGER_EXECUTABLE` with `getPackageManagerName()` once the test infra is ready. /** * Installs the given package names as devDependencies @@ -34,9 +37,18 @@ export class NpmPackageManagerController implements PackageManagerController { } try { - await executeWithDebugLogger(this.projectRoot, 'npm', args, this.execa); + await executeWithDebugLogger( + this.projectRoot, + this.executableName, + args, + this.execa + ); } catch { - throw new Error(`\`npm ${args.join(' ')}\` did not exit successfully.`); + throw new Error( + `\`${this.executableName} ${args.join( + ' ' + )}\` did not exit successfully.` + ); } }; } diff --git a/packages/create-amplify/src/tsconfig_initializer.ts b/packages/create-amplify/src/tsconfig_initializer.ts index b17c4c27440..d6aba8a5b91 100644 --- a/packages/create-amplify/src/tsconfig_initializer.ts +++ b/packages/create-amplify/src/tsconfig_initializer.ts @@ -18,8 +18,11 @@ export class TsConfigInitializer { private readonly existsSync = _existsSync, private readonly execa = _execa ) {} - private readonly executableName = - process.env.PACKAGE_MANAGER_EXECUTABLE || 'npx'; // TODO: replace `process.env.PACKAGE_MANAGER_EXECUTABLE` with `getPackageManagerName()` once the test infra is ready. + private readonly executableName = !process.env.PACKAGE_MANAGER_EXECUTABLE + ? 'npx' + : process.env.PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : process.env.PACKAGE_MANAGER_EXECUTABLE; // TODO: replace `process.env.PACKAGE_MANAGER_EXECUTABLE` with `getPackageManagerName()` once the test infra is ready. /** * If tsconfig.json already exists, this is a noop. Otherwise, `npx tsc --init` is executed to create a tsconfig.json file diff --git a/packages/integration-tests/src/test-e2e/create_amplify.test.ts b/packages/integration-tests/src/test-e2e/create_amplify.test.ts index e21648a331a..3f18df07958 100644 --- a/packages/integration-tests/src/test-e2e/create_amplify.test.ts +++ b/packages/integration-tests/src/test-e2e/create_amplify.test.ts @@ -7,19 +7,87 @@ import { after, afterEach, before, beforeEach, describe, it } from 'node:test'; import assert from 'assert'; import { glob } from 'glob'; -void describe('create-amplify script', () => { - before(async () => { - // start a local npm proxy and publish the current codebase to the proxy - await execa('npm', ['run', 'clean:npm-proxy'], { stdio: 'inherit' }); - await execa('npm', ['run', 'vend'], { stdio: 'inherit' }); +type PackageManagerExecutable = 'npm' | 'yarn' | 'yarn-stable' | 'pnpm'; - // nuke the npx cache to ensure we are installing packages from the npm proxy +const packageManagerSetup = async ( + packageManagerExecutable: PackageManagerExecutable, + dir: string +) => { + const execaOptions = { + cwd: dir, + stdio: 'inherit' as const, + }; + + if (packageManagerExecutable === 'npm') { const { stdout } = await execa('npm', ['config', 'get', 'cache']); const npxCacheLocation = path.join(stdout.toString().trim(), '_npx'); if (existsSync(npxCacheLocation)) { await fs.rm(npxCacheLocation, { recursive: true }); } + } else if (packageManagerExecutable.startsWith('yarn')) { + if (packageManagerExecutable === 'yarn-stable') { + await execa('yarn', ['set', 'version', 'stable'], execaOptions); + await execa('npm', ['pkg', 'set', 'type=module'], execaOptions); // `npm pkg set type="module"` only run when package.json does not exist, so we need to run it manually here + + await execa( + 'yarn', + ['config', 'set', 'npmRegistryServer', 'http://localhost:4873'], + execaOptions + ); + await execa( + 'yarn', + ['config', 'set', 'unsafeHttpWhitelist', 'localhost'], + execaOptions + ); + } else { + await execa( + packageManagerExecutable, + ['config', 'set', 'registry', 'http://localhost:4873'], + execaOptions + ); + await execa( + packageManagerExecutable, + ['config', 'get', 'registry'], + execaOptions + ); + } + await execa( + packageManagerExecutable === 'yarn-stable' + ? 'yarn' + : packageManagerExecutable, + ['cache', 'clean'], + execaOptions + ); + } else if (packageManagerExecutable === 'pnpm') { + await execa(packageManagerExecutable, ['--version']); + await execa(packageManagerExecutable, [ + 'config', + 'set', + 'registry', + 'http://localhost:4873', + ]); + await execa(packageManagerExecutable, ['config', 'get', 'registry']); + + await execa(packageManagerExecutable, ['store', 'clear']); + } +}; + +void describe('create-amplify script', () => { + const { PACKAGE_MANAGER_EXECUTABLE = 'npm' } = process.env; + + before(async () => { + // start a local npm proxy and publish the current codebase to the proxy + await execa('npm', ['run', 'clean:npm-proxy'], { stdio: 'inherit' }); + await execa('npm', ['run', 'vend'], { stdio: 'inherit' }); + + if (PACKAGE_MANAGER_EXECUTABLE.startsWith('yarn')) { + await execa('npm', ['install', '-g', 'yarn'], { stdio: 'inherit' }); + } else if (PACKAGE_MANAGER_EXECUTABLE === 'pnpm') { + await execa('npm', ['install', '-g', PACKAGE_MANAGER_EXECUTABLE], { + stdio: 'inherit', + }); + } }); after(async () => { @@ -28,8 +96,30 @@ void describe('create-amplify script', () => { }); let tempDir: string; + beforeEach(async () => { - tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'test-create-amplify')); + const tempDirPath = await fs.mkdtemp( + path.join(os.tmpdir(), 'test-create-amplify') + ); + tempDir = path.join(tempDirPath); + + const { stdout } = await execa( + PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, + ['--version'], + { + cwd: tempDir, + } + ); + + console.log(`Using ${PACKAGE_MANAGER_EXECUTABLE} version ${stdout}`); + + // nuke the npx cache to ensure we are installing packages from the npm proxy + await packageManagerSetup( + PACKAGE_MANAGER_EXECUTABLE as PackageManagerExecutable, + tempDir + ); }); afterEach(async () => { @@ -55,10 +145,17 @@ void describe('create-amplify script', () => { ); } - await execa('npm', ['create', 'amplify', '--yes'], { - cwd: tempDir, - stdio: 'inherit', - }); + await execa( + PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, + ['create', 'amplify', '--yes'], + { + cwd: tempDir, + stdio: 'inherit', + } + ); + const packageJsonPath = path.resolve(tempDir, 'package.json'); const packageJsonObject = JSON.parse( await fs.readFile(packageJsonPath, 'utf-8') @@ -121,7 +218,11 @@ void describe('create-amplify script', () => { // assert that project compiles successfully await execa( - 'npx', + PACKAGE_MANAGER_EXECUTABLE === 'npm' + ? 'npx' + : PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, [ 'tsc', '--noEmit', @@ -140,9 +241,50 @@ void describe('create-amplify script', () => { } ); + if (PACKAGE_MANAGER_EXECUTABLE.startsWith('yarn')) { + await execa( + PACKAGE_MANAGER_EXECUTABLE === 'npm' + ? 'npx' + : PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, + ['add', 'aws-cdk', 'aws-cdk-lib', 'constructs'], + { + cwd: tempDir, + stdio: 'inherit', + } + ); + if (PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable') { + await execa( + 'yarn', + [ + 'add', + '-D', + 'tsx', + 'graphql', + 'pluralize', + 'zod', + '@aws-amplify/platform-core', + ], + { + cwd: tempDir, + stdio: 'inherit', + } + ); + + await execa('node', ['--version'], { + cwd: tempDir, + }); + } + } + // assert that project synthesizes successfully await execa( - 'npx', + PACKAGE_MANAGER_EXECUTABLE === 'npm' + ? 'npx' + : PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, [ 'cdk', 'synth', @@ -153,7 +295,13 @@ void describe('create-amplify script', () => { '--context', `amplify-backend-type=sandbox`, '--app', - "'npx tsx amplify/backend.ts'", + `'${ + PACKAGE_MANAGER_EXECUTABLE === 'npm' + ? 'npx' + : PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE + } tsx amplify/backend.ts'`, '--quiet', ], { @@ -168,11 +316,17 @@ void describe('create-amplify script', () => { const amplifyDirPath = path.join(tempDir, 'amplify'); await fs.mkdir(amplifyDirPath, { recursive: true }); - const result = await execa('npm', ['create', 'amplify', '--yes'], { - cwd: tempDir, - stdio: 'pipe', - reject: false, - }); + const result = await execa( + PACKAGE_MANAGER_EXECUTABLE === 'yarn-stable' + ? 'yarn' + : PACKAGE_MANAGER_EXECUTABLE, + ['create', 'amplify', '--yes'], + { + cwd: tempDir, + stdio: 'pipe', + reject: false, + } + ); assert.equal(result.exitCode, 1); assert.ok( result.stderr