Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions .github/workflows/test-matrix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Test Angular Matrix

on:
pull_request:

jobs:
test-matrix:
name: Angular ${{ matrix.angular }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- angular: 15
node: 18
- angular: 16
node: 18
- angular: 17
node: 18
- angular: 18
node: 20
- angular: 19
node: 20
- angular: 20
node: 20
- angular: 21
node: 22

steps:
- uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 9

- name: Install dependencies
run: pnpm install

- name: Generate project version
run: pnpm generate:version

- name: Run Matrix Check for Angular ${{ matrix.angular }}
run: pnpm run test:matrix -- --version=${{ matrix.angular }}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
/tmp
/out-tsc
/bazel-out
.pnpm-store
projects/*/dist
projects/*/out-tsc

# Node
node_modules
npm-debug.log
yarn-error.log
test-logs

# IDEs and editors
.idea/
Expand Down
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18
192 changes: 192 additions & 0 deletions bin/test-matrix.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#!/usr/bin/env node

import fs from 'fs'
import path from 'path'
import os from 'os'
import { angularMetadata } from './utils/angularMetadata.mjs'
import { logErrorSummary, setupLogDir, executeCommand, updateJsonFile, copyRecursive } from './utils/helpers.mjs'

const LIB_NAME = 'fingerprintjs-pro-angular'
const LOG_DIR = path.join(process.cwd(), 'test-logs')

process.env.NG_CLI_ANALYTICS = 'false'

async function testVersion(version) {
const meta = angularMetadata[version]
if (!meta) {
const errorMsg = `No metadata found for version ${version}`
console.error(errorMsg)
const logFile = path.join(LOG_DIR, `angular-${version}.log`)
fs.writeFileSync(logFile, errorMsg)
return 1
}

const logFile = path.join(LOG_DIR, `angular-${version}.log`)
const logStream = fs.createWriteStream(logFile)
const log = (data) => logStream.write(data)

const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'angular-test-'))
const workspaceDir = path.join(tempDir, 'test-workspace')

try {
log(`Angular ${version}: Starting check...\n`)
console.log(`Angular ${version}: Starting check...`)

await executeCommand(
'npx',
[
'-y',
`@angular/cli@${version}`,
'new',
'test-workspace',
'--create-application=false',
'--skip-git',
'--skip-install',
'--defaults',
'--package-manager=pnpm',
],
{ cwd: tempDir, env: { ...process.env } },
log
)

await executeCommand(
'npx',
['-y', `@angular/cli@${version}`, 'generate', 'library', LIB_NAME, '--skip-install'],
{ cwd: workspaceDir, env: { ...process.env } },
log
)

const angularVersionTag = version > 15 ? `v${version}-lts` : `^${version}`

const packagesToInstall = [
`@angular/animations@${angularVersionTag}`,
`@angular/common@${angularVersionTag}`,
`@angular/compiler@${angularVersionTag}`,
`@angular/core@${angularVersionTag}`,
`@angular/forms@${angularVersionTag}`,
`@angular/platform-browser@${angularVersionTag}`,
`@angular/platform-browser-dynamic@${angularVersionTag}`,
`@angular/router@${angularVersionTag}`,
`zone.js@${meta.zone}`,
'@fingerprint/agent',
]

const jestVersion = meta.jest
const typesJestVersion = meta.typesJest || jestVersion

const devPackagesToInstall = [
`@angular/cli@${angularVersionTag}`,
`@angular/compiler-cli@${angularVersionTag}`,
`@angular-devkit/build-angular@${angularVersionTag}`,
`ng-packagr@^${version}`,
`typescript@${meta.typescript}`,
`jest-preset-angular@${meta.jpa}`,
`jest@${jestVersion}`,
`jest-environment-jsdom@${jestVersion}`,
`@types/jest@${typesJestVersion}`,
'@types/node',
]

const commonOptions = ['--config.strict-peer-dependencies=false', '--no-lockfile', '--config.fund=false']

await executeCommand(
'pnpm',
['add', ...packagesToInstall, ...devPackagesToInstall, ...commonOptions],
{ cwd: workspaceDir, env: { ...process.env } },
log
)

const libDestDir = path.join(workspaceDir, 'projects', LIB_NAME, 'src', 'lib')
fs.mkdirSync(libDestDir, { recursive: true })
copyRecursive(path.join(process.cwd(), 'projects', LIB_NAME, 'src', 'lib'), libDestDir)

fs.copyFileSync(
path.join(process.cwd(), 'projects', LIB_NAME, 'src', 'public-api.ts'),
path.join(workspaceDir, 'projects', LIB_NAME, 'src', 'public-api.ts')
)

const rootFiles = ['jest.config.js', 'tsconfig.json', 'tsconfig.spec.json', 'test.ts']
for (const file of rootFiles) {
const src = path.join(process.cwd(), file)
if (fs.existsSync(src)) {
fs.copyFileSync(src, path.join(workspaceDir, file))
}
}

const projectDir = path.join(workspaceDir, 'projects', LIB_NAME)
const projectFiles = [
'ng-package.json',
'package.json',
'tsconfig.lib.json',
'tsconfig.lib.prod.json',
'tsconfig.spec.json',
]
for (const file of projectFiles) {
fs.copyFileSync(path.join(process.cwd(), 'projects', LIB_NAME, file), path.join(projectDir, file))
}

const tsconfigFiles = [
path.join(workspaceDir, 'tsconfig.json'),
path.join(workspaceDir, 'projects', LIB_NAME, 'tsconfig.spec.json'),
]
for (const file of tsconfigFiles) {
if (fs.existsSync(file)) {
updateJsonFile(file, (json) => {
if (!json.compilerOptions) {
json.compilerOptions = {}
}
json.compilerOptions.skipLibCheck = true
json.compilerOptions.esModuleInterop = true
if (parseInt(version) >= 21) {
json.compilerOptions.moduleResolution = 'bundler'
}
})
}
}

await executeCommand(
'./node_modules/.bin/ng',
['build', LIB_NAME],
{ cwd: workspaceDir, env: { ...process.env } },
log
)

await executeCommand('./node_modules/.bin/jest', [], { cwd: workspaceDir, env: { ...process.env } }, log)

console.log(`Angular ${version}: PASSED`)
return 0
} catch (err) {
log(err.stack || err.message)
logErrorSummary(logStream, logFile, err, version)
return 1
} finally {
logStream.end()
fs.rmSync(tempDir, { recursive: true, force: true })
}
}

setupLogDir(LOG_DIR)

const args = process.argv.slice(2)
const versionArgs = []
for (let i = 0; i < args.length; i++) {
if (args[i].startsWith('--version=')) {
versionArgs.push(args[i].split('=')[1])
} else if (args[i] === '--version' && i + 1 < args.length) {
versionArgs.push(args[++i])
}
}

const versionsToTest = versionArgs.length > 0 ? versionArgs : Object.keys(angularMetadata)

console.log(`Starting matrix tests for: ${versionsToTest.join(', ')}`)

const results = await Promise.all(versionsToTest.map((version) => testVersion(version)))
const failed = results.some((code) => code !== 0)

if (failed) {
process.exit(1)
} else {
console.log('Success: All versions passed')
process.exit(0)
}
9 changes: 9 additions & 0 deletions bin/utils/angularMetadata.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const angularMetadata = {
15: { node: 18, typescript: '~4.9.5', zone: '~0.11.4', jpa: '^13.1.4', jest: '^29.0.0' },
16: { node: 18, typescript: '~5.1.6', zone: '~0.13.3', jpa: '^13.1.4', jest: '^29.0.0' },
17: { node: 18, typescript: '~5.2.2', zone: '~0.14.4', jpa: '^14.1.1', jest: '^29.0.0' },
18: { node: 20, typescript: '~5.4.5', zone: '~0.14.4', jpa: '^14.1.1', jest: '^29.0.0' },
19: { node: 20, typescript: '~5.6.3', zone: '~0.15.0', jpa: '^14.4.2', jest: '^29.0.0' },
20: { node: 20, typescript: '~5.8.3', zone: '~0.15.0', jpa: '^15.0.0', jest: '^30.2.0', typesJest: '^30.0.0' },
21: { node: 22, typescript: '~5.9.2', zone: '~0.16.0', jpa: '^17.0.0', jest: '^30.2.0', typesJest: '^30.0.0' },
}
67 changes: 67 additions & 0 deletions bin/utils/helpers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs'
import path from 'path'
import { spawn } from 'child_process'

export function setupLogDir(logDir) {
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
} else {
fs.readdirSync(logDir).forEach((file) => {
fs.unlinkSync(path.join(logDir, file))
})
}
}

export function logErrorSummary(logStream, logFile, err, version) {
console.log(`Angular ${version}: FAILED (see ${logFile})`)

try {
const logContent = fs.readFileSync(logFile, 'utf8')
const lines = logContent.split('\n')
console.log(lines.slice(-15).join('\n'))
} catch (e) {
// ignore
}
}

export function executeCommand(cmd, args, opts, log) {
return new Promise((resolve, reject) => {
const child = spawn(cmd, args, opts)
if (log) {
child.stdout.on('data', log)
child.stderr.on('data', log)
}
child.on('close', (code) => {
if (code === 0) {
resolve()
} else {
reject(new Error(`Command failed with code ${code}: ${cmd} ${args.join(' ')}`))
}
})
})
}

export function updateJsonFile(filePath, updater) {
if (!fs.existsSync(filePath)) {
return
}
const content = fs.readFileSync(filePath, 'utf8')
const cleanContent = content.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, '$1')
const json = JSON.parse(cleanContent)
updater(json)
fs.writeFileSync(filePath, JSON.stringify(json, null, 2), 'utf8')
}

export function copyRecursive(src, dest) {
const isDirectory = fs.existsSync(src) && fs.statSync(src).isDirectory()
if (isDirectory) {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true })
}
fs.readdirSync(src).forEach((childItemName) => {
copyRecursive(path.join(src, childItemName), path.join(dest, childItemName))
})
} else {
fs.copyFileSync(src, dest)
}
}
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "fingerprintjs-pro-angular-demo",
"version": "0.0.0",
"engines": {
"node": ">=18.0.0"
},
Comment thread
Copilot marked this conversation as resolved.
"scripts": {
"prepare": "husky install",
"ng": "ng",
Expand All @@ -13,11 +16,12 @@
"build:ssr": "ng build && ng run fingerprintjs-pro-angular-demo:server",
"prerender": "ng run fingerprintjs-pro-angular-demo:prerender",
"generate:version": "node bin/generate-version.js",
"lint": "eslint --ext .js,.ts --ignore-path .gitignore --max-warnings 0 .",
"lint:fix": "eslint --ext .js,.ts --ignore-path .gitignore --max-warnings 0 --fix .",
"lint": "eslint --ext .js,.mjs,.ts --ignore-path .gitignore --max-warnings 0 .",
"lint:fix": "eslint --ext .js,.mjs,.ts --ignore-path .gitignore --max-warnings 0 --fix .",
"test:dts": "tsc -p tsconfig.test-dts.json",
"test": "jest",
"test:coverage": "jest --coverage",
"test:matrix": "node bin/test-matrix.mjs",
"docs": "typedoc projects/fingerprintjs-pro-angular/src/public-api.ts --out docs",
"changeset:publish": "HUSKY=0 pnpm build && changeset publish",
"changeset:version": "changeset version"
Expand Down
Loading