Browse Source

chore: release multiple packages (#9698)

Marcel Mraz 3 tuần trước cách đây
mục cha
commit
258605d1d5

+ 1 - 1
.github/workflows/autorelease-excalidraw.yml

@@ -24,4 +24,4 @@ jobs:
       - name: Auto release
         run: |
           yarn add @actions/core -W
-          yarn autorelease
+          yarn release --tag=next --non-interactive

+ 0 - 55
.github/workflows/autorelease-preview.yml

@@ -1,55 +0,0 @@
-name: Auto release excalidraw preview
-on:
-  issue_comment:
-    types: [created, edited]
-
-jobs:
-  Auto-release-excalidraw-preview:
-    name: Auto release preview
-    if: github.event.comment.body == '@excalibot trigger release' && github.event.issue.pull_request
-    runs-on: ubuntu-latest
-    steps:
-      - name: React to release comment
-        uses: peter-evans/create-or-update-comment@v1
-        with:
-          token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
-          comment-id: ${{ github.event.comment.id }}
-          reactions: "+1"
-      - name: Get PR SHA
-        id: sha
-        uses: actions/github-script@v4
-        with:
-          result-encoding: string
-          script: |
-            const { owner, repo, number } = context.issue;
-            const pr = await github.pulls.get({
-              owner,
-              repo,
-              pull_number: number,
-            });
-            return pr.data.head.sha
-      - uses: actions/checkout@v2
-        with:
-          ref: ${{ steps.sha.outputs.result }}
-          fetch-depth: 2
-      - name: Setup Node.js 18.x
-        uses: actions/setup-node@v2
-        with:
-          node-version: 18.x
-      - name: Set up publish access
-        run: |
-          npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
-        env:
-          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
-      - name: Auto release preview
-        id: "autorelease"
-        run: |
-          yarn add @actions/core -W
-          yarn autorelease preview ${{ github.event.issue.number }}
-      - name: Post comment post release
-        if: always()
-        uses: peter-evans/create-or-update-comment@v1
-        with:
-          token: ${{ secrets.PUSH_TRANSLATIONS_COVERAGE_PAT }}
-          issue-number: ${{ github.event.issue.number }}
-          body: "@${{ github.event.comment.user.login }} ${{ steps.autorelease.outputs.result }}"

+ 2 - 22
dev-docs/docs/@excalidraw/excalidraw/development.mdx

@@ -28,32 +28,12 @@ To start the example app using the `@excalidraw/excalidraw` package, follow the
 
 ## Releasing
 
-### Create a test release
-
-You can create a test release by posting the below comment in your pull request:
-
-```bash
-@excalibot trigger release
-```
-
-Once the version is released `@excalibot` will post a comment with the release version.
-
 ### Creating a production release
 
 To release the next stable version follow the below steps:
 
 ```bash
-yarn prerelease:excalidraw
+yarn release --tag=latest --version=0.19.0
 ```
 
-You need to pass the `version` for which you want to create the release. This will make the changes needed before making the release like updating `package.json`, `changelog` and more.
-
-The next step is to run the `release` script:
-
-```bash
-yarn release:excalidraw
-```
-
-This will publish the package.
-
-Right now there are two steps to create a production release but once this works fine these scripts will be combined and more automation will be done.
+You will need to pass the `latest` tag with `version` for which you want to create the release. This will make the changes needed before publishing the packages into NPM, like updating dependencies of all `@excalidraw/*` packages, generating new entries in `CHANGELOG.md` and more.

+ 2 - 1
examples/with-nextjs/package.json

@@ -3,7 +3,8 @@
   "version": "0.1.0",
   "private": true,
   "scripts": {
-    "build:workspace": "yarn workspace @excalidraw/excalidraw run build:esm && yarn copy:assets",
+    "build:packages": "yarn --cwd ../../ build:packages",
+    "build:workspace": "yarn build:packages && yarn copy:assets",
     "copy:assets": "cp -r ../../packages/excalidraw/dist/prod/fonts ./public",
     "dev": "yarn build:workspace && next dev -p 3005",
     "build": "yarn build:workspace && next build",

+ 1 - 1
examples/with-script-in-browser/package.json

@@ -17,6 +17,6 @@
     "build": "vite build",
     "preview": "vite preview --port 5002",
     "build:preview": "yarn build && yarn preview",
-    "build:package": "yarn workspace @excalidraw/excalidraw run build:esm"
+    "build:packages": "yarn --cwd ../../ build:packages"
   }
 }

+ 1 - 1
examples/with-script-in-browser/vercel.json

@@ -1,5 +1,5 @@
 {
   "outputDirectory": "dist",
   "installCommand": "yarn install",
-  "buildCommand": "yarn build:package && yarn build"
+  "buildCommand": "yarn build:packages && yarn build"
 }

+ 10 - 5
package.json

@@ -52,13 +52,17 @@
     "build-node": "node ./scripts/build-node.js",
     "build:app:docker": "yarn --cwd ./excalidraw-app build:app:docker",
     "build:app": "yarn --cwd ./excalidraw-app build:app",
-    "build:package": "yarn --cwd ./packages/excalidraw build:esm",
+    "build:common": "yarn --cwd ./packages/common build:esm",
+    "build:element": "yarn --cwd ./packages/element build:esm",
+    "build:excalidraw": "yarn --cwd ./packages/excalidraw build:esm",
+    "build:math": "yarn --cwd ./packages/math build:esm",
+    "build:packages": "yarn build:common && yarn build:math && yarn build:element && yarn build:excalidraw",
     "build:version": "yarn --cwd ./excalidraw-app build:version",
     "build": "yarn --cwd ./excalidraw-app build",
     "build:preview": "yarn --cwd ./excalidraw-app build:preview",
     "start": "yarn --cwd ./excalidraw-app start",
     "start:production": "yarn --cwd ./excalidraw-app start:production",
-    "start:example": "yarn build:package && yarn --cwd ./examples/with-script-in-browser start",
+    "start:example": "yarn build:packages && yarn --cwd ./examples/with-script-in-browser start",
     "test:all": "yarn test:typecheck && yarn test:code && yarn test:other && yarn test:app --watch=false",
     "test:app": "vitest",
     "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .",
@@ -76,9 +80,10 @@
     "locales-coverage:description": "node scripts/locales-coverage-description.js",
     "prepare": "husky install",
     "prettier": "prettier \"**/*.{css,scss,json,md,html,yml}\" --ignore-path=.eslintignore",
-    "autorelease": "node scripts/autorelease.js",
-    "prerelease:excalidraw": "node scripts/prerelease.js",
-    "release:excalidraw": "node scripts/release.js",
+    "release": "node scripts/release.js",
+    "release:test": "node scripts/release.js --tag=test",
+    "release:next": "node scripts/release.js --tag=next",
+    "release:latest": "node scripts/release.js --tag=latest",
     "rm:build": "rimraf --glob excalidraw-app/build excalidraw-app/dist excalidraw-app/dev-dist packages/*/dist packages/*/build examples/*/build examples/*/dist",
     "rm:node_modules": "rimraf --glob node_modules excalidraw-app/node_modules packages/*/node_modules",
     "clean-install": "yarn rm:node_modules && yarn install"

+ 5 - 2
packages/common/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@excalidraw/common",
-  "version": "0.1.0",
+  "version": "0.18.0",
   "type": "module",
   "types": "./dist/types/common/src/index.d.ts",
   "main": "./dist/prod/index.js",
@@ -13,7 +13,10 @@
       "default": "./dist/prod/index.js"
     },
     "./*": {
-      "types": "./dist/types/common/src/*.d.ts"
+      "types": "./dist/types/common/src/*.d.ts",
+      "development": "./dist/dev/index.js",
+      "production": "./dist/prod/index.js",
+      "default": "./dist/prod/index.js"
     }
   },
   "files": [

+ 9 - 2
packages/element/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@excalidraw/element",
-  "version": "0.1.0",
+  "version": "0.18.0",
   "type": "module",
   "types": "./dist/types/element/src/index.d.ts",
   "main": "./dist/prod/index.js",
@@ -13,7 +13,10 @@
       "default": "./dist/prod/index.js"
     },
     "./*": {
-      "types": "./dist/types/element/src/*.d.ts"
+      "types": "./dist/types/element/src/*.d.ts",
+      "development": "./dist/dev/index.js",
+      "production": "./dist/prod/index.js",
+      "default": "./dist/prod/index.js"
     }
   },
   "files": [
@@ -52,5 +55,9 @@
   "scripts": {
     "gen:types": "rimraf types && tsc",
     "build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
+  },
+  "dependencies": {
+    "@excalidraw/common": "0.18.0",
+    "@excalidraw/math": "0.18.0"
   }
 }

+ 10 - 7
packages/excalidraw/package.json

@@ -66,12 +66,22 @@
       "last 1 safari version"
     ]
   },
+  "repository": "https://github.com/excalidraw/excalidraw",
+  "bugs": "https://github.com/excalidraw/excalidraw/issues",
+  "homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw",
+  "scripts": {
+    "gen:types": "rimraf types && tsc",
+    "build:esm": "rimraf dist && node ../../scripts/buildPackage.js && yarn gen:types"
+  },
   "peerDependencies": {
     "react": "^17.0.2 || ^18.2.0 || ^19.0.0",
     "react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0"
   },
   "dependencies": {
     "@braintree/sanitize-url": "6.0.2",
+    "@excalidraw/common": "0.18.0",
+    "@excalidraw/element": "0.18.0",
+    "@excalidraw/math": "0.18.0",
     "@excalidraw/laser-pointer": "1.3.1",
     "@excalidraw/mermaid-to-excalidraw": "1.1.2",
     "@excalidraw/random-username": "1.1.0",
@@ -124,12 +134,5 @@
     "harfbuzzjs": "0.3.6",
     "jest-diff": "29.7.0",
     "typescript": "4.9.4"
-  },
-  "repository": "https://github.com/excalidraw/excalidraw",
-  "bugs": "https://github.com/excalidraw/excalidraw/issues",
-  "homepage": "https://github.com/excalidraw/excalidraw/tree/master/packages/excalidraw",
-  "scripts": {
-    "gen:types": "rimraf types && tsc",
-    "build:esm": "rimraf dist && node ../../scripts/buildPackage.js && yarn gen:types"
   }
 }

+ 8 - 2
packages/math/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@excalidraw/math",
-  "version": "0.1.0",
+  "version": "0.18.0",
   "type": "module",
   "types": "./dist/types/math/src/index.d.ts",
   "main": "./dist/prod/index.js",
@@ -13,7 +13,10 @@
       "default": "./dist/prod/index.js"
     },
     "./*": {
-      "types": "./dist/types/math/src/*.d.ts"
+      "types": "./dist/types/math/src/*.d.ts",
+      "development": "./dist/dev/index.js",
+      "production": "./dist/prod/index.js",
+      "default": "./dist/prod/index.js"
     }
   },
   "files": [
@@ -56,5 +59,8 @@
   "scripts": {
     "gen:types": "rimraf types && tsc",
     "build:esm": "rimraf dist && node ../../scripts/buildBase.js && yarn gen:types"
+  },
+  "dependencies": {
+    "@excalidraw/common": "0.18.0"
   }
 }

+ 0 - 71
scripts/autorelease.js

@@ -1,71 +0,0 @@
-const { exec, execSync } = require("child_process");
-const fs = require("fs");
-
-const core = require("@actions/core");
-
-const excalidrawDir = `${__dirname}/../packages/excalidraw`;
-const excalidrawPackage = `${excalidrawDir}/package.json`;
-const pkg = require(excalidrawPackage);
-const isPreview = process.argv.slice(2)[0] === "preview";
-
-const getShortCommitHash = () => {
-  return execSync("git rev-parse --short HEAD").toString().trim();
-};
-
-const publish = () => {
-  const tag = isPreview ? "preview" : "next";
-
-  try {
-    execSync(`yarn  --frozen-lockfile`);
-    execSync(`yarn run build:esm`, { cwd: excalidrawDir });
-    execSync(`yarn --cwd ${excalidrawDir} publish --tag ${tag}`);
-    console.info(`Published ${pkg.name}@${tag}🎉`);
-    core.setOutput(
-      "result",
-      `**Preview version has been shipped** :rocket:
-    You can use [@excalidraw/excalidraw@${pkg.version}](https://www.npmjs.com/package/@excalidraw/excalidraw/v/${pkg.version}) for testing!`,
-    );
-  } catch (error) {
-    core.setOutput("result", "package couldn't be published :warning:!");
-    console.error(error);
-    process.exit(1);
-  }
-};
-// get files changed between prev and head commit
-exec(`git diff --name-only HEAD^ HEAD`, async (error, stdout, stderr) => {
-  if (error || stderr) {
-    console.error(error);
-    core.setOutput("result", ":warning: Package couldn't be published!");
-    process.exit(1);
-  }
-  const changedFiles = stdout.trim().split("\n");
-
-  const excalidrawPackageFiles = changedFiles.filter((file) => {
-    return (
-      file.indexOf("packages/excalidraw") >= 0 ||
-      file.indexOf("buildPackage.js") > 0
-    );
-  });
-  if (!excalidrawPackageFiles.length) {
-    console.info("Skipping release as no valid diff found");
-    core.setOutput("result", "Skipping release as no valid diff found");
-    process.exit(0);
-  }
-
-  // update package.json
-  let version = `${pkg.version}-${getShortCommitHash()}`;
-
-  // update readme
-
-  if (isPreview) {
-    // use pullNumber-commithash as the version for preview
-    const pullRequestNumber = process.argv.slice(3)[0];
-    version = `${pkg.version}-${pullRequestNumber}-${getShortCommitHash()}`;
-  }
-  pkg.version = version;
-
-  fs.writeFileSync(excalidrawPackage, JSON.stringify(pkg, null, 2), "utf8");
-
-  console.info("Publish in progress...");
-  publish();
-});

+ 1 - 4
scripts/buildBase.js

@@ -11,12 +11,9 @@ const getConfig = (outdir) => ({
   entryNames: "[name]",
   assetNames: "[dir]/[name]",
   alias: {
-    "@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
-    "@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
-    "@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
-    "@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
     "@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
   },
+  external: ["@excalidraw/common", "@excalidraw/element", "@excalidraw/math"],
 });
 
 function buildDev(config) {

+ 1 - 4
scripts/buildPackage.js

@@ -28,12 +28,9 @@ const getConfig = (outdir) => ({
   assetNames: "[dir]/[name]",
   chunkNames: "[dir]/[name]-[hash]",
   alias: {
-    "@excalidraw/common": path.resolve(__dirname, "../packages/common/src"),
-    "@excalidraw/element": path.resolve(__dirname, "../packages/element/src"),
-    "@excalidraw/excalidraw": path.resolve(__dirname, "../packages/excalidraw"),
-    "@excalidraw/math": path.resolve(__dirname, "../packages/math/src"),
     "@excalidraw/utils": path.resolve(__dirname, "../packages/utils/src"),
   },
+  external: ["@excalidraw/common", "@excalidraw/element", "@excalidraw/math"],
   loader: {
     ".woff2": "file",
   },

+ 0 - 38
scripts/prerelease.js

@@ -1,38 +0,0 @@
-const fs = require("fs");
-const util = require("util");
-
-const exec = util.promisify(require("child_process").exec);
-const updateChangelog = require("./updateChangelog");
-
-const excalidrawDir = `${__dirname}/../packages/excalidraw/`;
-const excalidrawPackage = `${excalidrawDir}/package.json`;
-
-const updatePackageVersion = (nextVersion) => {
-  const pkg = require(excalidrawPackage);
-  pkg.version = nextVersion;
-  const content = `${JSON.stringify(pkg, null, 2)}\n`;
-  fs.writeFileSync(excalidrawPackage, content, "utf-8");
-};
-
-const prerelease = async (nextVersion) => {
-  try {
-    await updateChangelog(nextVersion);
-    updatePackageVersion(nextVersion);
-    await exec(`git add -u`);
-    await exec(
-      `git commit -m "docs: release @excalidraw/excalidraw@${nextVersion}  🎉"`,
-    );
-
-    console.info("Done!");
-  } catch (error) {
-    console.error(error);
-    process.exit(1);
-  }
-};
-
-const nextVersion = process.argv.slice(2)[0];
-if (!nextVersion) {
-  console.error("Pass the next version to release!");
-  process.exit(1);
-}
-prerelease(nextVersion);

+ 231 - 20
scripts/release.js

@@ -1,28 +1,239 @@
+const fs = require("fs");
+const path = require("path");
+
 const { execSync } = require("child_process");
 
-const excalidrawDir = `${__dirname}/../packages/excalidraw`;
-const excalidrawPackage = `${excalidrawDir}/package.json`;
-const pkg = require(excalidrawPackage);
-
-const publish = () => {
-  try {
-    console.info("Installing the dependencies in root folder...");
-    execSync(`yarn  --frozen-lockfile`);
-    console.info("Installing the dependencies in excalidraw directory...");
-    execSync(`yarn --frozen-lockfile`, { cwd: excalidrawDir });
-    console.info("Building ESM Package...");
-    execSync(`yarn run build:esm`, { cwd: excalidrawDir });
-    console.info("Publishing the package...");
-    execSync(`yarn --cwd ${excalidrawDir} publish`);
-  } catch (error) {
-    console.error(error);
+const updateChangelog = require("./updateChangelog");
+
+// skipping utils for now, as it has independent release process
+const PACKAGES = ["common", "math", "element", "excalidraw"];
+const PACKAGES_DIR = path.resolve(__dirname, "../packages");
+
+/**
+ * Returns the arguments for the release script.
+ *
+ * Usage examples:
+ * - yarn release --help                          -> prints this help message
+ * - yarn release                                 -> publishes `@excalidraw` packages with "test" tag and "-[hash]" version suffix
+ * - yarn release --tag=test                      -> same as above
+ * - yarn release --tag=next                      -> publishes `@excalidraw` packages with "next" tag and version "-[hash]" suffix
+ * - yarn release --tag=next --non-interactive    -> skips interactive prompts (runs on CI/CD), otherwise same as above
+ * - yarn release --tag=latest --version=0.19.0   -> publishes `@excalidraw` packages with "latest" tag and version "0.19.0" & prepares changelog for the release
+ *
+ * @returns [tag, version, nonInteractive]
+ */
+const getArguments = () => {
+  let tag = "test";
+  let version = "";
+  let nonInteractive = false;
+
+  for (const argument of process.argv.slice(2)) {
+    if (/--help/.test(argument)) {
+      console.info(`Available arguments:
+  --tag=<tag>                                    -> (optional) "test" (default), "next" for auto release, "latest" for stable release
+  --version=<version>                            -> (optional) for "next" and "test", (required) for "latest" i.e. "0.19.0"
+  --non-interactive                              -> (optional) disables interactive prompts`);
+
+      console.info(`\nUsage examples:
+  - yarn release                                 -> publishes \`@excalidraw\` packages with "test" tag and "-[hash]" version suffix
+  - yarn release --tag=test                      -> same as above
+  - yarn release --tag=next                      -> publishes \`@excalidraw\` packages with "next" tag and version "-[hash]" suffix
+  - yarn release --tag=next --non-interactive    -> skips interactive prompts (runs on CI/CD), otherwise same as above
+  - yarn release --tag=latest --version=0.19.0   -> publishes \`@excalidraw\` packages with "latest" tag and version "0.19.0" & prepares changelog for the release`);
+
+      process.exit(0);
+    }
+
+    if (/--tag=/.test(argument)) {
+      tag = argument.split("=")[1];
+    }
+
+    if (/--version=/.test(argument)) {
+      version = argument.split("=")[1];
+    }
+
+    if (/--non-interactive/.test(argument)) {
+      nonInteractive = true;
+    }
+  }
+
+  if (tag !== "latest" && tag !== "next" && tag !== "test") {
+    console.error(`Unsupported tag "${tag}", use "latest", "next" or "test".`);
+    process.exit(1);
+  }
+
+  if (tag === "latest" && !version) {
+    console.error("Pass the version to make the latest stable release!");
+    process.exit(1);
+  }
+
+  if (!version) {
+    // set the next version based on the excalidraw package version + commit hash
+    const excalidrawPackageVersion = require(getPackageJsonPath(
+      "excalidraw",
+    )).version;
+
+    const hash = getShortCommitHash();
+
+    if (!excalidrawPackageVersion.includes(hash)) {
+      version = `${excalidrawPackageVersion}-${hash}`;
+    } else {
+      // ensuring idempotency
+      version = excalidrawPackageVersion;
+    }
+  }
+
+  console.info(`Running with tag "${tag}" and version "${version}"...`);
+
+  return [tag, version, nonInteractive];
+};
+
+const validatePackageName = (packageName) => {
+  if (!PACKAGES.includes(packageName)) {
+    console.error(`Package "${packageName}" not found!`);
     process.exit(1);
   }
 };
 
-const release = () => {
-  publish();
-  console.info(`Published ${pkg.version}!`);
+const getPackageJsonPath = (packageName) => {
+  validatePackageName(packageName);
+  return path.resolve(PACKAGES_DIR, packageName, "package.json");
 };
 
-release();
+const updatePackageJsons = (nextVersion) => {
+  const packageJsons = new Map();
+
+  for (const packageName of PACKAGES) {
+    const pkg = require(getPackageJsonPath(packageName));
+
+    pkg.version = nextVersion;
+
+    if (pkg.dependencies) {
+      for (const dependencyName of PACKAGES) {
+        if (!pkg.dependencies[`@excalidraw/${dependencyName}`]) {
+          continue;
+        }
+
+        pkg.dependencies[`@excalidraw/${dependencyName}`] = nextVersion;
+      }
+    }
+
+    packageJsons.set(packageName, `${JSON.stringify(pkg, null, 2)}\n`);
+  }
+
+  // modify once, to avoid inconsistent state
+  for (const packageName of PACKAGES) {
+    const content = packageJsons.get(packageName);
+    fs.writeFileSync(getPackageJsonPath(packageName), content, "utf-8");
+  }
+};
+
+const getShortCommitHash = () => {
+  return execSync("git rev-parse --short HEAD").toString().trim();
+};
+
+const askToCommit = (tag, nextVersion) => {
+  if (tag !== "latest") {
+    return Promise.resolve();
+  }
+
+  return new Promise((resolve) => {
+    const rl = require("readline").createInterface({
+      input: process.stdin,
+      output: process.stdout,
+    });
+
+    rl.question(
+      "Do you want to commit these changes to git? (Y/n): ",
+      (answer) => {
+        rl.close();
+
+        if (answer.toLowerCase() === "y") {
+          execSync(`git add -u`);
+          execSync(
+            `git commit -m "chore: release @excalidraw/excalidraw@${nextVersion} 🎉"`,
+          );
+        } else {
+          console.warn(
+            "Skipping commit. Don't forget to commit manually later!",
+          );
+        }
+
+        resolve();
+      },
+    );
+  });
+};
+
+const buildPackages = () => {
+  console.info("Running yarn install...");
+  execSync(`yarn --frozen-lockfile`, { stdio: "inherit" });
+
+  console.info("Removing existing build artifacts...");
+  execSync(`yarn rm:build`, { stdio: "inherit" });
+
+  for (const packageName of PACKAGES) {
+    console.info(`Building "@excalidraw/${packageName}"...`);
+    execSync(`yarn run build:esm`, {
+      cwd: path.resolve(PACKAGES_DIR, packageName),
+      stdio: "inherit",
+    });
+  }
+};
+
+const askToPublish = (tag, version) => {
+  return new Promise((resolve) => {
+    const rl = require("readline").createInterface({
+      input: process.stdin,
+      output: process.stdout,
+    });
+
+    rl.question(
+      "Do you want to publish these changes to npm? (Y/n): ",
+      (answer) => {
+        rl.close();
+
+        if (answer.toLowerCase() === "y") {
+          publishPackages(tag, version);
+        } else {
+          console.info("Skipping publish.");
+        }
+
+        resolve();
+      },
+    );
+  });
+};
+
+const publishPackages = (tag, version) => {
+  for (const packageName of PACKAGES) {
+    execSync(`yarn publish --tag ${tag}`, {
+      cwd: path.resolve(PACKAGES_DIR, packageName),
+      stdio: "inherit",
+    });
+
+    console.info(
+      `Published "@excalidraw/${packageName}@${tag}" with version "${version}"! 🎉`,
+    );
+  }
+};
+
+/** main */
+(async () => {
+  const [tag, version, nonInteractive] = getArguments();
+
+  buildPackages();
+
+  if (tag === "latest") {
+    await updateChangelog(version);
+  }
+
+  updatePackageJsons(version);
+
+  if (nonInteractive) {
+    publishPackages(tag, version);
+  } else {
+    await askToCommit(tag, version);
+    await askToPublish(tag, version);
+  }
+})();

+ 4 - 2
scripts/updateChangelog.js

@@ -20,14 +20,16 @@ const headerForType = {
   perf: "Performance",
   build: "Build",
 };
+
 const badCommits = [];
 const getCommitHashForLastVersion = async () => {
   try {
-    const commitMessage = `"release @excalidraw/excalidraw@${lastVersion}"`;
+    const commitMessage = `"release @excalidraw/excalidraw"`;
     const { stdout } = await exec(
       `git log --format=format:"%H" --grep=${commitMessage}`,
     );
-    return stdout;
+    // take commit hash from latest release
+    return stdout.split(/\r?\n/)[0];
   } catch (error) {
     console.error(error);
   }