diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0afa7173e..68e78d2fe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,3 +60,4 @@ jobs: run: npm run lint - run: npm run prettier:check - run: npm run check:pr-version + - run: npm run check:pr-next-version diff --git a/next/package.json b/next/package.json index 7bc2acacf..7c3426d57 100644 --- a/next/package.json +++ b/next/package.json @@ -1,6 +1,6 @@ { "name": "@remoteoss/json-schema-form", - "version": "1.0.0-alpha.1", + "version": "1.0.0-beta.0", "packageManager": "pnpm@9.15.2", "description": "WIP V2 – Headless UI form powered by JSON Schemas", "author": "Remote.com (https://remote.com/)", @@ -16,16 +16,23 @@ "json-schema", "form" ], - "main": "index.js", + "main": "dist/index.mjs", + "module": "dist/index.mjs", + "files": [ + "README.md", + "dist" + ], "engines": { - "node": "22.13.1" + "node": ">=18.14.0" }, "scripts": { + "build": "tsup", "test": "jest", "test:watch": "jest --watchAll", "test:file": "jest --runTestsByPath", "lint": "eslint --max-warnings 0 .", - "build": "tsup" + "release:dev": "cd .. && npm run release:v1:dev", + "release:beta": "cd .. && npm run release:v1:beta" }, "devDependencies": { "@antfu/eslint-config": "^3.14.0", @@ -35,6 +42,7 @@ "@jest/globals": "^29.7.0", "babel-jest": "^29.7.0", "eslint": "^9.18.0", + "generate-changelog": "^1.8.0", "jest": "^29.7.0", "json-schema-typed": "^8.0.1", "tsup": "^8.3.5", diff --git a/next/pnpm-lock.yaml b/next/pnpm-lock.yaml index a995f495a..bbd18b1cb 100644 --- a/next/pnpm-lock.yaml +++ b/next/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: eslint: specifier: ^9.18.0 version: 9.18.0 + generate-changelog: + specifier: ^1.8.0 + version: 1.8.0 jest: specifier: ^29.7.0 version: 29.7.0(@types/node@22.10.7)(ts-node@10.9.2(@types/node@22.10.7)(typescript@5.7.3)) @@ -1409,6 +1412,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1513,6 +1519,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1940,6 +1949,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + generate-changelog@1.8.0: + resolution: {integrity: sha512-msgpxeB75Ziyg3wGsZuPNl7c5RxChMKmYcAX5obnhUow90dBZW3nLic6nxGtst7Bpx453oS6zAIHcX7F3QVasw==} + hasBin: true + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -1959,6 +1972,9 @@ packages: get-tsconfig@4.9.0: resolution: {integrity: sha512-52n24W52sIueosRe0XZ8Ex5Yle+WbhfCKnV/gWXpbVR8FXNTfqdKEKUSypKso66VRHTvvcQxL44UTZbJRlCTnw==} + github-url-from-git@1.5.0: + resolution: {integrity: sha512-WWOec4aRI7YAykQ9+BHmzjyNlkfJFG8QLXnDTsLz/kZefq7qkzdfo4p6fkYYMIq1aj+gZcQs/1HQhQh3DPPxlQ==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -4812,6 +4828,8 @@ snapshots: balanced-match@1.0.2: {} + bluebird@3.7.2: {} + boolbase@1.0.0: {} brace-expansion@1.1.11: @@ -4898,6 +4916,8 @@ snapshots: color-name@1.1.4: {} + commander@2.20.3: {} + commander@4.1.1: {} comment-parser@1.4.1: {} @@ -5411,6 +5431,12 @@ snapshots: function-bind@1.1.2: {} + generate-changelog@1.8.0: + dependencies: + bluebird: 3.7.2 + commander: 2.20.3 + github-url-from-git: 1.5.0 + gensync@1.0.0-beta.2: {} get-caller-file@2.0.5: {} @@ -5423,6 +5449,8 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + github-url-from-git@1.5.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 diff --git a/package.json b/package.json index 1a12ceb00..1b98b1035 100644 --- a/package.json +++ b/package.json @@ -32,11 +32,14 @@ "format:prettier": "prettier --write \"src/**/*.{js,ts}\"", "prettier:check": "prettier --check \"src/**/*.{js,ts}\"", "check:pr-version": "node scripts/pr_dev_version", + "check:pr-next-version": "node scripts/pr_next_dev_version", "release:local": "node scripts/release_local", "release:dev:patch": "node scripts/release_dev patch", "release:dev:minor": "node scripts/release_dev minor", "release:main:patch": "node scripts/release_main patch", "release:main:minor": "node scripts/release_main minor", + "release:v1:dev": "node scripts/release_v1 dev", + "release:v1:beta": "node scripts/release_v1 beta", "version_as_main": "node scripts/version_as_main.js", "psrepublishOnly": "if [[ ! $PWD =~ scripts$ ]]; then npm run publish:nopublish; fi", "psublish:nopublish": "echo 'Use `npm release:*` script instead && exit 1" diff --git a/scripts/pr_next_dev_version.js b/scripts/pr_next_dev_version.js new file mode 100644 index 000000000..239ead02a --- /dev/null +++ b/scripts/pr_next_dev_version.js @@ -0,0 +1,29 @@ +const fs = require('fs'); +const path = require('path'); + +function init() { + const packageJson = fs.readFileSync(path.resolve(__dirname, '../next/package.json'), 'utf8'); + const { version } = JSON.parse(packageJson); + + if (version.includes('-dev')) { + console.log( + `🟠 This PR cannot be merged because the next/package.json version ${version} contains "-dev".` + + '\n The version in next/package.json should be a beta version (e.g., 1.0.0-beta.0).' + ); + process.exit(1); + } + + if (!version.includes('-beta.')) { + console.log( + `🟠 This PR cannot be merged because the next/package.json version ${version} is not a beta version.` + + '\n The version in next/package.json should be in format X.X.X-beta.Y (e.g., 1.0.0-beta.0).' + ); + process.exit(1); + } + + console.log( + `The package version in next/package.json is ${version} and seems valid. Continuing...` + ); +} + +init(); diff --git a/scripts/release_v1.js b/scripts/release_v1.js new file mode 100644 index 000000000..c63052a4c --- /dev/null +++ b/scripts/release_v1.js @@ -0,0 +1,178 @@ +const path = require('path'); + +const semver = require('semver'); + +const { + askForConfirmation, + askForText, + checkGitStatus, + checkNpmAuth, + runExec, + revertCommit, + revertChanges, + getDateYYYYMMDDHHMMSS, +} = require('./release.helpers'); + +const packageJsonPath = path.resolve(__dirname, '../next/package.json'); +const packageJson = require(packageJsonPath); + +async function checkGitBranchAndStatus() { + const releaseType = process.argv[2]; + console.log(`Checking your branch for ${releaseType} release...`); + + const resultBranch = await runExec('git branch --show-current', { + silent: true, + }); + const branchName = resultBranch.stdout.toString().trim(); + + if (releaseType === 'dev') { + // For dev releases, cannot be on main branch + if (branchName === 'main') { + console.error(`🟠 You are at "main". Dev versions cannot be released from main branch.`); + process.exit(1); + } + } else if (releaseType === 'beta') { + // For beta releases, must be on main branch and up to date + if (branchName !== 'main') { + console.error( + `🟠 You are at "${branchName}" instead of "main" branch. Beta versions must be released from main.` + ); + process.exit(1); + } + + // Check if local main is up to date + await runExec('git remote update', { silent: true }); + const resultStatus = await runExec('git status -uno', { silent: true }); + const mainStatus = resultStatus.stdout.toString().trim(); + + if (!mainStatus.includes("Your branch is up to date with 'origin/main'.")) { + console.error(`🟠 Please make sure your branch is up to date with the git repo.`); + process.exit(1); + } + } + + await checkGitStatus(); +} + +async function getNewVersion() { + const releaseType = process.argv[2]; + if (!['dev', 'beta'].includes(releaseType)) { + console.error('🟠 Invalid release type. Use dev or beta'); + process.exit(1); + } + + const currentVersion = packageJson.version; + + if (releaseType === 'dev') { + const timestamp = getDateYYYYMMDDHHMMSS(); + console.log('Creating new dev version...'); + return `1.0.0-dev.${timestamp}`; + } + + // For beta releases + console.log('Creating new beta version...'); + if (currentVersion.includes('-beta.')) { + return semver.inc(currentVersion, 'prerelease', 'beta'); + } + + console.error( + `🟠 Cannot create beta version: Current version "${currentVersion}" is not a beta version.\n` + + ' The package.json version should be in the format "1.0.0-beta.X"' + ); + process.exit(1); +} + +async function bumpVersion({ newVersion }) { + const cmd = `cd next && npm version --no-git-tag-version ${newVersion}`; + await runExec(cmd); +} + +async function build() { + console.log('Building next version...'); + const cmd = 'cd next && npm run build'; + await runExec(cmd); +} + +async function updateChangelog() { + console.log('Updating changelog...'); + const cmd = 'cd next && npx generate-changelog'; + await runExec(cmd); +} + +async function gitCommit({ newVersion, releaseType }) { + console.log('Committing published version...'); + const prefix = `v1-${releaseType}`; + + let cmd; + if (releaseType === 'beta') { + // For beta, we commit package.json changes and changelog + cmd = `git add next/package.json next/CHANGELOG.md && git commit -m "Release ${prefix} ${newVersion}" && git tag ${prefix}-${newVersion} && git push && git push origin --tags`; + } else { + // For dev, we only create a tag + cmd = `git tag ${prefix}-${newVersion} && git push origin --tags`; + } + + await runExec(cmd); +} + +async function publish({ newVersion, releaseType, otp }) { + console.log('Publishing new version...'); + const npmTag = `v1-${releaseType}`; + const originalVersion = packageJson.version; + + try { + // Publish with the dev/beta version + const cmd = `cd next && npm publish --access=public --tag=${npmTag} --otp=${otp}`; + await runExec(cmd); + + // For dev releases, revert package.json back to original version + if (releaseType === 'dev') { + const revertCmd = `cd next && npm version --no-git-tag-version ${originalVersion}`; + await runExec(revertCmd); + } + + console.log(`🎉 ${npmTag} version ${newVersion} published!`); + console.log(`Install with: npm i @remoteoss/json-schema-form@${npmTag}`); + } catch { + console.log('🚨 Publish failed! Perhaps the OTP is wrong.'); + await revertCommit({ newVersion }); + } +} + +async function init() { + const releaseType = process.argv[2]; + await checkGitBranchAndStatus(); + const newVersion = await getNewVersion(); + + console.log(':: Current version:', packageJson.version); + console.log(`:::::: New version (${releaseType}):`, newVersion); + + const answer = await askForConfirmation('Ready to commit and publish it?'); + + if (answer === 'no') { + process.exit(1); + } + + await checkNpmAuth(); + + await bumpVersion({ newVersion }); + + // Only update changelog for beta releases + if (releaseType === 'beta') { + await updateChangelog(); + const answerChangelog = await askForConfirmation( + 'Changelog is updated. You may tweak it as needed. Once ready, press Y to continue.' + ); + if (answerChangelog === 'no') { + await revertChanges(); + } + } + + await build(); + const otp = await askForText('🔐 What is the NPM Auth OTP? (Check 1PW) '); + + await gitCommit({ newVersion, releaseType }); + await publish({ newVersion, releaseType, otp }); +} + +init();