Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
46a1cac
feat: add support for deno
google-labs-jules[bot] Sep 5, 2025
5ab62ab
feat: add support for deno
google-labs-jules[bot] Sep 5, 2025
1bff91e
chore: formatting
fry69 Sep 5, 2025
d8e02b5
fix: types and naming
fry69 Sep 5, 2025
00da70c
chore: formatting
fry69 Sep 5, 2025
d3e7c47
fix: tests
fry69 Sep 5, 2025
8915cc1
feat: expand tests for deno.json and deno.jsonc
google-labs-jules[bot] Sep 5, 2025
3b19989
feat: expand tests for deno.json and deno.jsonc
google-labs-jules[bot] Sep 5, 2025
ebcaa91
chore: formatting
fry69 Sep 5, 2025
856d8eb
Merge pull request #2 from fry69/feat/expand-deno-tests
fry69 Sep 5, 2025
f8fc565
Refactor DenoTool to remove redundant code
google-labs-jules[bot] Sep 5, 2025
6f3a34d
Merge pull request #3 from fry69/refactor-deno-tool
fry69 Sep 5, 2025
ec378e5
test: add deno monorepo root test
fry69 Sep 5, 2025
7564a73
feat: add deno support to the cli
google-labs-jules[bot] Sep 6, 2025
2d57dfa
feat: add deno support to the cli
google-labs-jules[bot] Sep 6, 2025
0d72550
fix: types
fry69 Sep 6, 2025
08ce539
Merge pull request #4 from fry69/deno-cli-support
fry69 Sep 6, 2025
196d6be
feat: Add Deno support to the manypkg CLI
google-labs-jules[bot] Sep 6, 2025
11e559a
feat: Add Deno support to manypkg checks
google-labs-jules[bot] Sep 6, 2025
d75638e
Merge pull request #5 from fry69/feat/deno-support-1
fry69 Sep 6, 2025
41e7eb1
chore: formatting
fry69 Sep 6, 2025
b3ab250
chore: fix types
fry69 Sep 6, 2025
c24e66f
refactor: use package-based imports for cross-package references
google-labs-jules[bot] Sep 6, 2025
26b6b0d
feat: update all cross-package imports for DenoJSON
google-labs-jules[bot] Sep 6, 2025
da3ade6
refactor: use discriminated union for PackageJSON and DenoJSON
google-labs-jules[bot] Sep 6, 2025
445c38d
fix: resolve all tsc errors
google-labs-jules[bot] Sep 6, 2025
40917b3
Merge pull request #6 from fry69/refactor/cross-package-imports
fry69 Sep 6, 2025
7d79e88
chore: formatting
fry69 Sep 6, 2025
e992f03
chore: remove workspace dependency
fry69 Sep 6, 2025
10d5908
chore: fix tools dependency
fry69 Sep 6, 2025
eadbf91
feat: Implement functional `fix` command for Deno workspaces
google-labs-jules[bot] Sep 7, 2025
28cfe44
chore: formatting
fry69 Sep 7, 2025
3bce0e0
Merge pull request #7 from fry69/feat/deno-support-fixes
fry69 Sep 7, 2025
5ee01b0
chore: fix jsonc-parser version
fry69 Sep 7, 2025
b911ac7
Refactor: Use generic Package<T> type to improve type safety
google-labs-jules[bot] Sep 7, 2025
24c52ba
chore: cleanup
fry69 Sep 7, 2025
62a9c1f
Merge pull request #8 from fry69/better-types
fry69 Sep 7, 2025
120a8c1
chore: consolidate imports
fry69 Sep 7, 2025
7326401
chore: remove unused import
fry69 Sep 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions __fixtures__/basic-deno/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": [
"packages/*"
]
}
5 changes: 5 additions & 0 deletions __fixtures__/basic-deno/packages/package-one/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@scope/package-one",
"version": "1.0.0",
"exports": "./mod.ts"
}
24 changes: 24 additions & 0 deletions __fixtures__/complex-deno/deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"workspace": [
"packages/*"
],
"imports": {
"@/": "./",
"class-variance-authority": "npm:class-variance-authority@^0.7.1",
"clsx": "npm:clsx@^2.1.1",
"cmdk": "https://esm.sh/[email protected]?alias=react:preact/compat&external=preact,@radix-ui/react-dialog&target=es2022",
"fresh": "jsr:@fresh/core@^2.0.0-beta.3",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^0.9.11",
"lucide-preact": "npm:lucide-preact@^0.539.0",
"postcss": "npm:postcss@^8.5.6",
"preact": "npm:preact@^10.27.1",
"@preact/signals": "npm:@preact/signals@^2.3.1",
"/*": "TODO: cleanup",
"@radix-ui/react-accordion": "https://esm.sh/@radix-ui/[email protected]?alias=react:preact/compat&external=preact,@radix-ui/react-collapsible,@radix-ui/react-collection,@radix-ui/react-compose-refs,@radix-ui/react-context,@radix-ui/react-direction,@radix-ui/react-id,@radix-ui/react-primitive,@radix-ui/react-use-controllable-state&target=es2022",
"tailwind-merge": "npm:tailwind-merge@^3.3.1",
"tw-animate-css": "npm:tw-animate-css@^1.3.7",
"vite": "npm:vite@^7.1.4",
"tailwindcss": "npm:tailwindcss@^4.1.12",
"@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.12"
}
}
5 changes: 5 additions & 0 deletions __fixtures__/complex-deno/packages/package-one/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@scope/package-one",
"version": "1.0.0",
"exports": "./mod.ts"
}
35 changes: 35 additions & 0 deletions __fixtures__/complex-deno/packages/package-three/deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@scope/package-three",
"version": "1.0.0",
"exports": "./mod.ts",
// "broken:", "this should not be here",
"exclude": [
"**/_fresh/*"
],
"compilerOptions": {
"lib": [
"dom",
"dom.asynciterable",
"dom.iterable",
"deno.ns"
],
"jsx": "precompile",
"jsxImportSource": "preact",
"jsxPrecompileSkipElements": [
"a",
"img",
"source",
"body",
"html",
"head",
"title",
"meta",
"script",
"link",
"style",
"base",
"noscript",
"template"
]
}
}
21 changes: 21 additions & 0 deletions __fixtures__/complex-deno/packages/package-two/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@scope/package-two",
"version": "1.0.0",
"exports": "./mod.ts",
"tasks": {
"check": "deno fmt --check . && deno lint . && deno check",
"dev": "vite",
"build": "vite build",
"start": "deno serve -A _fresh/server.js",
"update": "deno run -A -r jsr:@fresh/update .",
"check-deps": "deno run -A jsr:@check/deps --allow-unused"
},
"lint": {
"rules": {
"tags": [
"fresh",
"recommended"
]
}
}
}
5 changes: 5 additions & 0 deletions __fixtures__/deno-external-mismatch/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": [
"packages/*"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@scope/package-one",
"version": "1.0.0",
"imports": {
"@oak/oak": "jsr:@oak/oak@^14.2.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@scope/package-two",
"version": "1.0.0",
"imports": {
"@oak/oak": "jsr:@oak/oak@^13.0.0"
}
}
5 changes: 5 additions & 0 deletions __fixtures__/deno-internal-mismatch/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": [
"packages/*"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@scope/package-one",
"version": "1.0.0",
"imports": {
"@scope/package-two": "jsr:@scope/package-two@^2.0.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "@scope/package-two",
"version": "1.0.0"
}
5 changes: 5 additions & 0 deletions __fixtures__/deno-invalid-package-name/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": [
"packages/*"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "package-one",
"version": "1.0.0"
}
5 changes: 5 additions & 0 deletions __fixtures__/deno-unsorted-dependencies/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"workspace": [
"packages/*"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@scope/package-one",
"version": "1.0.0",
"imports": {
"zod": "npm:zod@^3.23.8",
"oak": "jsr:@oak/oak@^14.2.0"
}
}
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"dependencies": {
"@manypkg/get-packages": "^3.1.0",
"detect-indent": "^7.0.1",
"jsonc-parser": "^3.3.1",
"normalize-path": "^3.0.0",
"p-limit": "^6.2.0",
"package-json": "^10.0.1",
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/__fixtures__/deno-fix-e2e/deno.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// This is the root config file.
{
"workspace": [
"packages/*"
],
"imports": {
// We should use a consistent version of oak.
"@oak/oak": "jsr:@oak/oak@^14.2.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Package one config.
{
"name": "@scope/package-one",
"version": "1.0.0"
// No dependencies here.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Package two has a mismatched dependency.
{
"name": "@scope/package-two",
"version": "1.0.0",
"imports": {
// This should be fixed to ^14.2.0
"@oak/oak": "jsr:@oak/oak@^13.0.0"
}
}
41 changes: 41 additions & 0 deletions packages/cli/src/__tests__/fix.deno.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, test, expect } from "vitest";
import fixturez from "fixturez";
import path from "node:path";
import fs from "node:fs/promises";
import { exec } from "tinyexec";
import { parse } from "jsonc-parser";

const f = fixturez(__dirname);

function executeBin(path: string, command: string, ...args: string[]) {
return exec("node", [require.resolve("../../bin.js"), command, ...args], {
nodeOptions: {
cwd: path,
env: {
...process.env,
NODE_OPTIONS: "--experimental-strip-types",
},
},
});
}

describe("deno fix e2e", () => {
test("should fix a mismatched dependency in a deno.jsonc file", async () => {
const fixtureDir = f.copy("deno-fix-e2e");

await executeBin(fixtureDir, "fix");

const fixedFileContent = await fs.readFile(
path.join(fixtureDir, "packages/package-two/deno.jsonc"),
"utf-8"
);

const fixedJson = parse(fixedFileContent);
expect(fixedJson.imports["@oak/oak"]).toBe("jsr:@oak/oak@^14.2.0");

// Also check that comments are gone, since that's the current implementation
expect(fixedFileContent).not.toContain(
"// This should be fixed to ^14.2.0"
);
}, 30000);
});
82 changes: 66 additions & 16 deletions packages/cli/src/checks/EXTERNAL_MISMATCH.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,103 @@ import {
getMostCommonRangeMap,
NORMAL_DEPENDENCY_TYPES,
} from "./utils.ts";
import type { Package } from "@manypkg/get-packages";
import {
isDenoPackage,
isNodePackage,
type Package,
} from "@manypkg/get-packages";
import { validRange } from "semver";

const dependencyRegexp =
/^(?<protocol>jsr:|npm:|https:|http:)(?:\/\/|\/)?(?<name>@?[^@\s]+)@?(?<version>[^?\s/]+)?/;

type ErrorType = {
type: "EXTERNAL_MISMATCH";
workspace: Package;
workspace: Package<any>;
dependencyName: string;
dependencyRange: string;
mostCommonDependencyRange: string;
dependencyAlias?: string;
};

export default makeCheck<ErrorType>({
validate: (workspace, allWorkspace) => {
let errors: ErrorType[] = [];
let mostCommonRangeMap = getMostCommonRangeMap(allWorkspace);
for (let depType of NORMAL_DEPENDENCY_TYPES) {
let deps = workspace.packageJson[depType];

if (deps) {
for (let depName in deps) {
let range = deps[depName];
let mostCommonRange = mostCommonRangeMap.get(depName);
if (isDenoPackage(workspace)) {
if (workspace.dependencies) {
for (let depAlias in workspace.dependencies) {
let dep = workspace.dependencies[depAlias];
let mostCommonRange = mostCommonRangeMap.get(dep.name);
if (
mostCommonRange !== undefined &&
mostCommonRange !== range &&
validRange(range)
dep.version &&
mostCommonRange !== dep.version &&
validRange(dep.version)
) {
errors.push({
type: "EXTERNAL_MISMATCH",
workspace,
dependencyName: depName,
dependencyRange: range,
dependencyName: dep.name,
dependencyRange: dep.version,
mostCommonDependencyRange: mostCommonRange,
dependencyAlias: depAlias,
});
}
}
}
} else if (isNodePackage(workspace)) {
for (let depType of NORMAL_DEPENDENCY_TYPES) {
let deps = workspace.packageJson[depType];

if (deps) {
for (let depName in deps) {
let range = deps[depName];
let mostCommonRange = mostCommonRangeMap.get(depName);
if (
mostCommonRange !== undefined &&
mostCommonRange !== range &&
validRange(range)
) {
errors.push({
type: "EXTERNAL_MISMATCH",
workspace,
dependencyName: depName,
dependencyRange: range,
mostCommonDependencyRange: mostCommonRange,
});
}
}
}
}
}
return errors;
},
fix: (error) => {
for (let depType of NORMAL_DEPENDENCY_TYPES) {
let deps = error.workspace.packageJson[depType];
if (deps && deps[error.dependencyName]) {
deps[error.dependencyName] = error.mostCommonDependencyRange;
if (isDenoPackage(error.workspace) && error.dependencyAlias) {
const imports = error.workspace.packageJson.imports;
if (imports && imports[error.dependencyAlias]) {
const originalSpecifier = imports[error.dependencyAlias];
const match = originalSpecifier.match(dependencyRegexp);
if (match && match.groups) {
const { protocol, name } = match.groups;
// The name can sometimes include the @ symbol, which we want to keep, but not if it's a trailing one from the version separator
const cleanName = name.endsWith("@") ? name.slice(0, -1) : name;
imports[error.dependencyAlias] =
`${protocol}${cleanName}@${error.mostCommonDependencyRange}`;
}
}
} else if (isNodePackage(error.workspace)) {
for (let depType of NORMAL_DEPENDENCY_TYPES) {
let deps = error.workspace.packageJson[depType];
if (deps && deps[error.dependencyName]) {
deps[error.dependencyName] = error.mostCommonDependencyRange;
}
}
}
// Deno doesn't have an install step, but this signals that a change was made.
// The install function itself is now a no-op for Deno.
return { requiresInstall: true };
},
print: (error) =>
Expand Down
Loading