diff --git a/.github/workflows/validate-new-notebooks.yml b/.github/workflows/validate-new-notebooks.yml index b1dc31f0..f551a178 100644 --- a/.github/workflows/validate-new-notebooks.yml +++ b/.github/workflows/validate-new-notebooks.yml @@ -20,8 +20,28 @@ on: paths: - 'docs/docs/**' - 'examples/**' + - 'deno.json' workflow_dispatch: +env: + NODE_VERSION: "22.4.1" + +jobs: + validate-dep-files: + name: Validate Deno and Node dependencies in sync + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ env.NODE_VERSION }} + uses: actions/setup-node@v3 + with: + node-version: ${{ env.NODE_VERSION }} + cache: 'yarn' + - name: Install dependencies + run: yarn install --immutable + - name: Validate + run: yarn tsx --experimental-wasm-modules ./scripts/validate_deps_sync.ts + jobs: validate-new-notebooks: runs-on: ubuntu-latest diff --git a/package.json b/package.json index 56fdbb92..729149ce 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "esm-hook": "^0.1.4", "readline": "^1.3.0", "release-it": "^17.6.0", - "semver": "^7.5.4", + "semver": "^7.6.3", "tslab": "^1.0.21", "tsx": "^4.7.0", "turbo": "canary", diff --git a/scripts/validate_deps_sync.ts b/scripts/validate_deps_sync.ts new file mode 100644 index 00000000..bb41e9e8 --- /dev/null +++ b/scripts/validate_deps_sync.ts @@ -0,0 +1,64 @@ +import fs from "fs"; +import semver from "semver"; + +type DenoJson = { + imports: Record; +}; + +type PackageJson = { + // Only adding the fields we care about + devDependencies: Record; +}; + +function main() { + const denoJson: DenoJson = JSON.parse(fs.readFileSync("deno.json", "utf-8")); + const packageJson: PackageJson = JSON.parse( + fs.readFileSync("examples/package.json", "utf-8") + ); + + // Parse the dependency names and versions from the deno.json file + const denoDeps = Object.entries(denoJson.imports).map(([name, version]) => { + let depName = name.endsWith("/") ? name.slice(0, -1) : name; + let depVersion = version + .replace(/^npm:\/?(.*)/g, "$1") + .replace(depName, ""); + depVersion = depVersion.replace(/@.*$/, "").replace(/\/$/, ""); + if (!depVersion || depVersion === "") { + depVersion = "latest"; + } + + // `latest` is not a valid semver, do not validate it + if (depVersion !== "latest" && !semver.valid(depVersion)) { + throw new Error(`Invalid version for ${depName}: ${depVersion}`); + } + return { name: depName, version: depVersion }; + }); + + // Match the dependencies to those in the `package.json` file, and + // use the `semver` package to verify the versions are compatible + denoDeps.forEach((denoDep) => { + if (!(denoDep.name in packageJson.devDependencies)) { + throw new Error( + `Dependency ${denoDep.name} is not in the package.json file` + ); + } + + const packageVersion = packageJson.devDependencies[denoDep.name]; + if (denoDep.version === "latest") { + // If the deno version is latest, we can not validate it. Assume it is correct + return; + } + const cleanedPackageJsonVersion = + semver.clean(packageVersion) ?? packageVersion; + if ( + cleanedPackageJsonVersion !== denoDep.version && + !semver.gte(cleanedPackageJsonVersion, denoDep.version) + ) { + throw new Error( + `Version mismatch for ${denoDep.name}: package.json version ${cleanedPackageJsonVersion} is less than deno.json version ${denoDep.version}` + ); + } + }); +} + +main(); diff --git a/yarn.lock b/yarn.lock index d7e28453..90e44576 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8779,7 +8779,7 @@ __metadata: esm-hook: ^0.1.4 readline: ^1.3.0 release-it: ^17.6.0 - semver: ^7.5.4 + semver: ^7.6.3 tslab: ^1.0.21 tsx: ^4.7.0 turbo: canary