Skip to content

Commit

Permalink
fix(core): build problems in amazon q do not fail ci
Browse files Browse the repository at this point in the history
Problem:
- npm workspaces do not have a fail fast mechanism. See npm/rfcs#575. Meaning, if amazon q fails to build or tests fail, ci can still show as passed

Solution:
- Introduce a wrapper script that implements this fail fast behaviour
  • Loading branch information
jpinkney-aws committed Dec 11, 2024
1 parent 401f24e commit 524c8af
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 12 deletions.
28 changes: 25 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 11 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,20 @@
"preinstall": "cd src.gen/@amzn/codewhisperer-streaming && npm i && cd ../amazon-q-developer-streaming-client && npm i",
"postinstall": "npm run buildCustomLintPlugin && npm run postinstall -ws --if-present",
"buildCustomLintPlugin": "npm run build -w plugins/eslint-plugin-aws-toolkits",
"compile": "npm run compile -w packages/",
"testCompile": "npm run testCompile -w packages/ --if-present",
"test": "npm run test -w packages/ --if-present",
"testWeb": "npm run testWeb -w packages/ --if-present",
"testE2E": "npm run testE2E -w packages/ --if-present",
"testInteg": "npm run testInteg -w packages/ --if-present",
"package": "npm run package -w packages/toolkit -w packages/amazonq",
"compile": "ts-node ./scripts/run.ts compile",
"testCompile": "ts-node ./scripts/run.ts testCompile --if-present",
"test": "ts-node ./scripts/run.ts test --if-present",
"testWeb": "ts-node ./scripts/run.ts testWeb --if-present",
"testE2E": "ts-node ./scripts/run.ts testE2E --if-present",
"testInteg": "ts-node ./scripts/run.ts testInteg --if-present",
"package": "ts-node ./scripts/run.ts package -w packages/toolkit -w packages/amazonq",
"newChange": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
"createRelease": "echo 'Must specify subproject/workspace with -w packages/<subproject>' && false",
"lint": "npm run lint -w packages/ --if-present",
"lintfix": "eslint -c .eslintrc.js --ignore-path .gitignore --ignore-pattern '**/*.json' --ignore-pattern '**/*.gen.ts' --ignore-pattern '**/types/*.d.ts' --ignore-pattern '**/src/testFixtures/**' --ignore-pattern '**/resources/js/graphStateMachine.js' --fix --ext .ts packages plugins",
"clean": "npm run clean -w packages/ -w plugins/",
"reset": "npm run clean && ts-node ./scripts/clean.ts node_modules && npm install",
"generateNonCodeFiles": "npm run generateNonCodeFiles -w packages/ --if-present"
"generateNonCodeFiles": "ts-node ./scripts/run.ts generateNonCodeFiles --if-present"
},
"devDependencies": {
"@aws-toolkits/telemetry": "^1.0.284",
Expand All @@ -46,6 +46,7 @@
"@types/vscode": "^1.68.0",
"@types/vscode-webview": "^1.57.1",
"@types/webpack-env": "^1.18.5",
"@types/yargs": "^17.0.33",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"@vscode/codicons": "^0.0.33",
Expand All @@ -68,7 +69,8 @@
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.2",
"webpack-merge": "^5.10.0"
"webpack-merge": "^5.10.0",
"yargs": "^17.7.2"
},
"dependencies": {
"@types/node": "^22.7.5",
Expand Down
134 changes: 134 additions & 0 deletions scripts/run.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*!
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/**
* Script for compiling/building/testing npm workspaces that allows
* processes to fail fast, rather than continuing regardless of whether
* or not the previous build failed, which is how npm currently works
*
* See: https://github.com/npm/rfcs/issues/575 for the rfcs that tracks
* this for npm
*/

import { spawn } from 'child_process'
import * as fs from 'fs'
import * as path from 'path'
import * as yargs from 'yargs'

interface PackageJson {
scripts?: Record<string, string>
}

const argv = yargs
.usage('Usage: $0 <command> [options]')
.option('w', {
alias: 'workspace',
type: 'array',
description: 'Specify workspace packages to run the command against',
})
.option('if-present', {
type: 'boolean',
description: 'Run script only if it exists in the package.json',
default: false,
})
.demandCommand(1)
.help().argv as { w?: string[]; 'if-present': boolean; _: string[] }

const command = argv._[0]
const workspaces = argv.w
const ifPresent = argv['if-present']

function findPackageDirectories(): string[] {
const packages: string[] = []

const workspacePatterns = workspaces || ['packages/*']

for (const pattern of workspacePatterns) {
const baseDir = pattern.replace('/*', '')
if (fs.existsSync(baseDir)) {
const dirs = fs.readdirSync(baseDir)
for (const dir of dirs) {
const packagePath = path.join(baseDir, dir)
if (fs.statSync(packagePath).isDirectory() && fs.existsSync(path.join(packagePath, 'package.json'))) {
packages.push(packagePath)
}
}
}
}

return packages
}

/**
* Check if scriptName exists in packageDir's package.json
*/
function hasScript(packageDir: string, scriptName: string): boolean {
try {
const packageJson: PackageJson = JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf-8'))
return !!(packageJson.scripts && packageJson.scripts[scriptName])
} catch {
return false
}
}

/**
* Run npm run @param command in packageDir
*/
function runCommand(packageDir: string, command: string): Promise<void> {
return new Promise((resolve, reject) => {
console.log(`\nExecuting "${command}" in ${packageDir}`)

const proc = spawn('npm', ['run', command], {
stdio: 'inherit',
cwd: packageDir,
shell: true,
})

proc.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Command failed with exit code ${code}`))
}
})

proc.on('error', (err) => {
reject(err)
})
})
}

async function main() {
try {
let packages = findPackageDirectories()

// Filter packages if -w option is provided
if (workspaces && workspaces.length > 0) {
packages = workspaces
}

console.log(`Found packages: ${packages.join(', ')}`)

// Run command in each package
for (const pkg of packages) {
if (ifPresent && !hasScript(pkg, command)) {
console.log(`Skipping ${pkg} - script "${command}" not found`)
continue
}

try {
await runCommand(pkg, command)
} catch (error) {
console.error(`Error executing command in ${pkg}:`, error)
process.exit(1)
}
}
} catch (error) {
console.error('Error:', error)
process.exit(1)
}
}

main()

0 comments on commit 524c8af

Please sign in to comment.