diff --git a/.eslintrc.json b/.eslintrc.json index e31d487770d..5cd610ee46e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -89,7 +89,7 @@ "error", { "mustMatch": "Copyright [0-9]{0,4} Adobe. All rights reserved.", - "templateFile": "config/license.js" + "templateFile": "config/HEADER.js" } ] } @@ -102,7 +102,7 @@ "error", { "mustMatch": "Copyright [0-9]{0,4} Adobe. All rights reserved.", - "templateFile": "../../../config/license.js" + "templateFile": "config/HEADER.js" } ] } @@ -113,7 +113,7 @@ "ecmaVersion": "latest", "sourceType": "module" }, - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "notice"], "root": true, "rules": { "no-console": [ diff --git a/.github/workflows/browser-tests.yml b/.github/workflows/browser-tests.yml index 0b8f3e88420..a109a4b8122 100644 --- a/.github/workflows/browser-tests.yml +++ b/.github/workflows/browser-tests.yml @@ -1,99 +1,98 @@ name: Browser Performance Tests on: - pull_request: - types: [opened, synchronize, reopened] + workflow_dispatch: # Only run manually, not on pull requests permissions: - contents: read - pull-requests: write + contents: read + pull-requests: write jobs: - test-changed-packages: - strategy: - matrix: - browser: [firefox, chrome] - name: Compare performance to latest release on ${{ matrix.browser }} - - # The job will only run if the pull request is from the same repository. - # Benchmarks can't run on PRs from forked repos due to comment posting restrictions without a GitHub token. - if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-22.04 - steps: - - name: Checkout main - uses: actions/checkout@v4 - with: - ref: main - - - name: Checkout PR branch - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Get full history - - - name: Fetch main branch - run: | - git fetch origin main:main - - - name: Setup Job and Install Dependencies - uses: ./.github/actions/setup-job - - - name: Check ChromeDriver Version - if: matrix.browser == 'chrome' - run: | - echo "Checking ChromeDriver version..." - npx chromedriver --version - echo "Checking Chrome version..." - google-chrome --version - echo "Checking tachometer chromedriver version..." - cd 1st-gen && yarn tachometer --version - - - name: Tachometer the changed packages - run: cd 1st-gen && yarn test:changed --browser=${{ matrix.browser }} - - - name: Create a dummy file to ensure at least one results file exists - run: touch 1st-gen/tachometer.${{ matrix.browser }}-ran.txt - - - name: Archive ${{ matrix.browser }} tachometer results - id: upload-artifact - uses: actions/upload-artifact@v4 - with: - name: tachometer-results-${{ matrix.browser }} - path: | - 1st-gen/tach-results.${{ matrix.browser }}.*.json - 1st-gen/tachometer.${{ matrix.browser }}-ran.txt - - comment-performance: - name: Comment tachometer performance results - needs: [test-changed-packages] - - # The job will only run if the pull request is from the same repository. - # Benchmarks can't run on PRs from forked repos due to comment posting restrictions without a GitHub token. - if: ${{ github.event.pull_request == null || github.event.pull_request.head.repo.full_name == github.repository }} - runs-on: ubuntu-latest - steps: - - name: Checkout main - uses: actions/checkout@v4 - with: - ref: main - - - name: Checkout PR branch - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Setup Job and Install Dependencies - uses: ./.github/actions/setup-job - - - uses: actions/download-artifact@v4 - with: - pattern: tachometer-results-* - merge-multiple: true - - - name: Post Tachometer Performance Comment - uses: actions/github-script@v7 - with: - script: | - const { buildTachometerComment } = await import('${{ github.workspace }}/.github/scripts/build-tachometer-comment.js'); - const { commentOrUpdate } = await import('${{ github.workspace }}/.github/scripts/comment-or-update.js'); - const body = buildTachometerComment(); - commentOrUpdate(github, context, '## Tachometer results', body); + test-changed-packages: + strategy: + matrix: + browser: [firefox, chrome] + name: Compare performance to latest release on ${{ matrix.browser }} + + # The job will only run if the pull request is from the same repository. + # Benchmarks can't run on PRs from forked repos due to comment posting restrictions without a GitHub token. + if: ${{ github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-22.04 + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Get full history + + - name: Fetch main branch + run: | + git fetch origin main:main + + - name: Setup Job and Install Dependencies + uses: ./.github/actions/setup-job + + - name: Check ChromeDriver Version + if: matrix.browser == 'chrome' + run: | + echo "Checking ChromeDriver version..." + npx chromedriver --version + echo "Checking Chrome version..." + google-chrome --version + echo "Checking tachometer chromedriver version..." + cd 1st-gen && yarn tachometer --version + + - name: Tachometer the changed packages + run: cd 1st-gen && yarn test:changed --browser=${{ matrix.browser }} + + - name: Create a dummy file to ensure at least one results file exists + run: touch 1st-gen/tachometer.${{ matrix.browser }}-ran.txt + + - name: Archive ${{ matrix.browser }} tachometer results + id: upload-artifact + uses: actions/upload-artifact@v4 + with: + name: tachometer-results-${{ matrix.browser }} + path: | + 1st-gen/tach-results.${{ matrix.browser }}.*.json + 1st-gen/tachometer.${{ matrix.browser }}-ran.txt + + comment-performance: + name: Comment tachometer performance results + needs: [test-changed-packages] + + # The job will only run if the pull request is from the same repository. + # Benchmarks can't run on PRs from forked repos due to comment posting restrictions without a GitHub token. + if: ${{ github.event.pull_request == null || github.event.pull_request.head.repo.full_name == github.repository }} + runs-on: ubuntu-latest + steps: + - name: Checkout main + uses: actions/checkout@v4 + with: + ref: main + + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup Job and Install Dependencies + uses: ./.github/actions/setup-job + + - uses: actions/download-artifact@v4 + with: + pattern: tachometer-results-* + merge-multiple: true + + - name: Post Tachometer Performance Comment + uses: actions/github-script@v7 + with: + script: | + const { buildTachometerComment } = await import('${{ github.workspace }}/.github/scripts/build-tachometer-comment.js'); + const { commentOrUpdate } = await import('${{ github.workspace }}/.github/scripts/comment-or-update.js'); + const body = buildTachometerComment(); + commentOrUpdate(github, context, '## Tachometer results', body); diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 914016401df..a2176b65a78 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -122,4 +122,4 @@ jobs: level: error reporter: github-pr-review filter_mode: diff_context - eslint_flags: '--config .eslintrc.json ${{ needs.changed_files.outputs.eslint_added_files }} ${{ needs.changed_files.outputs.eslint_modified_files }}' + eslint_flags: '${{ needs.changed_files.outputs.eslint_added_files }} ${{ needs.changed_files.outputs.eslint_modified_files }}' diff --git a/1st-gen/packages/.eslintrc.json b/1st-gen/packages/.eslintrc.json index ec0a155288c..7d4734f40b8 100644 --- a/1st-gen/packages/.eslintrc.json +++ b/1st-gen/packages/.eslintrc.json @@ -194,7 +194,7 @@ "error", { "mustMatch": "Copyright [0-9]{0,4} Adobe. All rights reserved.", - "templateFile": "config/license.js" + "templateFile": "config/HEADER.js" } ], "sort-imports": [ diff --git a/1st-gen/scripts/css-tools.js b/1st-gen/scripts/css-tools.js index 9ce41f54923..ded7b49a9b5 100644 --- a/1st-gen/scripts/css-tools.js +++ b/1st-gen/scripts/css-tools.js @@ -58,10 +58,10 @@ const wrapCSSResult = (content) => { `; }; -const licensePath = path.resolve(__dirname, '..', 'config', 'license.js'); +const headerPath = path.resolve(__dirname, '..', 'config', 'HEADER.js'); let header = ''; -if (fs.existsSync(licensePath)) { - header = fs.readFileSync(licensePath, 'utf8'); +if (fs.existsSync(headerPath)) { + header = fs.readFileSync(headerPath, 'utf8'); header = header.replace('<%= YEAR %>', new Date().getFullYear()); } diff --git a/1st-gen/scripts/test-changes.js b/1st-gen/scripts/test-changes.js index 7bab231532e..29f7c66da90 100644 --- a/1st-gen/scripts/test-changes.js +++ b/1st-gen/scripts/test-changes.js @@ -27,7 +27,7 @@ const { browser = 'chrome' } = yargs(hideBin(process.argv)).argv; */ export const getChangedPackages = () => { let changedPackages = []; - const changesetDir = '.changeset'; + const changesetDir = '../.changeset'; try { // Check if .changeset directory exists diff --git a/1st-gen/test/benchmark/cli.ts b/1st-gen/test/benchmark/cli.ts index 35c668466fa..000c726e3ab 100644 --- a/1st-gen/test/benchmark/cli.ts +++ b/1st-gen/test/benchmark/cli.ts @@ -178,7 +178,7 @@ $ node test/benchmark/cli -n 20 for (const packageName of packages) { const runCommands: string[] = []; const config: Partial = { - root: '../..', + root: '../../..', $schema: 'https://raw.githubusercontent.com/Polymer/tachometer/master/config.schema.json', timeout: parseFloat(opts.timeout) || 0, diff --git a/1st-gen/test/tsconfig-node.json b/1st-gen/test/tsconfig-node.json index 9c631f13728..6f0cedfbe5d 100644 --- a/1st-gen/test/tsconfig-node.json +++ b/1st-gen/test/tsconfig-node.json @@ -4,7 +4,7 @@ "outDir": ".", "rootDir": ".", "types": ["node"], - "typeRoots": ["../node_modules/@types"] + "typeRoots": ["../../node_modules/@types"] }, "include": ["benchmark/cli.ts"], "exclude": [] diff --git a/1st-gen/tools/.eslintrc.json b/1st-gen/tools/.eslintrc.json index e81cd6d1bbc..72c39409b6c 100644 --- a/1st-gen/tools/.eslintrc.json +++ b/1st-gen/tools/.eslintrc.json @@ -187,7 +187,7 @@ "error", { "mustMatch": "Copyright [0-9]{0,4} Adobe. All rights reserved.", - "templateFile": "config/license.js" + "templateFile": "config/HEADER.js" } ], "sort-imports": [ diff --git a/2nd-gen/package.json b/2nd-gen/package.json index 025cc0d5e38..2b382d87833 100644 --- a/2nd-gen/package.json +++ b/2nd-gen/package.json @@ -20,7 +20,7 @@ "clean": "yarn workspaces foreach --from '{@spectrum-web-components/core,@adobe/swc}' --recursive run clean", "dev:analyze": "yarn workspace @adobe/swc analyze:watch", "dev:core": "yarn workspace @spectrum-web-components/core dev", - "lint": "eslint . --ext .ts,.js,.json", + "lint": "cd .. && eslint 2nd-gen --ext .ts,.js,.json", "start": "run-p dev:core dev:analyze storybook", "storybook": "yarn workspace @adobe/swc storybook", "storybook:build": "yarn workspace @adobe/swc storybook:build", diff --git a/2nd-gen/packages/core/package.json b/2nd-gen/packages/core/package.json index 189944d996c..ebf90249cd2 100644 --- a/2nd-gen/packages/core/package.json +++ b/2nd-gen/packages/core/package.json @@ -6,16 +6,14 @@ "author": "Adobe", "homepage": "https://opensource.adobe.com/spectrum-web-components/", "repository": { + "directory": "2nd-gen/packages/core", "type": "git", - "url": "https://github.com/adobe/spectrum-web-components.git", - "directory": "2nd-gen/packages/core" + "url": "https://github.com/adobe/spectrum-web-components.git" }, "bugs": { "url": "https://github.com/adobe/spectrum-web-components/issues" }, "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", "exports": { ".": { "types": "./dist/index.d.ts", @@ -38,6 +36,8 @@ "import": "./dist/shared/base/*.js" } }, + "main": "./dist/index.js", + "module": "./dist/index.js", "files": [ "dist/" ], @@ -47,6 +47,7 @@ "dev": "vite build --watch", "prepublishOnly": "yarn build" }, + "types": "./dist/index.d.ts", "dependencies": { "lit": "^2.5.0 || ^3.1.3" }, @@ -71,6 +72,5 @@ ], "publishConfig": { "access": "public" - }, - "module": "./dist/index.js" + } } diff --git a/2nd-gen/packages/core/shared/base/version.ts b/2nd-gen/packages/core/shared/base/version.ts index 64e807e036e..018b37860c1 100644 --- a/2nd-gen/packages/core/shared/base/version.ts +++ b/2nd-gen/packages/core/shared/base/version.ts @@ -1,2 +1,13 @@ +/** + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ // Generated by genversion. export const version = '1.9.0'; diff --git a/2nd-gen/packages/core/tsconfig.json b/2nd-gen/packages/core/tsconfig.json index cc9846e058a..a7320e34224 100644 --- a/2nd-gen/packages/core/tsconfig.json +++ b/2nd-gen/packages/core/tsconfig.json @@ -1,16 +1,16 @@ { - "extends": "../../tsconfig.json", "compilerOptions": { - "outDir": "./dist", - "rootDir": "./", + "allowImportingTsExtensions": false, "composite": true, "noEmit": false, - "allowImportingTsExtensions": false, + "outDir": "./dist", "paths": { - "@spectrum-web-components/core/shared/*": ["./shared/*"], - "@spectrum-web-components/core/*": ["./*"] - } + "@spectrum-web-components/core/*": ["./*"], + "@spectrum-web-components/core/shared/*": ["./shared/*"] + }, + "rootDir": "./" }, - "include": ["**/*.ts"], - "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.stories.ts"] + "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.stories.ts"], + "extends": "../../tsconfig.json", + "include": ["**/*.ts"] } diff --git a/2nd-gen/packages/swc/.storybook/main.ts b/2nd-gen/packages/swc/.storybook/main.ts index 839edfc9a80..3f29c12473a 100644 --- a/2nd-gen/packages/swc/.storybook/main.ts +++ b/2nd-gen/packages/swc/.storybook/main.ts @@ -1,4 +1,5 @@ import { resolve, dirname } from 'path'; +import { mergeConfig } from 'vite'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -28,13 +29,38 @@ const config = { '@storybook/addon-vitest', ], viteFinal: async (config) => { - config.resolve = config.resolve || {}; - config.resolve.alias = { - ...config.resolve.alias, - '@spectrum-web-components/core': resolve(__dirname, '../../core'), - }; + return mergeConfig(config, { + plugins: [ + { + name: 'css-hmr', + handleHotUpdate({ file, modules, server }) { + if (!file.endsWith('.css') || file.includes('tokens')) + return; - return config; + const affected = new Set(modules); + + for (const mod of modules) { + for (const importer of mod.importers) + affected.add(importer); + } + + for (const mod of affected) + server.moduleGraph.invalidateModule(mod); + + return [...affected]; + }, + }, + ], + resolve: { + alias: { + '@spectrum-web-components/core': resolve( + __dirname, + '../../core' + ), + '@adobe/swc': resolve(__dirname, '../components'), + }, + }, + }); }, typescript: { check: true, diff --git a/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts b/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts index 66579a6b4f1..4d590ae63f5 100644 --- a/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts +++ b/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts @@ -12,10 +12,11 @@ import { html, TemplateResult } from 'lit'; import { styleMap } from 'lit/directives/style-map.js'; -import { Badge } from '@adobe/swc/badge'; import type { Meta, StoryObj as Story } from '@storybook/web-components'; import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers'; +import { Badge } from '@adobe/swc/badge'; + import '@adobe/swc/badge'; // ──────────────── diff --git a/2nd-gen/packages/swc/components/badge/test/badge.test.ts b/2nd-gen/packages/swc/components/badge/test/badge.test.ts index ec753764680..05736d65e7a 100644 --- a/2nd-gen/packages/swc/components/badge/test/badge.test.ts +++ b/2nd-gen/packages/swc/components/badge/test/badge.test.ts @@ -11,9 +11,10 @@ */ import { html } from 'lit'; -import type { Badge } from '@adobe/swc/badge'; import { beforeEach, describe, expect, test } from 'vitest'; +import type { Badge } from '@adobe/swc/badge'; + import '@adobe/swc/badge'; import { fixture } from '../../../utils/test-utils.js'; diff --git a/2nd-gen/packages/swc/components/divider/stories/divider.stories.ts b/2nd-gen/packages/swc/components/divider/stories/divider.stories.ts index fe0b0a14da7..625fc3bef03 100644 --- a/2nd-gen/packages/swc/components/divider/stories/divider.stories.ts +++ b/2nd-gen/packages/swc/components/divider/stories/divider.stories.ts @@ -11,10 +11,11 @@ */ import { html } from 'lit'; -import { Divider } from '@adobe/swc/divider'; import type { Meta, StoryObj as Story } from '@storybook/web-components'; import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers'; +import { Divider } from '@adobe/swc/divider'; + import '@adobe/swc/divider'; // ──────────────── diff --git a/2nd-gen/packages/swc/components/progress-circle/stories/progress-circle.stories.ts b/2nd-gen/packages/swc/components/progress-circle/stories/progress-circle.stories.ts index 51d6458cb8f..e8985065668 100644 --- a/2nd-gen/packages/swc/components/progress-circle/stories/progress-circle.stories.ts +++ b/2nd-gen/packages/swc/components/progress-circle/stories/progress-circle.stories.ts @@ -11,10 +11,11 @@ */ import { html } from 'lit'; -import { ProgressCircle } from '@adobe/swc/progress-circle'; import type { Meta, StoryObj as Story } from '@storybook/web-components'; import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers'; +import { ProgressCircle } from '@adobe/swc/progress-circle'; + import '@adobe/swc/progress-circle'; // ──────────────── diff --git a/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts b/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts index 6c1ccfe4469..567898fa3f5 100644 --- a/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts +++ b/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts @@ -11,10 +11,11 @@ */ import { html, TemplateResult } from 'lit'; import { styleMap } from 'lit/directives/style-map.js'; -import { StatusLight } from '@adobe/swc/status-light'; import type { Meta, StoryObj as Story } from '@storybook/web-components'; import { getStorybookHelpers } from '@wc-toolkit/storybook-helpers'; +import { StatusLight } from '@adobe/swc/status-light'; + import '@adobe/swc/status-light'; // ──────────────── diff --git a/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts b/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts index 13d87d3e027..0da2b3cd4c6 100644 --- a/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts +++ b/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts @@ -11,9 +11,10 @@ */ import { html } from 'lit'; -import type { StatusLight } from '@adobe/swc/status-light'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; +import type { StatusLight } from '@adobe/swc/status-light'; + import '@adobe/swc/status-light'; import { fixture } from '../../../utils/test-utils.js'; diff --git a/2nd-gen/packages/swc/package.json b/2nd-gen/packages/swc/package.json index 9422321bd01..40c49984cb6 100644 --- a/2nd-gen/packages/swc/package.json +++ b/2nd-gen/packages/swc/package.json @@ -56,18 +56,18 @@ "devDependencies": { "@axe-core/playwright": "4.10.2", "@custom-elements-manifest/analyzer": "0.10.8", - "@storybook/addon-a11y": "10.0.1", + "@storybook/addon-a11y": "10.0.3", "@storybook/addon-designs": "11.0.1", - "@storybook/addon-docs": "10.0.1", - "@storybook/addon-vitest": "10.0.1", + "@storybook/addon-docs": "10.0.3", + "@storybook/addon-vitest": "10.0.3", "@storybook/test-runner": "0.23.0", - "@storybook/web-components-vite": "10.0.1", + "@storybook/web-components-vite": "10.0.3", "@types/react": "19.1.16", "@types/react-dom": "19.1.9", "@vitest/browser": "3.2.4", "@vitest/coverage-v8": "3.2.4", "@vitest/ui": "3.2.4", - "@wc-toolkit/storybook-helpers": "9.0.1", + "@wc-toolkit/storybook-helpers": "10.0.0", "autoprefixer": "10.4.21", "glob": "11.0.3", "playwright": "1.53.1", @@ -76,7 +76,7 @@ "react": "19.1.1", "react-dom": "19.1.1", "rimraf": "6.0.1", - "storybook": "10.0.1", + "storybook": "10.0.3", "typescript": "5.9.2", "vite": "7.1.7", "vite-plugin-dts": "4.5.4", diff --git a/CONTRIBUTOR-DOCS/01_contributor-guides/01_getting-involved.md b/CONTRIBUTOR-DOCS/01_contributor-guides/01_getting-involved.md index 8a39e2678a3..ea3cbe952bb 100644 --- a/CONTRIBUTOR-DOCS/01_contributor-guides/01_getting-involved.md +++ b/CONTRIBUTOR-DOCS/01_contributor-guides/01_getting-involved.md @@ -80,7 +80,7 @@ Should any behavior fall short of these expectations, please report it to =9.0.0-0 <10.0.0-0" - checksum: 10c0/70f57c869c1d58f9eaf09d1cdc841784ceb01f7154452808d736431d79ba8757817320668e703a5136fe0b64428bdae386cd5b4b9cbabcfe75321eb234695673 + storybook: ">=10.0.0-0 <11.0.0-0" + checksum: 10c0/b35cd84e6888277057a5309d605f787d0794cf6fcd5db2a877fb59a0f89b6fe82311c9a6dd2a369a27e574cc07adbba59dda393287da44bfd247d77d9828db7c languageName: node linkType: hard @@ -28182,9 +28182,9 @@ __metadata: languageName: node linkType: hard -"storybook@npm:10.0.1": - version: 10.0.1 - resolution: "storybook@npm:10.0.1" +"storybook@npm:10.0.3": + version: 10.0.3 + resolution: "storybook@npm:10.0.3" dependencies: "@storybook/global": "npm:^5.0.0" "@storybook/icons": "npm:^1.6.0" @@ -28204,7 +28204,7 @@ __metadata: optional: true bin: storybook: ./dist/bin/dispatcher.js - checksum: 10c0/5b251ce434e365ae6c2f019c9f215fa10159d6f7660f0e1a61467d59d8230f2aa4de4dff5e599d81c0fb38a7b3cd51c7a9b423c4cb7abc63bd4a0ca39ea4de17 + checksum: 10c0/48c432f8db5bfe7c5c0101c176a65bfa1bff8b9d365bcee8acf5ba3b6b3d6b10d2f96976e08169cc4057566fb63848d3800ea9f20a640800e23cfd791093699e languageName: node linkType: hard