From d2d31f3c4ac3a68d450a0fbb7ba3f70934d40d55 Mon Sep 17 00:00:00 2001
From: Alan Agius <17563226+alan-agius4@users.noreply.github.com>
Date: Tue, 21 Oct 2025 09:16:58 +0000
Subject: [PATCH] build: remove shelljs usage
The script `goldens/public-api/manage.js` is no longer functional due to the removal of the public API bazel tasks. This script was responsible for managing the public API golden files, but it is now obsolete.
As part of this change, the `shelljs` dependency has been replaced with native `node:fs` calls, and the associated npm scripts and documentation have been removed.
---
CONTRIBUTING.md | 12 -------
goldens/public-api/manage.js | 54 ------------------------------
package.json | 4 ---
pnpm-lock.yaml | 23 -------------
scripts/build-packages-dist.mts | 49 ++++++++++++++++++++++-----
scripts/diff-release-package.mts | 29 +++++++++++++---
scripts/templates/contributing.ejs | 12 -------
7 files changed, 65 insertions(+), 118 deletions(-)
delete mode 100644 goldens/public-api/manage.js
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 0642e0b7ff65..4dabb694f83e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -301,15 +301,3 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular-devkit
-## Updating the Public API
-Our Public API surface is tracked using golden files.
-
-You check all golden files by running:
-```bash
-pnpm public-api:check
-```
-
-If you modified the public API, the test will fail. To update the golden files you need to run:
-```bash
-pnpm public-api:update
-```
diff --git a/goldens/public-api/manage.js b/goldens/public-api/manage.js
deleted file mode 100644
index 569f2a6bd1e3..000000000000
--- a/goldens/public-api/manage.js
+++ /dev/null
@@ -1,54 +0,0 @@
-const {exec} = require('shelljs');
-const {Parser: parser} = require('yargs/helpers');
-
-// Remove all command line flags from the arguments.
-const argv = parser(process.argv.slice(2));
-// The command the user would like to run, either 'accept' or 'test'
-const USER_COMMAND = argv._[0];
-// The shell command to query for all Public API guard tests.
-const BAZEL_PUBLIC_API_TARGET_QUERY_CMD =
- `pnpm -s bazel query --output label 'kind(nodejs_test, ...) intersect attr("tags", "api_guard", ...)'`
-// Bazel targets for testing Public API goldens
-process.stdout.write('Gathering all Public API targets');
-const ALL_PUBLIC_API_TESTS = exec(BAZEL_PUBLIC_API_TARGET_QUERY_CMD, {silent: true})
- .trim()
- .split('\n')
- .map(test => test.trim());
-process.stdout.clearLine();
-process.stdout.cursorTo(0);
-// Bazel targets for generating Public API goldens
-const ALL_PUBLIC_API_ACCEPTS = ALL_PUBLIC_API_TESTS.map(test => `${test}.accept`);
-
-/**
- * Run the provided bazel commands on each provided target individually.
- */
-function runBazelCommandOnTargets(command, targets, present) {
- for (const target of targets) {
- process.stdout.write(`${present}: ${target}`);
- const commandResult = exec(`pnpm -s bazel ${command} ${target}`, {silent: true});
- process.stdout.clearLine();
- process.stdout.cursorTo(0);
- if (commandResult.code) {
- console.error(`Failed ${command}: ${target}`);
- console.group();
- console.error(commandResult.stdout || commandResult.stderr);
- console.groupEnd();
- } else {
- console.log(`Successful ${command}: ${target}`);
- }
- }
-}
-
-switch (USER_COMMAND) {
- case 'accept':
- runBazelCommandOnTargets('run', ALL_PUBLIC_API_ACCEPTS, 'Running');
- break;
- case 'test':
- runBazelCommandOnTargets('test', ALL_PUBLIC_API_TESTS, 'Testing');
- break;
- default:
- console.warn('Invalid command provided.');
- console.warn();
- console.warn(`Run this script with either "accept" and "test"`);
- break;
-}
diff --git a/package.json b/package.json
index 114d23740dc1..0a7e561dd329 100644
--- a/package.json
+++ b/package.json
@@ -21,9 +21,7 @@
"postinstall": "pnpm -s webdriver-update && husky",
"//webdriver-update-README": "ChromeDriver version must match Puppeteer Chromium version, see https://github.com/GoogleChrome/puppeteer/releases http://chromedriver.chromium.org/downloads",
"webdriver-update": "webdriver-manager update --standalone false --gecko false --versions.chrome 106.0.5249.21",
- "public-api:check": "node goldens/public-api/manage.js test",
"ng-dev": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only node_modules/@angular/ng-dev/bundles/cli.mjs",
- "public-api:update": "node goldens/public-api/manage.js accept",
"ts-circular-deps": "pnpm -s ng-dev ts-circular-deps --config ./scripts/circular-deps-test.conf.mjs",
"check-tooling-setup": "tsc --project .ng-dev/tsconfig.json",
"diff-release-package": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/diff-release-package.mts"
@@ -89,7 +87,6 @@
"@types/progress": "^2.0.3",
"@types/resolve": "^1.17.1",
"@types/semver": "^7.3.12",
- "@types/shelljs": "^0.8.11",
"@types/watchpack": "^2.4.4",
"@types/yargs": "^17.0.20",
"@types/yargs-parser": "^21.0.0",
@@ -134,7 +131,6 @@
"rollup-plugin-dts": "6.2.3",
"rollup-plugin-sourcemaps2": "0.5.4",
"semver": "7.7.3",
- "shelljs": "^0.10.0",
"source-map-support": "0.5.21",
"tar": "^7.0.0",
"ts-node": "^10.9.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3b97a55c8d64..61a35355c7a8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -148,9 +148,6 @@ importers:
'@types/semver':
specifier: ^7.3.12
version: 7.7.1
- '@types/shelljs':
- specifier: ^0.8.11
- version: 0.8.17
'@types/watchpack':
specifier: ^2.4.4
version: 2.4.4
@@ -283,9 +280,6 @@ importers:
semver:
specifier: 7.7.3
version: 7.7.3
- shelljs:
- specifier: ^0.10.0
- version: 0.10.0
source-map-support:
specifier: 0.5.21
version: 0.5.21
@@ -3633,9 +3627,6 @@ packages:
'@types/serve-static@1.15.9':
resolution: {integrity: sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==}
- '@types/shelljs@0.8.17':
- resolution: {integrity: sha512-IDksKYmQA2W9MkQjiyptbMmcQx+8+Ol6b7h6dPU5S05JyiQDSb/nZKnrMrZqGwgV6VkVdl6/SPCKPDlMRvqECg==}
-
'@types/sockjs@0.3.36':
resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==}
@@ -8092,10 +8083,6 @@ packages:
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
engines: {node: '>= 0.4'}
- shelljs@0.10.0:
- resolution: {integrity: sha512-Jex+xw5Mg2qMZL3qnzXIfaxEtBaC4n7xifqaqtrZDdlheR70OGkydrPJWT0V1cA1k3nanC86x9FwAmQl6w3Klw==}
- engines: {node: '>=18'}
-
side-channel-list@1.0.0:
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
engines: {node: '>= 0.4'}
@@ -12225,11 +12212,6 @@ snapshots:
'@types/node': 22.18.11
'@types/send': 0.17.5
- '@types/shelljs@0.8.17':
- dependencies:
- '@types/node': 22.18.11
- glob: 11.0.3
-
'@types/sockjs@0.3.36':
dependencies:
'@types/node': 22.18.11
@@ -17652,11 +17634,6 @@ snapshots:
shell-quote@1.8.3: {}
- shelljs@0.10.0:
- dependencies:
- execa: 5.1.1
- fast-glob: 3.3.3
-
side-channel-list@1.0.0:
dependencies:
es-errors: 1.3.0
diff --git a/scripts/build-packages-dist.mts b/scripts/build-packages-dist.mts
index 441a3a2a021a..9a4fa1c764b9 100644
--- a/scripts/build-packages-dist.mts
+++ b/scripts/build-packages-dist.mts
@@ -14,9 +14,17 @@
import { BuiltPackage } from '@angular/ng-dev';
import { execSync } from 'node:child_process';
-import { chmodSync, copyFileSync, mkdirSync, rmSync } from 'node:fs';
+import {
+ chmodSync,
+ copyFileSync,
+ cpSync,
+ existsSync,
+ lstatSync,
+ mkdirSync,
+ readdirSync,
+ rmSync,
+} from 'node:fs';
import { dirname, join } from 'node:path';
-import sh from 'shelljs';
/** Name of the Bazel tag that will be used to find release package targets. */
const releaseTargetTag = 'release-package';
@@ -89,14 +97,15 @@ function buildReleasePackages(
// Archive output is created by the npm_package_archive target
const archiveOutputPath = directoryOutputPath + '_archive.tgz';
- if (sh.test('-d', directoryOutputPath)) {
- sh.chmod('-R', 'u+w', directoryOutputPath);
- sh.rm('-rf', directoryOutputPath);
+ if (existsSync(directoryOutputPath)) {
+ chmodRecursiveSync(directoryOutputPath, '0755');
+ rmSync(directoryOutputPath, { recursive: true, force: true });
}
- try {
+
+ if (existsSync(archiveOutputPath)) {
chmodSync(archiveOutputPath, '0755');
rmSync(archiveOutputPath, { force: true });
- } catch {}
+ }
});
// Build both the npm_package and npm_package_archive targets for each package
@@ -123,8 +132,8 @@ function buildReleasePackages(
mkdirSync(dirname(targetFolder), { recursive: true });
// Copy package contents to target directory
- sh.cp('-R', directoryOutputPath, targetFolder);
- sh.chmod('-R', 'u+w', targetFolder);
+ cpSync(directoryOutputPath, targetFolder, { recursive: true });
+ chmodRecursiveSync(targetFolder, '0755');
// Copy archive of package to target directory
const archiveTargetPath = join(distPath, `${pkgName.replace('/', '_')}.tgz`);
@@ -176,3 +185,25 @@ function exec(command: string, captureStdout?: true) {
return stdout.toString().trim();
}
}
+
+/**
+ * Recursively changes the permissions (mode) of a directory and all its contents (files and subdirectories).
+ * @param startPath The starting directory path.
+ * @param mode The new permissions mode (e.g., 0755).
+ */
+function chmodRecursiveSync(startPath: string, mode: string): void {
+ chmodSync(startPath, mode);
+
+ const files = readdirSync(startPath);
+
+ for (const file of files) {
+ const filePath = join(startPath, file);
+ const stat = lstatSync(filePath);
+
+ if (stat.isDirectory()) {
+ chmodRecursiveSync(filePath, mode);
+ } else {
+ chmodSync(filePath, mode);
+ }
+ }
+}
diff --git a/scripts/diff-release-package.mts b/scripts/diff-release-package.mts
index 2bf01aded3cd..8ec6b6df48db 100644
--- a/scripts/diff-release-package.mts
+++ b/scripts/diff-release-package.mts
@@ -18,10 +18,9 @@
import { GitClient } from '@angular/ng-dev';
import childProcess from 'node:child_process';
-import fs from 'node:fs';
+import fs, { chmodSync, lstatSync, readdirSync } from 'node:fs';
import os from 'node:os';
-import path from 'node:path';
-import sh from 'shelljs';
+import path, { join } from 'node:path';
// Do not remove `.git` as we use Git for comparisons later.
// Also preserve `uniqueId` as it's irrelevant for the diff and not included via Bazel.
@@ -130,6 +129,28 @@ async function deleteDir(dirPath: string) {
}
// Needed as Bazel artifacts are readonly and cannot be deleted otherwise.
- sh.chmod('-R', 'u+w', dirPath);
+ chmodRecursiveSync(dirPath, '0755');
await fs.promises.rm(dirPath, { recursive: true, force: true, maxRetries: 3 });
}
+
+/**
+ * Recursively changes the permissions (mode) of a directory and all its contents (files and subdirectories).
+ * @param startPath The starting directory path.
+ * @param mode The new permissions mode (e.g., 0755).
+ */
+function chmodRecursiveSync(startPath: string, mode: string): void {
+ chmodSync(startPath, mode);
+
+ const files = readdirSync(startPath);
+
+ for (const file of files) {
+ const filePath = join(startPath, file);
+ const stat = lstatSync(filePath);
+
+ if (stat.isDirectory()) {
+ chmodRecursiveSync(filePath, mode);
+ } else {
+ chmodSync(filePath, mode);
+ }
+ }
+}
diff --git a/scripts/templates/contributing.ejs b/scripts/templates/contributing.ejs
index b5bb997911ce..745d6d3500d2 100644
--- a/scripts/templates/contributing.ejs
+++ b/scripts/templates/contributing.ejs
@@ -291,15 +291,3 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
[js-style-guide]: https://google.github.io/styleguide/jsguide.html
[stackoverflow]: https://stackoverflow.com/questions/tagged/angular-devkit
-## Updating the Public API
-Our Public API surface is tracked using golden files.
-
-You check all golden files by running:
-```bash
-pnpm public-api:check
-```
-
-If you modified the public API, the test will fail. To update the golden files you need to run:
-```bash
-pnpm public-api:update
-```