From 1707fa2c13a48cc303eba4a4b58fba229a2e4a64 Mon Sep 17 00:00:00 2001 From: Christian Svensson Date: Mon, 18 Aug 2025 22:08:46 +0200 Subject: [PATCH 1/2] feat(@angular/cli): allow using Deno as package manager --- .../cli/lib/config/workspace-schema.json | 4 +- .../src/commands/update/schematic/schema.json | 2 +- .../cli/src/utilities/package-manager.ts | 42 ++++++++++++++++--- packages/angular/create/README.md | 6 +++ packages/angular/create/src/index.ts | 2 +- .../tasks/package-manager/executor.ts | 6 +++ .../schematics_cli/blank/schema.json | 2 +- .../schematics_cli/schematic/schema.json | 2 +- .../schematics/angular/ng-new/schema.json | 2 +- .../schematics/angular/workspace/schema.json | 2 +- tests/legacy-cli/e2e.bzl | 5 ++- tests/legacy-cli/e2e/setup/100-global-cli.ts | 1 + .../e2e/tests/misc/create-angular.ts | 6 ++- tests/legacy-cli/e2e/utils/packages.ts | 12 +++++- tests/legacy-cli/e2e/utils/process.ts | 4 ++ 15 files changed, 80 insertions(+), 18 deletions(-) diff --git a/packages/angular/cli/lib/config/workspace-schema.json b/packages/angular/cli/lib/config/workspace-schema.json index 0c551dc4fb14..9e8ed1e839b4 100644 --- a/packages/angular/cli/lib/config/workspace-schema.json +++ b/packages/angular/cli/lib/config/workspace-schema.json @@ -47,7 +47,7 @@ "packageManager": { "description": "Specify which package manager tool to use.", "type": "string", - "enum": ["npm", "cnpm", "yarn", "pnpm", "bun"] + "enum": ["npm", "cnpm", "yarn", "pnpm", "bun", "deno"] }, "warnings": { "description": "Control CLI specific console warnings", @@ -101,7 +101,7 @@ "packageManager": { "description": "Specify which package manager tool to use.", "type": "string", - "enum": ["npm", "cnpm", "yarn", "pnpm", "bun"] + "enum": ["npm", "cnpm", "yarn", "pnpm", "bun", "deno"] }, "warnings": { "description": "Control CLI specific console warnings", diff --git a/packages/angular/cli/src/commands/update/schematic/schema.json b/packages/angular/cli/src/commands/update/schematic/schema.json index 649d2f5db01f..7105172fd56d 100644 --- a/packages/angular/cli/src/commands/update/schematic/schema.json +++ b/packages/angular/cli/src/commands/update/schematic/schema.json @@ -57,7 +57,7 @@ "description": "The preferred package manager configuration files to use for registry settings.", "type": "string", "default": "npm", - "enum": ["npm", "yarn", "cnpm", "pnpm", "bun"] + "enum": ["npm", "yarn", "cnpm", "pnpm", "bun", "deno"] } }, "required": [] diff --git a/packages/angular/cli/src/utilities/package-manager.ts b/packages/angular/cli/src/utilities/package-manager.ts index d95205f95184..1adc19b07a71 100644 --- a/packages/angular/cli/src/utilities/package-manager.ts +++ b/packages/angular/cli/src/utilities/package-manager.ts @@ -148,6 +148,14 @@ export class PackageManagerUtils { prefix: '--cwd', noLockfile: '', }; + case PackageManager.Deno: + return { + saveDev: '--dev', + install: 'add', + installAll: 'install', + prefix: '--root', + noLockfile: '--no-lock', + }; default: return { saveDev: '--save-dev', @@ -194,7 +202,8 @@ export class PackageManagerUtils { @memoize private getVersion(name: PackageManager): string | undefined { try { - return execSync(`${name} --version`, { + const versionArg = name !== PackageManager.Deno ? '--version' : '-v'; + const version = execSync(`${name} ${versionArg}`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], env: { @@ -204,6 +213,13 @@ export class PackageManagerUtils { NPM_CONFIG_UPDATE_NOTIFIER: 'false', }, }).trim(); + + if (name === PackageManager.Deno) { + // Deno CLI outputs "deno 2.4.4" + return version.replace('deno ', ''); + } + + return version; } catch { return undefined; } @@ -220,6 +236,7 @@ export class PackageManagerUtils { const hasYarnLock = this.hasLockfile(PackageManager.Yarn); const hasPnpmLock = this.hasLockfile(PackageManager.Pnpm); const hasBunLock = this.hasLockfile(PackageManager.Bun); + const hasDenoLock = this.hasLockfile(PackageManager.Deno); // PERF NOTE: `this.getVersion` spawns the package a the child_process which can take around ~300ms at times. // Therefore, we should only call this method when needed. IE: don't call `this.getVersion(PackageManager.Pnpm)` unless truly needed. @@ -227,7 +244,13 @@ export class PackageManagerUtils { if (hasNpmLock) { // Has NPM lock file. - if (!hasYarnLock && !hasPnpmLock && !hasBunLock && this.getVersion(PackageManager.Npm)) { + if ( + !hasYarnLock && + !hasPnpmLock && + !hasBunLock && + !hasDenoLock && + this.getVersion(PackageManager.Npm) + ) { // Only NPM lock file and NPM binary is available. return PackageManager.Npm; } @@ -242,6 +265,9 @@ export class PackageManagerUtils { } else if (hasBunLock && this.getVersion(PackageManager.Bun)) { // Bun lock file and Bun binary is available. return PackageManager.Bun; + } else if (hasDenoLock && this.getVersion(PackageManager.Deno)) { + // Deno lock file and Deno binary is available. + return PackageManager.Deno; } } @@ -250,13 +276,16 @@ export class PackageManagerUtils { const hasYarn = !!this.getVersion(PackageManager.Yarn); const hasPnpm = !!this.getVersion(PackageManager.Pnpm); const hasBun = !!this.getVersion(PackageManager.Bun); + const hasDeno = !!this.getVersion(PackageManager.Deno); - if (hasYarn && !hasPnpm && !hasBun) { + if (hasYarn && !hasPnpm && !hasBun && !hasDeno) { return PackageManager.Yarn; - } else if (hasPnpm && !hasYarn && !hasBun) { + } else if (hasPnpm && !hasYarn && !hasBun && !hasDeno) { return PackageManager.Pnpm; - } else if (hasBun && !hasYarn && !hasPnpm) { + } else if (hasBun && !hasYarn && !hasPnpm && !hasDeno) { return PackageManager.Bun; + } else if (hasDeno && !hasYarn && !hasPnpm && !hasBun) { + return PackageManager.Deno; } } @@ -277,6 +306,9 @@ export class PackageManagerUtils { case PackageManager.Bun: lockfileName = 'bun.lockb'; break; + case PackageManager.Deno: + lockfileName = 'deno.lock'; + break; case PackageManager.Npm: default: lockfileName = 'package-lock.json'; diff --git a/packages/angular/create/README.md b/packages/angular/create/README.md index 46135476e406..45831e08077b 100644 --- a/packages/angular/create/README.md +++ b/packages/angular/create/README.md @@ -29,3 +29,9 @@ pnpm create @angular [project-name] [...options] ``` bun create @angular [project-name] [...options] ``` + +### deno + +``` +deno init --npm @angular [project-name] [...options] +``` diff --git a/packages/angular/create/src/index.ts b/packages/angular/create/src/index.ts index 47343ae9014d..89fa1aa97c42 100644 --- a/packages/angular/create/src/index.ts +++ b/packages/angular/create/src/index.ts @@ -17,7 +17,7 @@ const hasPackageManagerArg = args.some((a) => a.startsWith('--package-manager')) if (!hasPackageManagerArg) { // Ex: yarn/1.22.18 npm/? node/v16.15.1 linux x64 const packageManager = process.env['npm_config_user_agent']?.split('/')[0]; - if (packageManager && ['npm', 'pnpm', 'yarn', 'cnpm', 'bun'].includes(packageManager)) { + if (packageManager && ['npm', 'pnpm', 'yarn', 'cnpm', 'bun', 'deno'].includes(packageManager)) { args.push('--package-manager', packageManager); } } diff --git a/packages/angular_devkit/schematics/tasks/package-manager/executor.ts b/packages/angular_devkit/schematics/tasks/package-manager/executor.ts index e0fa17ee6a7b..b0451f7ea4af 100644 --- a/packages/angular_devkit/schematics/tasks/package-manager/executor.ts +++ b/packages/angular_devkit/schematics/tasks/package-manager/executor.ts @@ -45,6 +45,12 @@ const packageManagers: { [name: string]: PackageManagerProfile } = { installPackage: 'add', }, }, + 'deno': { + commands: { + installAll: 'install', + installPackage: 'add', + }, + }, 'pnpm': { commands: { installAll: 'install', diff --git a/packages/angular_devkit/schematics_cli/blank/schema.json b/packages/angular_devkit/schematics_cli/blank/schema.json index 9b4ba24fd945..bd280e71a4a1 100644 --- a/packages/angular_devkit/schematics_cli/blank/schema.json +++ b/packages/angular_devkit/schematics_cli/blank/schema.json @@ -15,7 +15,7 @@ "packageManager": { "description": "The package manager used to install dependencies.", "type": "string", - "enum": ["npm", "yarn", "pnpm", "cnpm", "bun"], + "enum": ["npm", "yarn", "pnpm", "cnpm", "bun", "deno"], "default": "npm" }, "author": { diff --git a/packages/angular_devkit/schematics_cli/schematic/schema.json b/packages/angular_devkit/schematics_cli/schematic/schema.json index 85aed7f7eba6..e38e1318fbb5 100644 --- a/packages/angular_devkit/schematics_cli/schematic/schema.json +++ b/packages/angular_devkit/schematics_cli/schematic/schema.json @@ -15,7 +15,7 @@ "packageManager": { "description": "The package manager used to install dependencies.", "type": "string", - "enum": ["npm", "yarn", "pnpm", "cnpm", "bun"], + "enum": ["npm", "yarn", "pnpm", "cnpm", "bun", "deno"], "default": "npm" } }, diff --git a/packages/schematics/angular/ng-new/schema.json b/packages/schematics/angular/ng-new/schema.json index 8764f307ef01..f1ae26c611a8 100644 --- a/packages/schematics/angular/ng-new/schema.json +++ b/packages/schematics/angular/ng-new/schema.json @@ -126,7 +126,7 @@ "packageManager": { "description": "The package manager used to install dependencies.", "type": "string", - "enum": ["npm", "yarn", "pnpm", "cnpm", "bun"] + "enum": ["npm", "yarn", "pnpm", "cnpm", "bun", "deno"] }, "standalone": { "description": "Creates an application based upon the standalone API, without NgModules.", diff --git a/packages/schematics/angular/workspace/schema.json b/packages/schematics/angular/workspace/schema.json index cd09b5d870c0..bd86fd9dc910 100644 --- a/packages/schematics/angular/workspace/schema.json +++ b/packages/schematics/angular/workspace/schema.json @@ -40,7 +40,7 @@ "packageManager": { "description": "The package manager to use for installing dependencies.", "type": "string", - "enum": ["npm", "yarn", "pnpm", "cnpm", "bun"] + "enum": ["npm", "yarn", "pnpm", "cnpm", "bun", "deno"] } }, "required": ["name", "version"] diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl index b275b7185e30..e136c72a7cb5 100644 --- a/tests/legacy-cli/e2e.bzl +++ b/tests/legacy-cli/e2e.bzl @@ -84,6 +84,7 @@ def e2e_suites(name, runner, data): _e2e_suite(name, runner, "npm", data, toolchain_name, toolchain) _e2e_suite(name, runner, "bun", data, toolchain_name, toolchain) + _e2e_suite(name, runner, "deno", data, toolchain_name, toolchain) _e2e_suite(name, runner, "pnpm", data, toolchain_name, toolchain) _e2e_suite(name, runner, "yarn", data, toolchain_name, toolchain) _e2e_suite(name, runner, "esbuild", data, toolchain_name, toolchain) @@ -140,7 +141,7 @@ def _e2e_tests(name, runner, toolchain, **kwargs): def _e2e_suite(name, runner, type, data, toolchain_name = "", toolchain = None): """ - Setup a predefined test suite (yarn|pnpm|bun|esbuild|saucelabs|npm). + Setup a predefined test suite (yarn|pnpm|bun|deno|esbuild|saucelabs|npm). """ args = [] tests = None @@ -149,7 +150,7 @@ def _e2e_suite(name, runner, type, data, toolchain_name = "", toolchain = None): if toolchain_name: toolchain_name = "_" + toolchain_name - if type == "yarn" or type == "bun" or type == "pnpm": + if type == "yarn" or type == "bun" or type == "deno" or type == "pnpm": args.append("--package-manager=%s" % type) args.append("--esbuild") tests = PACKAGE_MANAGER_SUBSET_TESTS diff --git a/tests/legacy-cli/e2e/setup/100-global-cli.ts b/tests/legacy-cli/e2e/setup/100-global-cli.ts index 63db2d365a4a..787df3696d33 100644 --- a/tests/legacy-cli/e2e/setup/100-global-cli.ts +++ b/tests/legacy-cli/e2e/setup/100-global-cli.ts @@ -7,6 +7,7 @@ const PACKAGE_MANAGER_VERSION = { 'yarn': '1.22.22', 'pnpm': '9.3.0', 'bun': '1.1.13', + 'deno': '2.4.4', }; export default async function () { diff --git a/tests/legacy-cli/e2e/tests/misc/create-angular.ts b/tests/legacy-cli/e2e/tests/misc/create-angular.ts index acbdf135359b..047d532a9a49 100644 --- a/tests/legacy-cli/e2e/tests/misc/create-angular.ts +++ b/tests/legacy-cli/e2e/tests/misc/create-angular.ts @@ -2,7 +2,7 @@ import { equal } from 'node:assert'; import { join, resolve } from 'node:path'; import { expectFileToExist, readFile, rimraf } from '../../utils/fs'; import { getActivePackageManager } from '../../utils/packages'; -import { silentBun, silentNpm, silentPnpm, silentYarn } from '../../utils/process'; +import { silentBun, silentDeno, silentNpm, silentPnpm, silentYarn } from '../../utils/process'; export default async function () { const currentDirectory = process.cwd(); @@ -25,6 +25,10 @@ export default async function () { case 'bun': await silentBun('create', '@angular', projectName, '--style=scss'); + break; + case 'deno': + await silentDeno('init', '--npm', '@angular', projectName, '--style=scss'); + break; case 'pnpm': await silentPnpm('create', '@angular', projectName, '--style=scss'); diff --git a/tests/legacy-cli/e2e/utils/packages.ts b/tests/legacy-cli/e2e/utils/packages.ts index 087d771f14b8..8c977170fd68 100644 --- a/tests/legacy-cli/e2e/utils/packages.ts +++ b/tests/legacy-cli/e2e/utils/packages.ts @@ -1,5 +1,5 @@ import { getGlobalVariable } from './env'; -import { ProcessOutput, silentBun, silentNpm, silentPnpm, silentYarn } from './process'; +import { ProcessOutput, silentBun, silentDeno, silentNpm, silentPnpm, silentYarn } from './process'; export interface PkgInfo { readonly name: string; @@ -7,7 +7,7 @@ export interface PkgInfo { readonly path: string; } -export function getActivePackageManager(): 'npm' | 'yarn' | 'bun' | 'pnpm' { +export function getActivePackageManager(): 'npm' | 'yarn' | 'bun' | 'deno' | 'pnpm' { return getGlobalVariable('package-manager'); } @@ -29,6 +29,9 @@ export async function installWorkspacePackages(options?: { force?: boolean }): P case 'bun': await silentBun('install'); break; + case 'deno': + await silentDeno('install'); + break; } } @@ -41,6 +44,8 @@ export function installPackage(specifier: string, registry?: string): Promise { case 'bun': await silentBun('remove', name); break; + case 'deno': + await silentDeno('remove', name); + break; case 'pnpm': await silentPnpm('remove', name); break; diff --git a/tests/legacy-cli/e2e/utils/process.ts b/tests/legacy-cli/e2e/utils/process.ts index 3cd6c77bc187..4fa89d7e10c8 100644 --- a/tests/legacy-cli/e2e/utils/process.ts +++ b/tests/legacy-cli/e2e/utils/process.ts @@ -392,6 +392,10 @@ export function silentBun(...args: string[]) { return _exec({ silent: true }, 'bun', args); } +export function silentDeno(...args: string[]) { + return _exec({ silent: true }, 'deno', args); +} + export function globalNpm(args: string[], env?: NodeJS.ProcessEnv) { if (!process.env.LEGACY_CLI_RUNNER) { throw new Error( From 93e7cb31cbff08c7e7554da689527f9c38bf9fdc Mon Sep 17 00:00:00 2001 From: Alan Agius <17563226+alan-agius4@users.noreply.github.com> Date: Tue, 19 Aug 2025 06:34:16 +0000 Subject: [PATCH 2/2] ci: add CI for deno package manager This commits configures CI to run Deno E2E tests. --- .github/workflows/ci.yml | 2 +- .github/workflows/pr.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e7976826b2de..1f0c5a692045 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -133,7 +133,7 @@ jobs: matrix: os: [ubuntu-latest] node: [22] - subset: [yarn, pnpm] + subset: [yarn, pnpm, deno] shard: [0, 1, 2] runs-on: ${{ matrix.os }} steps: diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index cc32412672de..e6e59f631c38 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -152,7 +152,7 @@ jobs: matrix: os: [ubuntu-latest] node: [22] - subset: [yarn, pnpm] + subset: [yarn, pnpm, deno] shard: [0, 1, 2] runs-on: ${{ matrix.os }} steps: