Telegram: haradkou_sdet
# pwd: <root>
npm init -y && cat package.json
Wrote to /Users/vitali.haradkou/bla-bla-bla/package.json:
{
"name": "at-workshop",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
{
"name": "at-workshop",
"version": "1.0.0",
"description": "Monorepo for automation",
"main": "index.js",
"private": true,
"engines": {
"node": ">=16"
},
"engineStrict": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
| Feature | pnpm | yarn | npm |
|---|---|---|---|
| node.js versioning [1] (aka nvm) | + | - | - |
| monorepo support | + | + | +- (from ver.8) |
| Installation time [2] avg. (in sec.) | 11.2 | 11.1 | 12.9 |
| Cross-env script support | + | + | - (cross-env) |
| is one library? | + | - (yarn-classic, yarn-pnp) | +- (npx) |
| Isolated node_modules | + | + | - |
| Autoinstalling peers | + | - | + |
| Listing licenses | + | + | - |
| Use Escaping (--) for run with args | - | - | + |
# pnpm-workspace.yaml
packages:
# all packages in direct subdirs of packages/
- "packages/*"
# all packages in subdirs of apps/
- "apps/**"
{
"name": "my-awesome-package",
"scripts": {
"test": "NODE_ENV=test node test.js"
}
}
Hello Windows!
# pnpm-workspace.yaml
shellEmulator: trueФича позаимствована у yarn
# pnpm-workspace.yaml
shellEmulator: true
useNodeVersion: 22.14.0 # pnpm exec <cli command>
engineStrict: true # panic when node js different
nodeVersion: 22.14.0 # (node -v)
When enabled, pnpm will fail if its version doesn't exactly match the version specified in the packageManager field of package.json.
# pnpm-workspace.yaml
shellEmulator: true
useNodeVersion: 22.14.0 # pnpm exec <cli command>
engineStrict: true # panic when node js different
nodeVersion: 22.14.0 # (node -v)
packageManagerStrictVersion: true
This project was originally created and hosted at https://github.com/marak/Faker.js/ - however around 4th Jan, 2022 - the author decided to delete the repository (for unknown reasons).
# pnpm-workspace.yaml
shellEmulator: true
useNodeVersion: 22.14.0 # pnpm exec <cli command>
engineStrict: true # panic when node js different
nodeVersion: 22.14.0 # (node -v)
packageManagerStrictVersion: true
save-prefix: '' # exact version
# pnpm-workspace.yaml
packages:
- apps/*
- packages/*
shellEmulator: true
useNodeVersion: 22.14.0 # pnpm exec <cli command>
engineStrict: true # panic when node js different
nodeVersion: 22.14.0 # (node -v)
packageManagerStrictVersion: true
save-prefix: '' # exact version
saveWorkspaceProtocol: rolling
# pwd: <root>
pnpm add typescript @types/node ts-node @tsconfig/node18 -D -w{
"extends": "@tsconfig/node18",
}
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Node 18",
"compilerOptions": {
"lib": ["es2022"],
"module": "commonjs",
"target": "es2022",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"moduleResolution": "node"
}
}
{
"name": "app1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
{
"name": "@apps/app1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
# pwd: <root>/apps/app1
pnpm create playwright// filename: app1/pages/login.ts
export type User = {
username: string;
password: string;
}
export class Login {
async login(user: User) {
console.log('APP1. LoginPage. USER:', user);
}
}
// filename: app1/pages/index.ts
import { Login, type User } from './login';
export { Login, User };
export default { Login };
// filename: app1/index.ts
// export for futher usage
export * from './pages';
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "build"
},
"include": [
"pages/**/*.ts",
"index.ts"
]
}{
"name": "@apps/app1",
"version": "1.0.0",
"description": "Automation for application 1",
"main": "index.js",
"scripts": {
"build": "tsc -p ./tsconfig.json"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "1.28.1"
}
}
{
"name": "at-workshop",
"version": "1.0.0",
"description": "Monorepo for automation",
"main": "index.js",
"private": true,
"engines": {
"node": ">=16"
},
"engineStrict": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "pnpm -r --parallel --if-present build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@tsconfig/node18": "1.0.1",
"@types/node": "18.11.14",
"ts-node": "10.9.1",
"typescript": "4.9.4"
}
}
# pwd: <root>
pnpm build
> at-workshop@1.0.0 build /bla-bla-bla/at-workshop
> pnpm -r --parallel --if-present build
apps/app1 build$ tsc -p ./tsconfig.json
apps/app1 build: Done
{
"name": "@apps/app1",
"version": "1.0.0",
"description": "Automation for application 1",
"main": "build/index.js",
"scripts": {
"build": "tsc -p ./tsconfig.json"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "1.28.1"
}
}
# pwd: <root>/apps/e2e-app1-app2
pnpm init && pnpm create playwright
Wrote to /Users/vitali.haradkou/bla-bla-bla/at-workshop/apps/e2e-app1-app2/package.json
{
"name": "e2e-app1-app2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
Done in 1.9s
Writing playwright.config.ts.
Writing tests/example.spec.ts.
Writing tests-examples/demo-todo-app.spec.ts.
Writing package.json.
Happy hacking! 🎭{
"extends": "../../tsconfig.json",
"compilerOptions": {
"declaration": true,
"outDir": "build"
}
}pnpm add @apps/app1
../..
dependencies:
+ @apps/app1 1.0.0 <- ../app1
Done in 1.6s{
"name": "@apps/e2e-app1-app2",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "playwright test"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "1.28.1"
},
"dependencies": {
"@apps/app1": "workspace:*"
}
}
// filename: <root>/apps/e2e-app1-app2/tests/example.spec.ts
import { test } from '@playwright/test';
import { Login } from '@apps/app1/pages';
test('homepage has title and links to intro page', async ({ }) => {
const login = new Login();
login.login({ username: 'qwe', password: 'qwe' });
});
# pwd: <root>/apps/e2e-app1-app2
pnpm test
> e2e-app1-app2@1.0.0 test /Users/vitali.haradkou/bla-bla-bla/at-workshop/apps/e2e-app1-app2
> playwright test
Running 3 tests using 3 workers
[chromium] › example.spec.ts:6:1 › homepage has title and links to intro page
APP1. LoginPage. USER: { username: 'qwe', password: 'qwe' }
[firefox] › example.spec.ts:6:1 › homepage has title and links to intro page
APP1. LoginPage. USER: { username: 'qwe', password: 'qwe' }
[webkit] › example.spec.ts:6:1 › homepage has title and links to intro page
APP1. LoginPage. USER: { username: 'qwe', password: 'qwe' }
3 passed (754ms)
To open last HTML report run:
npx playwright show-report
# pwd: <root>/packages/async
pnpm init
Wrote to /Users/vitali.haradkou/bla-bla-bla/at-workshop/packages/async/package.json
{
"name": "@utils/async",
"version": "1.0.0",
"description": "Async Utility for project X",
"main": "build/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}// filename: <root>/packages/async/foreach.ts
type Callback<T> = (item: Awaited<T>, index: number, array: readonly T[]) => Promise<void> | void;
type Options = {
serial?: boolean;
};
export default async function foreach<T>(array: T[], callback: Callback<T>, options?: Options) {
const serial = options?.serial ?? false;
if (serial) {
for (let index = 0; index < array.length; index++) {
const element = await array[index];
await callback(element, index, array);
}
return;
}
await Promise.all(array.map(async (v, id, arr) => {
callback(await v, id, arr);
}));
}
// filename: <root>/packages/async/index.ts
import foreach from './foreach';
export { foreach };
export default { foreach };
{
"name": "@utils/async",
"version": "1.0.0",
"description": "",
"main": "build/index.js",
"exports": {
".": "./build/index.js",
"./package.json": "./build/index.js",
"./foreach": "./build/foreach.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "tsc -p ./tsconfig.json"
},
"keywords": [],
"author": "",
"license": "ISC"
}
# pwd: <root>
pnpm build
> at-workshop@1.0.0 build /Users/vitali.haradkou/bla-bla-bla/at-workshop
> pnpm -r --parallel --if-present build
Scope: 3 of 4 workspace projects
packages/async build$ tsc -p ./tsconfig.json
apps/app1 build$ tsc -p ./tsconfig.json
apps/app1 build: Done
packages/async build: Done# pwd: <root>/apps/e2e-app1-app2
pnpm add @utils/async
dependencies:
+ @utils/async 1.0.0 <- ../../packages/async
Done in 1.8simport { test } from '@playwright/test';
import { Login } from '@apps/app1/pages';
import foreach from '@utils/async/foreach';
test('homepage has title and links to intro page', async ({ }) => {
const login = new Login();
login.login({ username: 'qwe', password: 'qwe' });
foreach([1, Promise.resolve(2), 3], (item) => {
console.log(`async Works: ${item}`);
});
});
# pwd: <root>/apps/e2e-app1-app2
pnpm test
> @apps/e2e-app1-app2@1.0.0 test /Users/vitali.haradkou/bla-bla-bla/at-workshop/apps/e2e-app1-app2
> playwright test
Running 3 tests using 3 workers
[firefox] › example.spec.ts:7:1 › homepage has title and links to intro page
APP1. LoginPage. USER: { username: 'qwe', password: 'qwe' }
async Works: 1
async Works: 2
async Works: 3
# pwd: <root>
pnpm add zx -Dw// filename: <root>/scripts/release.mjs
await $`cat package.json | grep name`
let branch = await $`git branch --show-current`
await $`dep deploy --branch=${branch}`
await Promise.all([
$`sleep 1; echo 1`,
$`sleep 2; echo 2`,
$`sleep 3; echo 3`,
])
let name = 'foo bar'
await $`mkdir /tmp/${name}`| Affect Root? | Dependent | InDependent | Mixed | NoVersion |
|---|---|---|---|---|
| Patch | + | - | - | - |
| Minor | + | - | +- (strategy) | - |
| Major | + | - | + | - |
Angular@2
# pwd: <root>
pnpm add -Dw @changesets/cli && pnpm changeset init
🦋 Thanks for choosing changesets to help manage your versioning and publishing
🦋
🦋 You should be set up to start using changesets now!
🦋
🦋 info We have added a `.changeset` folder, and a couple of files to help you out:
🦋 info - .changeset/README.md contains information about using changesets
🦋 info - .changeset/config.json is our default config
{
"$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
---
"@utils/async": major
"@apps/app1": patch
"@apps/e2e-app1-app2": patch
---
Add async package
pnpm changeset version
🦋 All files have been updated. Review them and commit at your leisure#!/usr/bin/env zx
import { $, argv, fs, os } from "zx";
const type = argv.type || argv.t;
let msg = '';
const upcomingFile = 'upcoming.md';
function assertType(type) {
if (!['major', 'minor', 'patch'].includes(type)) {
throw new Error(`Invalid type: ${type}`);
}
}
const template = (type, msg) => `
---
"@apps/app1": ${type}
"@apps/e2e-app1-app2": ${type}
"@utils/async": ${type}
---
${msg}
`;
assertType(type);
if (!fs.existsSync(upcomingFile)) {
throw new Error(`! No upcoming.md found, create and fill ${upcomingFile} first`);
}
msg = await fs.readFile(upcomingFile, 'utf8');
await $`chmod -R 777 .changeset`; // update permissions for execution
await $`chmod -R 777 releases`; // update permissions for releases
// await fs.copyFile(upcomingFile, `.changeset/${upcomingFile}`);
// create empty changeset file
await $`pnpm changeset --empty`;
await $`exit 0`; // exit with success
const { stdout: filename } = await $`find ${process.cwd()}/.changeset -type f -name '*.md' ! -name 'README.md'`; // find filename
await fs.writeFile(filename.trim(), template(type, msg)); // write changeset
await $`pnpm changeset version`; // add changeset
const { stdout: version } = await $`pnpm version ${type} --no-git-tag-version`;
// write to releases folder release.
await fs.writeFile(`releases/${version.trim()}.md`, msg);
// fill upcoming.md release with empty string
console.log('cleanup upcoming file');
await fs.writeFile(upcomingFile, os.EOL);
const commitMsg = `${'Release ' + version.trim() + '\n\n' + msg}`;
// git commit message
await $`git add .`;
await $`git commit -m ${commitMsg}`;
// create git tag without pushing
await $`git tag -a ${version.trim()} -m ${commitMsg}`;
// push tag
await $`git push --follow-tags origin`;
// push main
await $`git push -u origin main`;
{
"name": "at-workshop",
"version": "1.0.0",
"description": "Monorepo for automation",
"main": "index.js",
"private": true,
"engines": {
"node": ">=16"
},
"engineStrict": true,
"scripts": {
"release": "pnpm zx ./scripts/release.mjs",
"test": "echo \"Error: no test specified\" && exit 1",
"build": "pnpm -r --parallel --if-present build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@changesets/cli": "2.25.2",
"@tsconfig/node18": "1.0.1",
"@types/node": "18.11.14",
"ts-node": "10.9.1",
"typescript": "4.9.4",
"zx": "7.1.1"
}
}
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
name: Release.
on:
workflow_dispatch:
inputs:
release_type:
type: choice
options:
- patch
- minor
- major
description: "version type, can be 'patch', 'minor', 'major'"
required: true
default: patch
tags:
- "*"
jobs:
publish:
name: "Build and publish to npm"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Setup pnpm
uses: pnpm/action-setup@v2.2.2
with:
version: 7.18.1
run_install: |
- args: []
- run: pnpm build
- name: Setup git config
run: |
git config --global user.name "github-actions[bot]" &&
git config --global user.email "github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>"
echo "GIT_USER=${GITHUB_ACTOR}:${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV
- name: Get upcoming changes
run: |
RELEASE_LOG=$(< ./upcoming.md)
- run: pnpm release --type ${{ github.event.inputs.release_type || 'patch' }}
- name: Get release log from changelog
run: |
TAG=`git describe --tags --abbrev=0`
echo "workaround described here: https://trstringer.com/github-actions-multiline-strings/"
echo "RELEASE_LOG<<EOF" >> $GITHUB_ENV
echo "$RELEASE_LOG" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- uses: fregante/release-with-changelog@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-template: "- {title} ← {hash}"
template: |
### Changelog
${{ env.RELEASE_LOG }}
### PRs
{commits}
Full changelog: {range}
made with ❤ from Vitali Haradkou