diff --git a/.eslintrc.json b/.eslintrc.json index 4dc6ab37921ca..b03a82584fcfe 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -64,6 +64,16 @@ ], "@nx/workspace/valid-command-object": "error" } + }, + { + "files": ["pnpm-lock.yaml"], + "parser": "./tools/eslint-rules/raw-file-parser.js", + "rules": { + "@nx/workspace/ensure-pnpm-lock-version": [ + "error", + { "version": "9.0" } + ] + } } ] } diff --git a/nx-dev/feature-search/src/lib/algolia-search.global.css b/nx-dev/feature-search/src/lib/algolia-search.global.css index c2a4c2fa073b3..32f5720f80490 100644 --- a/nx-dev/feature-search/src/lib/algolia-search.global.css +++ b/nx-dev/feature-search/src/lib/algolia-search.global.css @@ -2,6 +2,10 @@ @apply overflow-hidden !important; } +.DocSearch-VisuallyHiddenForAccessibility { + visibility: hidden; +} + body .DocSearch-Container { @apply fixed left-0 top-0 z-[50] flex h-screen w-screen cursor-auto flex-col bg-black/10 p-4 backdrop-blur-sm sm:p-6 md:p-[10vh] lg:p-[12vh] dark:bg-white/10; } diff --git a/nx.json b/nx.json index ba9dcfcaec1c4..3b26ecd70e65c 100644 --- a/nx.json +++ b/nx.json @@ -123,7 +123,14 @@ } }, "lint": { - "dependsOn": ["build-native", "^build-native"] + "dependsOn": [ + "build-native", + "^build-native", + "@nx/nx-source:lint-pnpm-lock" + ] + }, + "lint-pnpm-lock": { + "cache": true }, "e2e": { "cache": true, diff --git a/package.json b/package.json index 1e4a9b4d41831..e156c20dcef84 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "preinstall": "node ./scripts/preinstall.js", "test": "nx run-many -t test", "e2e": "nx run-many -t e2e --projects ./e2e/*", - "build:wasm": "rustup override set nightly-2024-07-19 && rustup target add wasm32-wasip1-threads && WASI_SDK_PATH=\"$(pwd)/wasi-sdk-23.0-x86_64-linux\" CMAKE_BUILD_PARALLEL_LEVEL=2 LIBSQLITE3_FLAGS=\"-DLONGDOUBLE_TYPE=double\" pnpm exec nx run-many -t build-native-wasm && rustup override unset" + "build:wasm": "rustup override set nightly-2024-07-19 && rustup target add wasm32-wasip1-threads && WASI_SDK_PATH=\"$(pwd)/wasi-sdk-23.0-x86_64-linux\" CMAKE_BUILD_PARALLEL_LEVEL=2 LIBSQLITE3_FLAGS=\"-DLONGDOUBLE_TYPE=double\" pnpm exec nx run-many -t build-native-wasm && rustup override unset", + "lint-pnpm-lock": "eslint pnpm-lock.yaml" }, "devDependencies": { "@actions/core": "^1.10.0", @@ -377,6 +378,7 @@ }, "nx": { "includedScripts": [ + "lint-pnpm-lock", "echo", "check-commit", "check-format", diff --git a/packages/nx/src/native/cache/cache.rs b/packages/nx/src/native/cache/cache.rs index e300f76b2dac4..e1236b741e255 100644 --- a/packages/nx/src/native/cache/cache.rs +++ b/packages/nx/src/native/cache/cache.rs @@ -26,6 +26,7 @@ pub struct NxCache { workspace_root: PathBuf, cache_path: PathBuf, db: External, + link_task_details: bool, } #[napi] @@ -35,6 +36,7 @@ impl NxCache { workspace_root: String, cache_path: String, db_connection: External, + link_task_details: Option, ) -> anyhow::Result { let cache_path = PathBuf::from(&cache_path); @@ -46,6 +48,7 @@ impl NxCache { workspace_root: PathBuf::from(workspace_root), cache_directory: cache_path.to_normalized_string(), cache_path, + link_task_details: link_task_details.unwrap_or(true) }; r.setup()?; @@ -54,9 +57,8 @@ impl NxCache { } fn setup(&self) -> anyhow::Result<()> { - self.db - .execute_batch( - "BEGIN; + let query = if self.link_task_details { + "BEGIN; CREATE TABLE IF NOT EXISTS cache_outputs ( hash TEXT PRIMARY KEY NOT NULL, code INTEGER NOT NULL, @@ -64,8 +66,23 @@ impl NxCache { accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (hash) REFERENCES task_details (hash) ); - COMMIT; - ", + COMMIT; + " + } else { + "BEGIN; + CREATE TABLE IF NOT EXISTS cache_outputs ( + hash TEXT PRIMARY KEY NOT NULL, + code INTEGER NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + accessed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + COMMIT; + " + }; + + self.db + .execute_batch( + query, ) .map_err(anyhow::Error::from) } @@ -115,6 +132,7 @@ impl NxCache { outputs: Vec, code: i16, ) -> anyhow::Result<()> { + trace!("PUT {}", &hash); let task_dir = self.cache_path.join(&hash); // Remove the task directory diff --git a/packages/nx/src/native/index.d.ts b/packages/nx/src/native/index.d.ts index 2839c5a94ee05..0b378e8302835 100644 --- a/packages/nx/src/native/index.d.ts +++ b/packages/nx/src/native/index.d.ts @@ -28,7 +28,7 @@ export declare class ImportResult { export declare class NxCache { cacheDirectory: string - constructor(workspaceRoot: string, cachePath: string, dbConnection: ExternalObject) + constructor(workspaceRoot: string, cachePath: string, dbConnection: ExternalObject, linkTaskDetails?: boolean | undefined | null) get(hash: string): CachedResult | null put(hash: string, terminalOutput: string, outputs: Array, code: number): void applyRemoteCacheResults(hash: string, result: CachedResult): void diff --git a/tools/eslint-rules/index.ts b/tools/eslint-rules/index.ts index 388cf11bf6af7..95abbbd01e5b9 100644 --- a/tools/eslint-rules/index.ts +++ b/tools/eslint-rules/index.ts @@ -1,3 +1,7 @@ +import { + RULE_NAME as ensurePnpmLockVersionName, + rule as ensurePnpmLockVersion, +} from './rules/ensure-pnpm-lock-version'; import { RULE_NAME as validCommandObjectName, rule as validCommandObject, @@ -34,5 +38,6 @@ module.exports = { rules: { [validSchemaDescriptionName]: validSchemaDescription, [validCommandObjectName]: validCommandObject, + [ensurePnpmLockVersionName]: ensurePnpmLockVersion, }, }; diff --git a/tools/eslint-rules/raw-file-parser.js b/tools/eslint-rules/raw-file-parser.js new file mode 100644 index 0000000000000..7d5506bd7d51c --- /dev/null +++ b/tools/eslint-rules/raw-file-parser.js @@ -0,0 +1,23 @@ +/** + * We have a custom lint rule for our pnpm-lock.yaml file and naturally ESLint does not natively know how to parse it. + * Rather than using a full yaml parser for this one case (which will need to spend time creating a real AST for the giant + * lock file), we can instead use a custom parser which just immediately returns a dummy AST and then build the reading of + * the lock file into the rule itself. + */ +module.exports = { + parseForESLint: (code) => ({ + ast: { + type: 'Program', + loc: { start: 0, end: code.length }, + range: [0, code.length], + body: [], + comments: [], + tokens: [], + }, + services: { isPlain: true }, + scopeManager: null, + visitorKeys: { + Program: [], + }, + }), +}; diff --git a/tools/eslint-rules/rules/ensure-pnpm-lock-version.ts b/tools/eslint-rules/rules/ensure-pnpm-lock-version.ts new file mode 100644 index 0000000000000..098e7aabc3716 --- /dev/null +++ b/tools/eslint-rules/rules/ensure-pnpm-lock-version.ts @@ -0,0 +1,109 @@ +/** + * This file sets you up with structure needed for an ESLint rule. + * + * It leverages utilities from @typescript-eslint to allow TypeScript to + * provide autocompletions etc for the configuration. + * + * Your rule's custom logic will live within the create() method below + * and you can learn more about writing ESLint rules on the official guide: + * + * https://eslint.org/docs/developer-guide/working-with-rules + * + * You can also view many examples of existing rules here: + * + * https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin/src/rules + */ + +import { ESLintUtils } from '@typescript-eslint/utils'; +import { closeSync, openSync, readSync } from 'node:fs'; + +// NOTE: The rule will be available in ESLint configs as "@nx/workspace-ensure-pnpm-lock-version" +export const RULE_NAME = 'ensure-pnpm-lock-version'; + +export const rule = ESLintUtils.RuleCreator(() => __filename)({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: ``, + }, + schema: [ + { + type: 'object', + properties: { + version: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + messages: { + unparseableLockfileVersion: + 'Could not parse lockfile version from pnpm-lock.yaml, the file may be corrupted or the ensure-pnpm-lock-version lint rule may need to be updated.', + incorrectLockfileVersion: + 'pnpm-lock.yaml has a lockfileVersion of {{version}}, but {{expectedVersion}} is required.', + }, + }, + defaultOptions: [], + create(context) { + // Read upon creation of the rule, the contents should not change during linting + const lockfileFirstLine = readFirstLineSync('pnpm-lock.yaml'); + // Extract the version number, it will be a string in single quotes + const lockfileVersion = lockfileFirstLine.match( + /lockfileVersion:\s*'([^']+)'/ + )?.[1]; + + const options = context.options as { version: string }[]; + if (!Array.isArray(options) || options.length === 0) { + throw new Error('Expected an array of options with a version property'); + } + const expectedLockfileVersion = options[0].version; + return { + Program(node) { + if (!lockfileVersion) { + context.report({ + node, + messageId: 'unparseableLockfileVersion', + }); + return; + } + + if (lockfileVersion !== expectedLockfileVersion) { + context.report({ + node, + messageId: 'incorrectLockfileVersion', + data: { + version: lockfileVersion, + expectedVersion: expectedLockfileVersion, + }, + }); + } + }, + }; + }, +}); + +/** + * pnpm-lock.yaml is a huge file, so only read the first line as efficiently as possible + * for optimum linting performance. + */ +function readFirstLineSync(filePath: string) { + const BUFFER_SIZE = 64; // Optimized for the expected line length + const buffer = Buffer.alloc(BUFFER_SIZE); + let line = ''; + let bytesRead: number; + let fd: number; + try { + fd = openSync(filePath, 'r'); + bytesRead = readSync(fd, buffer, 0, BUFFER_SIZE, 0); + line = buffer.toString('utf8', 0, bytesRead).split('\n')[0]; + } catch (err) { + throw err; // Re-throw to allow caller to handle + } finally { + if (fd !== undefined) { + closeSync(fd); + } + } + return line; +}