Tiga tim front-end saat ini sedang mengembangkan tiga proyek besar di
ISPsystem : ISPmanager untuk mengelola server web, VMmanager untuk bekerja dengan virtualisasi, dan BILLmanager untuk mengotomatisasi bisnis hosters. Tim bekerja pada saat yang sama, dalam mode tenggat waktu yang ketat, sehingga Anda tidak dapat melakukannya tanpa optimasi. Untuk menghemat waktu, kami menggunakan solusi umum dan mengambil komponen umum ke dalam proyek terpisah. Proyek-proyek tersebut memiliki repositori mereka sendiri, yang didukung oleh anggota semua tim. Artikel ini akan membahas tentang pembangunan repositori-repositori ini, serta pekerjaan dengan mereka.

Bagaimana repositori proyek-proyek umum
Kami menggunakan server kami sendiri dengan
GitLab untuk menyimpan repositori jarak jauh. Penting bagi kami untuk mempertahankan lingkungan kerja yang sudah dikenal dan dapat bekerja dengan modul umum dalam proses pengembangannya. Karena itu, kami menolak untuk mempublikasikan di repositori pribadi
npmjs.com . Untungnya, modul Node.js dapat diinstal tidak hanya dengan NPM, tetapi juga dari
sumber lain , termasuk repositori git.
Kami menulis dalam TypeScript, yang kemudian dikompilasi ke dalam JavaScript untuk digunakan nanti. Tetapi di zaman kita, mungkin front-end yang malas tidak mengkompilasi JavaScript-nya. Oleh karena itu, kita memerlukan repositori berbeda untuk kode sumber dan proyek yang dikompilasi.
Setelah melalui duri diskusi panjang, kami mengembangkan konsep berikut. Harus ada dua repositori terpisah untuk sumber dan untuk versi modul yang dikompilasi. Selain itu, repositori kedua harus menjadi cermin dari yang pertama.
Ini berarti bahwa selama pengembangan, setiap fitur harus dipublikasikan sebelum rilis di cabang dengan nama yang sama persis dengan cabang di mana pengembangan sedang berlangsung. Dengan demikian, kami memiliki kesempatan untuk menggunakan versi eksperimental modul, menginstalnya dari cabang tertentu. Yang sedang kami kembangkan sangat nyaman untuk memeriksanya.
Plus, untuk setiap publikasi, kami membuat label yang menyimpan status proyek. Nama label sesuai dengan versi yang ditentukan dalam package.json. Ketika menginstal dari repositori git, label ditunjukkan setelah kisi, misalnya:
npm install git+ssh://[url ]
Dengan demikian, kita dapat memperbaiki versi modul yang digunakan dan tidak khawatir bahwa seseorang akan mengubah sesuatu.
Label juga dibuat untuk versi yang tidak stabil, namun, hash disingkat dari komit ditambahkan ke mereka dalam repositori sumber, dari mana publikasi dibuat. Berikut adalah contoh label seperti itu:
1.0.0_e5541dc1
Pendekatan ini memungkinkan Anda untuk mencapai keunikan label, serta mengaitkannya dengan repositori sumber.
Karena kita berbicara tentang versi modul yang stabil dan tidak stabil, inilah cara kita membedakannya: jika publikasi dilakukan dari master atau mengembangkan cabang, versinya stabil, sebaliknya tidak.
Bagaimana cara kerja dengan proyek-proyek umum yang diselenggarakan?
Semua perjanjian kami tidak masuk akal jika kami tidak dapat mengotomatiskannya. Secara khusus, mengotomatiskan proses penerbitan. Di bawah ini saya akan menunjukkan bagaimana pekerjaan diatur dengan salah satu modul umum - utilitas untuk menguji skrip khusus.
Utilitas ini, menggunakan perpustakaan
boneka , menyiapkan browser Chromium untuk digunakan dalam wadah buruh pelabuhan dan menjalankan tes menggunakan
Mocha . Peserta dari semua tim dapat memodifikasi utilitas tanpa takut merusak sesuatu dari satu sama lain.
Perintah berikut ini ditulis dalam file package.json dari utilitas pengujian:
"publish:git": "ts-node ./scripts/publish.ts"
Dia menjalankan skrip terdekat:
Publikasikan kode skrip penuh import { spawnSync } from 'child_process'; import { mkdirSync, existsSync } from 'fs'; import { join } from 'path'; import chalk from 'chalk'; /** * */ /** * * @param cwd - * @param stdio - / */ const getSpawnOptions = (cwd = process.cwd(), stdio = 'inherit') => ({ cwd, shell: true, stdio, }); /* */ const rootDir = join(__dirname, '../'); /* */ const isDiff = !!spawnSync('git', ['diff'], getSpawnOptions(rootDir, 'pipe')).stdout.toString().trim(); if (isDiff) { console.log(chalk.red('There are uncommitted changes')); } else { /* */ const build = spawnSync('npm', ['run', 'build'], getSpawnOptions(rootDir)); /* */ if (build.status === 0) { /* */ const tempDir = join(rootDir, 'temp'); if (existsSync(tempDir)) { spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } mkdirSync(tempDir); /* package.json */ const { name, version, repository } = require(join(rootDir, 'package.json')); const originUrl = repository.url.replace(`${name}-source`, name); spawnSync('git', ['init'], getSpawnOptions(tempDir)); spawnSync('git', ['remote', 'add', 'origin', originUrl], getSpawnOptions(tempDir)); /* */ const branch = spawnSync( 'git', ['symbolic-ref', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const buildBranch = branch === 'develop' ? 'master' : branch; /* , */ const shortSHA = spawnSync( 'git', ['rev-parse', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const tag = buildBranch === 'master' ? version : `${version}_${shortSHA}`; /* */ const isTagExists = !!spawnSync( 'git', ['ls-remote', 'origin', `refs/tags/${tag}`], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isTagExists) { console.log(chalk.red(`Tag ${tag} already exists`)); } else { /* */ const isBranchExits = !!spawnSync( 'git', ['ls-remote', '--exit-code', 'origin', buildBranch], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isBranchExits) { /* */ spawnSync('git', ['fetch', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', buildBranch], getSpawnOptions(tempDir)); } else { /* master */ spawnSync('git', ['fetch', 'origin', 'master'], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', 'master'], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['checkout', '-b', buildBranch], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['commit', '--allow-empty', '-m', '"Initial commit"'], getSpawnOptions(tempDir)); } /* */ spawnSync( 'rm', ['-rf', 'lib', 'package.json', 'package-lock.json', 'README.md'], getSpawnOptions(tempDir) ); /* */ spawnSync('cp', ['-r', 'lib', 'temp/lib'], getSpawnOptions(rootDir)); spawnSync('cp', ['package.json', 'temp/package.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['package-lock.json', 'temp/package-lock.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['README.md', 'temp/README.md'], getSpawnOptions(rootDir)); /* */ spawnSync('git', ['add', '--all'], getSpawnOptions(tempDir)); /* */ const lastCommitMessage = spawnSync( 'git', ['log', '--oneline', '-1'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); /* */ const message = buildBranch === 'master' ? version : lastCommitMessage; /* */ spawnSync('git', ['commit', '-m', `"${message}"`], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['tag', tag], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['push', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['push', '--tags'], getSpawnOptions(tempDir)); console.log(chalk.green('Published successfully!')); } /* */ spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } else { console.log(chalk.red(`Build was exited exited with code ${build.status}`)); } } console.log(''); // space
Pada gilirannya, kode ini melalui modul
child_process Node.js mengeksekusi semua perintah yang diperlukan.
Inilah tahapan utama dari karyanya:
1. Periksa perubahan yang tidak sah const isDiff = !!spawnSync('git', ['diff'], getSpawnOptions(rootDir, 'pipe')).stdout.toString().trim();
Di sini kita memeriksa hasil dari perintah
git diff . Tidak baik jika publikasi berisi perubahan yang tidak ada dalam sumber. Selain itu, ini akan memutus koneksi versi yang tidak stabil dengan commit.
2. Perakitan utilitas const build = spawnSync('npm', ['run', 'build'], getSpawnOptions(rootDir))
Konstanta build mendapatkan hasil build. Jika semuanya berjalan dengan baik, parameter status akan menjadi 0. Jika tidak, tidak ada yang akan dipublikasikan.
3. Menyebarkan repositori versi yang dikompilasiSeluruh proses penerbitan tidak lebih dari mengirimkan perubahan ke repositori tertentu. Oleh karena itu, skrip membuat direktori sementara di proyek kami di mana ia menginisialisasi repositori git dan mengaitkannya dengan repositori assembly jarak jauh.
const tempDir = join(rootDir, 'temp'); if (existsSync(tempDir)) { spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir)); } mkdirSync(tempDir); const { name, version, repository } = require(join(rootDir, 'package.json')); const originUrl = repository.url.replace(`${name}-source`, name); spawnSync('git', ['init'], getSpawnOptions(tempDir)); spawnSync('git', ['remote', 'add', 'origin', originUrl], getSpawnOptions(tempDir));
Ini adalah proses standar menggunakan
git init dan
git remote .
4. Pembuatan nama labelPertama, kita mencari tahu nama cabang tempat kita menerbitkan menggunakan perintah
git symbolic-ref . Dan atur nama cabang tempat perubahan akan diunggah (tidak ada cabang berkembang di repositori rakitan).
const branch = spawnSync( 'git', ['symbolic-ref', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); const buildBranch = branch === 'develop' ? 'master' : branch;
Menggunakan perintah
git rev-parse , kita mendapatkan hash disingkat dari commit terakhir di cabang tempat kita berada. Mungkin diperlukan untuk menghasilkan nama label dari versi yang tidak stabil.
<source lang="typescript"> const shortSHA = spawnSync( 'git', ['rev-parse', '--short', 'HEAD'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim();
Sebenarnya, buatlah nama labelnya.
const tag = buildBranch === 'master' ? version : `${version}_${shortSHA}`;
5. Memeriksa tidak adanya tag yang sama persis di repositori jarak jauh /* */ const isTagExists = !!spawnSync( 'git', ['ls-remote', 'origin', `refs/tags/${tag}`], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim();
Jika label serupa dibuat sebelumnya, hasil dari
perintah git ls-remote tidak akan kosong. Versi yang sama harus diterbitkan hanya sekali.
6. Membuat cabang yang sesuai dalam repositori assemblySeperti yang saya katakan sebelumnya, repositori versi yang dikompilasi dari utilitas adalah mirror dari repositori dengan kode sumber. Oleh karena itu, jika publikasi bukan dari master atau mengembangkan cabang, kita harus membuat cabang terkait di repositori perakitan. Ya, atau paling tidak pastikan keberadaannya
/* */ const isBranchExits = !!spawnSync( 'git', ['ls-remote', '--exit-code', 'origin', buildBranch], getSpawnOptions(tempDir, 'pipe') ).stdout.toString().trim(); if (isBranchExits) { /* */ spawnSync('git', ['fetch', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', buildBranch], getSpawnOptions(tempDir)); } else { /* master */ spawnSync('git', ['fetch', 'origin', 'master'], getSpawnOptions(tempDir)); spawnSync('git', ['checkout', 'master'], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['checkout', '-b', buildBranch], getSpawnOptions(tempDir)); /* */ spawnSync('git', ['commit', '--allow-empty', '-m', '"Initial commit"'], getSpawnOptions(tempDir)); }
Jika cabang tidak ada sebelumnya, kami menginisialisasi dengan komit kosong menggunakan bendera
--allow-empty .
7. Persiapan filePertama, Anda perlu menghapus semua yang ada di repositori yang digunakan. Lagi pula, jika kita menggunakan cabang yang sudah ada, itu berisi versi utilitas sebelumnya.
spawnSync( 'rm', ['-rf', 'lib', 'package.json', 'package-lock.json', 'README.md'], getSpawnOptions(tempDir) );
Selanjutnya, kami mentransfer file yang diperbarui yang diperlukan untuk publikasi, dan menambahkannya ke indeks repositori.
/* */ spawnSync('cp', ['-r', 'lib', 'temp/lib'], getSpawnOptions(rootDir)); spawnSync('cp', ['package.json', 'temp/package.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['package-lock.json', 'temp/package-lock.json'], getSpawnOptions(rootDir)); spawnSync('cp', ['README.md', 'temp/README.md'], getSpawnOptions(rootDir)); /* */ spawnSync('git', ['add', '--all'], getSpawnOptions(tempDir));
Setelah manipulasi seperti itu, git akan mengenali dengan baik perubahan yang dilakukan oleh baris file. Dengan cara ini kita mendapatkan riwayat perubahan yang konsisten bahkan dalam repositori versi yang dikompilasi.
8. Melakukan dan mengirimkan perubahanSebagai pesan komit di repositori rakitan, kami menggunakan nama label untuk versi stabil. Dan untuk tidak stabil - pesan komit dari repositori sumber. Dengan cara ini, mendukung gagasan kami tentang repositori cermin.
const lastCommitMessage = spawnSync( 'git', ['log', '--oneline', '-1'], getSpawnOptions(rootDir, 'pipe') ).stdout.toString().trim(); const message = buildBranch === 'master' ? version : lastCommitMessage; spawnSync('git', ['commit', '-m', `"${message}"`], getSpawnOptions(tempDir)); spawnSync('git', ['tag', tag], getSpawnOptions(tempDir)); spawnSync('git', ['push', 'origin', buildBranch], getSpawnOptions(tempDir)); spawnSync('git', ['push', '--tags'], getSpawnOptions(tempDir));
9. Menghapus direktori sementara spawnSync('rm', ['-rf', 'temp'], getSpawnOptions(rootDir))
Tinjau pembaruan dalam proyek umum
Salah satu proses terpenting setelah melakukan perubahan pada proyek umum menjadi tinjauan. Terlepas dari kenyataan bahwa teknologi yang dikembangkan memungkinkan Anda untuk membuat versi modul yang sepenuhnya terisolasi, tidak ada yang mau memiliki puluhan versi berbeda dari utilitas yang sama. Oleh karena itu, setiap proyek umum harus mengikuti jalur pengembangan tunggal. Ini harus disepakati antara tim.
Tinjauan pembaruan dalam proyek umum dilakukan oleh anggota semua tim sejauh mungkin. Ini adalah proses yang kompleks, karena masing-masing tim hidup dengan sprint sendiri dan memiliki beban kerja yang berbeda. Terkadang transisi ke versi baru mungkin tertunda.
Di sini Anda hanya dapat merekomendasikan untuk tidak mengabaikan dan tidak menunda dengan proses ini.