diff --git a/.ng-dev/release.mts b/.ng-dev/release.mts index 84e8271d2f0b..42ec4ff2ee77 100644 --- a/.ng-dev/release.mts +++ b/.ng-dev/release.mts @@ -1,3 +1,11 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + import semver from 'semver'; import {ReleaseConfig} from '@angular/ng-dev'; import {assertValidUpdateMigrationCollections} from '../tools/release-checks/check-migration-collections.mjs'; diff --git a/package.json b/package.json index 9011eaf4dd5c..a7e5dda11231 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "karma-parallel": "^0.3.1", "karma-sourcemap-loader": "^0.4.0", "magic-string": "0.30.17", - "marked": "^15.0.12", + "marked": "^16.0.0", "minimatch": "^10.0.3", "parse5": "^7.1.2", "postcss": "^8.4.17", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e303e5eb2fb..03b19f59db7d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -270,8 +270,8 @@ importers: specifier: 0.30.17 version: 0.30.17 marked: - specifier: ^15.0.12 - version: 15.0.12 + specifier: ^16.0.0 + version: 16.0.0 minimatch: specifier: ^10.0.3 version: 10.0.3 @@ -6139,9 +6139,9 @@ packages: engines: {node: '>= 18'} hasBin: true - marked@15.0.12: - resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} - engines: {node: '>= 18'} + marked@16.0.0: + resolution: {integrity: sha512-MUKMXDjsD/eptB7GPzxo4xcnLS6oo7/RHimUMHEDRhUooPwmN9BEpMl7AEOJv3bmso169wHI2wUF9VQgL7zfmA==} + engines: {node: '>= 20'} hasBin: true marky@1.3.0: @@ -15182,7 +15182,7 @@ snapshots: marked@13.0.3: {} - marked@15.0.12: {} + marked@16.0.0: {} marky@1.3.0: {} diff --git a/tools/dgeni/docs-package.ts b/tools/dgeni/docs-package.ts index c006065849b2..bbdc1b22a7c3 100644 --- a/tools/dgeni/docs-package.ts +++ b/tools/dgeni/docs-package.ts @@ -2,7 +2,6 @@ import {Package} from 'dgeni'; import {ReadTypeScriptModules} from 'dgeni-packages/typescript/processors/readTypeScriptModules'; import {Host} from 'dgeni-packages/typescript/services/ts-host/host'; import {TypeFormatFlags} from 'typescript'; -import {HighlightNunjucksExtension} from './nunjucks-tags/highlight'; import {patchLogService} from './patch-log-service'; import {AsyncFunctionsProcessor} from './processors/async-functions'; import {categorizer} from './processors/categorizer'; @@ -165,6 +164,4 @@ apiDocsPackage.config(function (templateFinder: any, templateEngine: any) { variableStart: '{$', variableEnd: '$}', }; - - templateEngine.tags.push(new HighlightNunjucksExtension()); }); diff --git a/tools/dgeni/nunjucks-tags/highlight.ts b/tools/dgeni/nunjucks-tags/highlight.ts deleted file mode 100644 index 9e42c6081026..000000000000 --- a/tools/dgeni/nunjucks-tags/highlight.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {highlightCodeBlock} from '../../highlight-files/highlight-code-block'; - -/** - * Nunjucks extension that supports rendering highlighted content. Content that is placed in - * between a {% highlight %} and {% end_highlight %} block will be automatically highlighted. - * - * HighlightJS cannot detect the code language automatically. Therefore, developers need to - * specify the language manually as first tag-block argument. - */ -export class HighlightNunjucksExtension { - /** Tags that will be parsed by this Nunjucks extension. */ - tags = ['highlight']; - - /** Disable auto-escaping for content that is rendered within this extension. */ - autoescape = false; - - parse(parser: any, nodes: any) { - const startToken = parser.nextToken(); - const args = parser.parseSignature(null, true); - - // Jump to the end of the "{% highlight }" block. - parser.advanceAfterBlockEnd(startToken.value); - - // Parse text content until {% end_highlight }" has been reached. - const textContent = parser.parseUntilBlocks('end_highlight'); - - // Jump to the end of the "{% highlight }" block. - parser.advanceAfterBlockEnd(); - - return new nodes.CallExtension(this, 'render', args, [textContent]); - } - - render(_context: any, language: string, contentFn: () => string) { - return highlightCodeBlock(contentFn(), language); - } -} diff --git a/tools/dgeni/templates/constant.template.html b/tools/dgeni/templates/constant.template.html index 6377a3ef266a..704df309c180 100644 --- a/tools/dgeni/templates/constant.template.html +++ b/tools/dgeni/templates/constant.template.html @@ -17,10 +17,6 @@
-diff --git a/tools/dgeni/templates/type-alias.template.html b/tools/dgeni/templates/type-alias.template.html index 397b1479798d..9da26b2bb097 100644 --- a/tools/dgeni/templates/type-alias.template.html +++ b/tools/dgeni/templates/type-alias.template.html @@ -17,10 +17,6 @@-{%- highlight "typescript" -%} - const {$ constant.name | safe $}: {$ constant.type | safe $}; -{%- end_highlight -%} -
+const {$ constant.name | safe $}: {$ constant.type | safe $};
-diff --git a/tools/extract-tokens/BUILD.bazel b/tools/extract-tokens/BUILD.bazel index 8f4763b38ccd..36dd4dec4c1c 100644 --- a/tools/extract-tokens/BUILD.bazel +++ b/tools/extract-tokens/BUILD.bazel @@ -5,7 +5,7 @@ package(default_visibility = ["//visibility:public"]) ts_project( name = "extract_tokens_lib", - srcs = glob(["**/*.ts"]), + srcs = glob(["**/*.mts"]), tsconfig = "//tools:tsconfig", deps = [ "//:node_modules/sass", @@ -18,5 +18,5 @@ js_binary( data = [ ":extract_tokens_lib", ], - entry_point = ":extract-tokens.js", + entry_point = ":extract-tokens.mjs", ) diff --git a/tools/extract-tokens/extract-tokens.ts b/tools/extract-tokens/extract-tokens.mts similarity index 89% rename from tools/extract-tokens/extract-tokens.ts rename to tools/extract-tokens/extract-tokens.mts index 8876d8f869bb..c4fce5f53d75 100644 --- a/tools/extract-tokens/extract-tokens.ts +++ b/tools/extract-tokens/extract-tokens.mts @@ -2,7 +2,7 @@ import {readFileSync, writeFileSync} from 'fs'; import {pathToFileURL} from 'url'; import {relative, join, dirname} from 'path'; import {compileString} from 'sass'; -import {highlightCodeBlock} from '../highlight-files/highlight-code-block'; +import {highlightCodeBlock} from '../highlight-files/highlight-code-block.mjs'; /** Information extracted for a single token from the theme. */ interface ExtractedToken { @@ -43,40 +43,39 @@ interface ThemeData { } // Script that extracts the tokens from a specific Bazel target. -if (require.main === module) { - const [packagePath, outputPath, ...inputFiles] = process.argv.slice(2); - const themeFiles = inputFiles - // Filter out only the files within the package - // since the path also includes dependencies. - .filter(file => file.startsWith(packagePath)) - .map(file => { - // Assumption: all theme files start with an underscore since they're - // partials and they end with `-theme`. - // Assumption: the name under which the theme mixin will be available is the - // same as the file name without the underscore and `-theme.scss`. - const match = file.match(/_(.*)-theme\.scss$/); - return match ? {mixinPrefix: match[1], filePath: file} : null; - }) - .filter(file => !!file); - - if (themeFiles.length === 0) { - throw new Error(`Could not find theme files in ${packagePath}`); - } - const themes: ThemeData[] = []; +const [packagePath, outputPath, ...inputFiles] = process.argv.slice(2); +const themeFiles = inputFiles + // Filter out only the files within the package + // since the path also includes dependencies. + .filter(file => file.startsWith(packagePath)) + .map(file => { + // Assumption: all theme files start with an underscore since they're + // partials and they end with `-theme`. + // Assumption: the name under which the theme mixin will be available is the + // same as the file name without the underscore and `-theme.scss`. + const match = file.match(/_(.*)-theme\.scss$/); + return match ? {mixinPrefix: match[1], filePath: file} : null; + }) + .filter(file => !!file); + +if (themeFiles.length === 0) { + throw new Error(`Could not find theme files in ${packagePath}`); +} + +const themes: ThemeData[] = []; - themeFiles.forEach(theme => { - themes.push({ - name: theme.mixinPrefix, - // This can be derived from the `name` already, but we want the source - // of truth to be in this repo, instead of whatever page consumes the data. - overridesMixin: `${theme.mixinPrefix}-overrides`, - tokens: extractTokens(theme.filePath), - }); +themeFiles.forEach(theme => { + themes.push({ + name: theme.mixinPrefix, + // This can be derived from the `name` already, but we want the source + // of truth to be in this repo, instead of whatever page consumes the data. + overridesMixin: `${theme.mixinPrefix}-overrides`, + tokens: extractTokens(theme.filePath), }); +}); - writeFileSync(outputPath, JSON.stringify({example: getUsageExample(themes), themes})); -} +writeFileSync(outputPath, JSON.stringify({example: getUsageExample(themes), themes})); /** * Extracts the tokens from a theme file. diff --git a/tools/highlight-files/BUILD.bazel b/tools/highlight-files/BUILD.bazel index c59808a8dd5a..5a48f49633b3 100644 --- a/tools/highlight-files/BUILD.bazel +++ b/tools/highlight-files/BUILD.bazel @@ -1,11 +1,11 @@ -load("//tools:defaults.bzl", "ts_project") load("@aspect_rules_js//js:defs.bzl", "js_binary") +load("//tools:defaults.bzl", "ts_project") package(default_visibility = ["//visibility:public"]) ts_project( name = "sources", - srcs = glob(["**/*.ts"]), + srcs = glob(["**/*.mts"]), tsconfig = "//tools:tsconfig", deps = [ "//:node_modules/@types/fs-extra", @@ -20,5 +20,5 @@ js_binary( data = [ ":sources", ], - entry_point = ":highlight-files.js", + entry_point = ":highlight-files.mjs", ) diff --git a/tools/highlight-files/highlight-code-block.ts b/tools/highlight-files/highlight-code-block.mts similarity index 100% rename from tools/highlight-files/highlight-code-block.ts rename to tools/highlight-files/highlight-code-block.mts diff --git a/tools/highlight-files/highlight-files.mts b/tools/highlight-files/highlight-files.mts new file mode 100644 index 000000000000..19d1633abad5 --- /dev/null +++ b/tools/highlight-files/highlight-files.mts @@ -0,0 +1,73 @@ +/** + * Script that will be used by the highlight_files Bazel rule in order to highlight + * multiple input files using highlight.js. The output will be HTML files. + */ + +import fsExtra from 'fs-extra'; +import {dirname, extname, join, relative} from 'path'; +import {highlightCodeBlock} from './highlight-code-block.mjs'; +import {regionParser} from '../region-parser/region-parser.mjs'; + +/** + * Determines the command line arguments for the current Bazel action. Since this action can + * have a large set of input files, Bazel may write the arguments into a parameter file. + * This function is responsible for handling normal argument passing or Bazel parameter files. + * Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + */ +function getBazelActionArguments() { + const args = process.argv.slice(2); + + // If Bazel uses a parameter file, we've specified that it passes the file in the following + // format: "arg0 arg1 --param-file={path_to_param_file}" + if (args[0].startsWith('--param-file=')) { + return fsExtra.readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n'); + } + + return args; +} + +function detectAndHighlightRegionBlocks( + parsed: {contents: string; regions: {[p: string]: string}}, + basePath: string, + outDir: string, +) { + const fileExtension = extname(basePath).substring(1); + for (const [regionName, regionSnippet] of Object.entries(parsed.regions)) { + // Create files for each found region + if (!regionName) { + continue; + } + const highlightedRegion = highlightCodeBlock(regionSnippet, fileExtension); + // Convert "my-component-example.ts" into "my-component-example_region-ts.html" + const regionBaseOutputPath = basePath.replace( + `.${fileExtension}`, + `_${regionName}-${fileExtension}.html`, + ); + const regionOutputPath = join(outDir, regionBaseOutputPath); + fsExtra.ensureDirSync(dirname(regionOutputPath)); + fsExtra.writeFileSync(regionOutputPath, highlightedRegion); + } +} + +// The script expects the output directory as first argument. Second is the name of the +// package where this the highlight target is declared. All remaining arguments will be +// considered as markdown input files that need to be transformed. +const [outDir, packageName, ...inputFiles] = getBazelActionArguments(); + +// Walk through each input file and write transformed markdown output +// to the specified output directory. +for (const execPath of inputFiles) { + // Compute a relative path from the package to the actual input file. + // e.g `src/components-examples/cdk/<..>/example.ts` becomes `cdk/<..>/example.ts`. + const basePath = relative(packageName, execPath); + const fileExtension = extname(basePath).substring(1); + const parsed = regionParser(fsExtra.readFileSync(execPath, 'utf8'), fileExtension); + detectAndHighlightRegionBlocks(parsed, basePath, outDir); + // Convert "my-component-example.ts" into "my-component-example-ts.html" + const baseOutputPath = basePath.replace(`.${fileExtension}`, `-${fileExtension}.html`); + const outputPath = join(outDir, baseOutputPath); + const htmlOutput = highlightCodeBlock(parsed.contents, fileExtension); + + fsExtra.ensureDirSync(dirname(outputPath)); + fsExtra.writeFileSync(outputPath, htmlOutput); +} diff --git a/tools/highlight-files/highlight-files.ts b/tools/highlight-files/highlight-files.ts deleted file mode 100644 index ebfbaff8f579..000000000000 --- a/tools/highlight-files/highlight-files.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Script that will be used by the highlight_files Bazel rule in order to highlight - * multiple input files using highlight.js. The output will be HTML files. - */ - -import {readFileSync, writeFileSync, ensureDirSync} from 'fs-extra'; -import {dirname, extname, join, relative} from 'path'; -import {highlightCodeBlock} from './highlight-code-block'; -import {regionParser} from '../region-parser/region-parser'; - -/** - * Determines the command line arguments for the current Bazel action. Since this action can - * have a large set of input files, Bazel may write the arguments into a parameter file. - * This function is responsible for handling normal argument passing or Bazel parameter files. - * Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file - */ -function getBazelActionArguments() { - const args = process.argv.slice(2); - - // If Bazel uses a parameter file, we've specified that it passes the file in the following - // format: "arg0 arg1 --param-file={path_to_param_file}" - if (args[0].startsWith('--param-file=')) { - return readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n'); - } - - return args; -} - -function detectAndHighlightRegionBlocks( - parsed: {contents: string; regions: {[p: string]: string}}, - basePath: string, - outDir: string, -) { - const fileExtension = extname(basePath).substring(1); - for (const [regionName, regionSnippet] of Object.entries(parsed.regions)) { - // Create files for each found region - if (!regionName) { - continue; - } - const highlightedRegion = highlightCodeBlock(regionSnippet, fileExtension); - // Convert "my-component-example.ts" into "my-component-example_region-ts.html" - const regionBaseOutputPath = basePath.replace( - `.${fileExtension}`, - `_${regionName}-${fileExtension}.html`, - ); - const regionOutputPath = join(outDir, regionBaseOutputPath); - ensureDirSync(dirname(regionOutputPath)); - writeFileSync(regionOutputPath, highlightedRegion); - } -} - -if (require.main === module) { - // The script expects the output directory as first argument. Second is the name of the - // package where this the highlight target is declared. All remaining arguments will be - // considered as markdown input files that need to be transformed. - const [outDir, packageName, ...inputFiles] = getBazelActionArguments(); - - // Walk through each input file and write transformed markdown output - // to the specified output directory. - for (const execPath of inputFiles) { - // Compute a relative path from the package to the actual input file. - // e.g `src/components-examples/cdk/<..>/example.ts` becomes `cdk/<..>/example.ts`. - const basePath = relative(packageName, execPath); - const fileExtension = extname(basePath).substring(1); - const parsed = regionParser(readFileSync(execPath, 'utf8'), fileExtension); - detectAndHighlightRegionBlocks(parsed, basePath, outDir); - // Convert "my-component-example.ts" into "my-component-example-ts.html" - const baseOutputPath = basePath.replace(`.${fileExtension}`, `-${fileExtension}.html`); - const outputPath = join(outDir, baseOutputPath); - const htmlOutput = highlightCodeBlock(parsed.contents, fileExtension); - - ensureDirSync(dirname(outputPath)); - writeFileSync(outputPath, htmlOutput); - } -} diff --git a/tools/markdown-to-html/BUILD.bazel b/tools/markdown-to-html/BUILD.bazel index 361934dde196..2e2c97c94ec6 100644 --- a/tools/markdown-to-html/BUILD.bazel +++ b/tools/markdown-to-html/BUILD.bazel @@ -6,9 +6,9 @@ package(default_visibility = ["//visibility:public"]) ts_project( name = "transform-markdown", srcs = glob( - ["**/*.ts"], + ["**/*.mts"], exclude = [ - "*.spec.ts", + "*.spec.mts", ], ), tsconfig = "//tools:tsconfig", @@ -24,14 +24,14 @@ js_binary( data = [ ":transform-markdown", ], - entry_point = ":transform-markdown.js", + entry_point = ":transform-markdown.mjs", ) ts_project( name = "unit_test_lib", testonly = True, srcs = glob( - ["*.spec.ts"], + ["*.spec.mts"], ), tsconfig = "//tools:tsconfig-test", visibility = ["//visibility:private"], diff --git a/tools/markdown-to-html/docs-marked-renderer.ts b/tools/markdown-to-html/docs-marked-renderer.mts similarity index 98% rename from tools/markdown-to-html/docs-marked-renderer.ts rename to tools/markdown-to-html/docs-marked-renderer.mts index 0686db40b181..1432b9860f51 100644 --- a/tools/markdown-to-html/docs-marked-renderer.ts +++ b/tools/markdown-to-html/docs-marked-renderer.mts @@ -1,7 +1,7 @@ import {Renderer, Tokens} from 'marked'; import {basename, extname} from 'path'; import slugify from 'slugify'; -import {highlightCodeBlock} from '../highlight-files/highlight-code-block'; +import {highlightCodeBlock} from '../highlight-files/highlight-code-block.mjs'; /** Regular expression that matches example comments. */ const exampleCommentRegex = //g; @@ -33,7 +33,7 @@ export class DocsMarkdownRenderer extends Renderer { const content = this.parser.parseInline(tag.tokens); if (depth === 2 || depth === 3 || depth === 4 || depth === 5 || depth === 6) { - const headingId = slugify(tag.text, {lower: true, strict: true}); + const headingId = slugify.default(tag.text, {lower: true, strict: true}); this._seenIds.add(headingId); return ` diff --git a/tools/markdown-to-html/docs-marked-renderer.spec.mts b/tools/markdown-to-html/docs-marked-renderer.spec.mts new file mode 100644 index 000000000000..e1003e1ba70b --- /dev/null +++ b/tools/markdown-to-html/docs-marked-renderer.spec.mts @@ -0,0 +1,129 @@ +import {DocsMarkdownRenderer} from './docs-marked-renderer.mjs'; +import * as marked from 'marked'; + +describe('DocsMarkdownRenderer', () => { + let renderer: DocsMarkdownRenderer; + beforeEach(() => { + renderer = new DocsMarkdownRenderer(); + }); + + function transform(markdown: string): string { + marked.setOptions({renderer}); + + return renderer.finalizeOutput(marked.parse(markdown, {async: false}), 'test.html'); + } + + it('generates regular headings for h1 and h2', () => { + expect(transform('# a')).toContain('-{%- highlight "typescript" -%} - type {$ alias.name | safe $} = {$ alias.typeDefinition | safe $}; -{%- end_highlight -%} -
+type {$ alias.name | safe $} = {$ alias.typeDefinition | safe $};