Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ci gh action #8

Merged
merged 2 commits into from
Oct 14, 2024
Merged
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
36 changes: 36 additions & 0 deletions .github/actions/setup/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Setup action
description: Reusable action to setup node and install packages

runs:
using: "composite"
steps:

- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 20

- uses: pnpm/action-setup@v3
name: Install pnpm
id: pnpm-install
with:
version: 9
run_install: false

- name: Get pnpm store directory
id: pnpm-cache
run: |
echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
shell: bash

- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: pnpm-store-${{ hashFiles('**/package.json') }}
restore-keys: |
pnpm-store-

- name: Install dependencies
run: pnpm install
shell: bash
101 changes: 101 additions & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: build
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
types:
- closed
- opened
- reopened
- synchronize
jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
affectedPackages: ${{ steps.get-affected-packages.outputs.packages }}
if: "!(github.event_name == 'pull_request' && github.event.action == 'closed' && github.event.pull_request.merged == false)"
steps:

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: ./.github/actions/setup

- name: Get affected packages
id: get-affected-packages
run: echo "packages=$(pnpm exec foundation-get-affected-project-chunks test:js origin/${{github.base_ref}} 4)" >> $GITHUB_OUTPUT

- name: Compile source files
run: pnpm run build

- name: Lint affected source files
run: pnpm run lint --filter=[origin/${{github.base_ref}}]

- name: Get current git commit hash
id: get-git-commit-hash
run: |
echo "gitCommitHash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
id: ts-build-cache
with:
path: |
./packages/container/libraries/*
./packages/foundation/tools/*
key: ts-build-${{ steps.get-git-commit-hash.outputs.gitCommitHash }}
restore-keys: |
ts-build-${{ steps.get-git-commit-hash.outputs.gitCommitHash }}

test-package:
name: Test package
needs: build
runs-on: ubuntu-latest
if: ${{ needs.build.outputs.affectedPackages != '[]' }}
strategy:
matrix:
packages: ${{fromJSON(needs.build.outputs.affectedPackages)}}
steps:

- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: ./.github/actions/setup

- name: Get current git commit hash
id: get-git-commit-hash
run: |
echo "gitCommitHash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT

- uses: actions/cache@v4
id: ts-build-cache
with:
path: |
./packages/container/libraries/*
./packages/foundation/tools/*
key: ts-build-${{ steps.get-git-commit-hash.outputs.gitCommitHash }}
restore-keys: |
ts-build-${{ steps.get-git-commit-hash.outputs.gitCommitHash }}

- name: Build on cache miss
run: pnpm run build --filter ${{ join(matrix.packages, ' --filter ') }}
if: ${{ !steps.ts-build-cache.outputs.cache-hit }}

- name: Launch Unit Tests
run: pnpm run test:unit:js --filter ${{ join(matrix.packages, ' --filter ') }} --only

- name: Launch Integration Tests
run: pnpm run test:integration:js --filter ${{ join(matrix.packages, ' --filter ') }} --only

done:
name: Done
needs:
- test-package
runs-on: ubuntu-latest
steps:
- run: 'echo "Done!"'
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@inversifyjs/foundation-prettier-config": "workspace:*",
"@inversifyjs/foundation-eslint-config": "workspace:*",
"@inversifyjs/foundation-jest-config": "workspace:*",
"@inversifyjs/foundation-scripts": "workspace:*",
"@inversifyjs/foundation-typescript-config": "workspace:*",
"husky": "9.1.6",
"lint-staged": "15.2.10",
Expand Down
2 changes: 2 additions & 0 deletions packages/foundation/tools/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# npm installed packages
/node_modules/
5 changes: 5 additions & 0 deletions packages/foundation/tools/scripts/.lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"*.js": [
"prettier --write"
]
}
60 changes: 60 additions & 0 deletions packages/foundation/tools/scripts/bin/getAffectedProjectsChunks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env node

import process from 'node:process';
import { promisifiedExec } from '../src/promisifiedExec.js';

/**
* @param {Array} elements Elements
* @param {number} chunks chunks amount
* @returns {Array.<Array>}
*/
function buildChunks(elements, chunks) {
const chunkSize = Math.ceil(elements.length / chunks);

const chunksArray = [];

for (let i = 0; i < elements.length; i += chunkSize) {
chunksArray.push(elements.slice(i, i + chunkSize));
}

return chunksArray;
}

const TURBOREPO_ROOT_PACKAGE_NAME = '//';
const TURBOREPO_TASK_NOT_FOUND_MAGIC_STRING = '\u003cNONEXISTENT\u003e';

const taskToDryRun = process.argv[2];
const baseRef = process.argv[3];
const chunks = parseInt(process.argv[4]);

let execCommand = `pnpm exec turbo run ${taskToDryRun} --dry=json`;

if (baseRef !== undefined) {
execCommand += ` --filter ...[${baseRef}]`;
}

const stringifiedDryRun = (await promisifiedExec(execCommand)).trim();

const dryRunResult = JSON.parse(stringifiedDryRun);

/** @type {Array.<string>} */
const dryResultPackageNames = dryRunResult.packages.filter(
(packageName) => packageName !== TURBOREPO_ROOT_PACKAGE_NAME,
);

const tasks = dryRunResult.tasks.filter((task) =>
dryResultPackageNames.some(
(packageName) =>
task.taskId === `${packageName}#${taskToDryRun}` &&
!task.command.includes(TURBOREPO_TASK_NOT_FOUND_MAGIC_STRING),
),
);

/** @type {Array.<string>} */
const packageNames = tasks.map((task) => task.package);

const packageNameChunks = buildChunks(packageNames, chunks).filter(
(chunk) => chunk.length > 0,
);

process.stdout.write(JSON.stringify(packageNameChunks));
43 changes: 43 additions & 0 deletions packages/foundation/tools/scripts/bin/writeCommonJsPackageJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node

import fs from 'node:fs/promises';
import { argv } from 'node:process';
import path from 'node:path';
import { writeFile } from 'node:fs/promises';

/**
* @param {string} path
* @returns {Promise<boolean>}
*/
async function pathExists(path) {
try {
await fs.access(path);
return true;
} catch (_err) {
return false;
}
}

const directory = argv[2];

if (directory === undefined) {
throw new Error('Expected a path');
}

const directoryExists = await pathExists(directory);

if (!directoryExists) {
throw new Error(`Path ${directory} not found`);
}

const filePath = path.join(directory, 'package.json');

const packageJsonFileContent = JSON.stringify(
{
type: 'commonjs',
},
undefined,
2,
);

await writeFile(filePath, packageJsonFileContent);
43 changes: 43 additions & 0 deletions packages/foundation/tools/scripts/bin/writeEsmPackageJson.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env node

import fs from 'node:fs/promises';
import { argv } from 'node:process';
import path from 'node:path';
import { writeFile } from 'node:fs/promises';

/**
* @param {string} path
* @returns {Promise<boolean>}
*/
async function pathExists(path) {
try {
await fs.access(path);
return true;
} catch (_err) {
return false;
}
}

const directory = argv[2];

if (directory === undefined) {
throw new Error('Expected a path');
}

const directoryExists = await pathExists(directory);

if (!directoryExists) {
throw new Error(`Path ${directory} not found`);
}

const filePath = path.join(directory, 'package.json');

const packageJsonFileContent = JSON.stringify(
{
type: 'module',
},
undefined,
2,
);

await writeFile(filePath, packageJsonFileContent);
21 changes: 21 additions & 0 deletions packages/foundation/tools/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@inversifyjs/foundation-scripts",
"private": true,
"description": "Common scripts for monorepo packages",
"repository": {
"type": "git",
"url": "git+https://github.com/notaphplover/one-game.git"
},
"author": "Multiple authors",
"license": "MIT",
"bugs": {
"url": "https://github.com/notaphplover/one-game/issues"
},
"type": "module",
"homepage": "https://github.com/notaphplover/one-game#readme",
"bin": {
"foundation-get-affected-project-chunks": "./bin/getAffectedProjectsChunks.js",
"foundation-ts-package-cjs": "./bin/writeCommonJsPackageJson.js",
"foundation-ts-package-esm": "./bin/writeEsmPackageJson.js"
}
}
3 changes: 3 additions & 0 deletions packages/foundation/tools/scripts/prettier.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import config from '@inversifyjs/foundation-prettier-config';

export default config;
42 changes: 42 additions & 0 deletions packages/foundation/tools/scripts/src/promisifiedExec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { exec } from 'node:child_process';

/**
* @param {string} command command
* @param {PromisifiedExecOptions} options true to pipe standard input output and error streams
* @returns {Promise<string>}
*/
export async function promisifiedExec(command, options) {
options = {
cwd: options?.cwd,
interactive: options?.interactive ?? false,
};

return new Promise((resolve, reject) => {
const childProcess = exec(
command,
{
cwd: options.cwd,
},
(error, stdout) => {
if (error === null) {
resolve(stdout);
} else {
reject(error);
}
},
);

if (options.interactive) {
childProcess.stdout.pipe(process.stdout);
childProcess.stderr.pipe(process.stderr);

process.stdin.pipe(childProcess.stdin);
}
});
}

/**
* @typedef {Object} PromisifiedExecOptions
* @property {boolean} interactive
* @property {string} cwd
*/