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 @@

a

'); + expect(transform('## b')).toContain('

{ + expectEqualIgnoreLeadingWhitespace( + transform('### header 3'), + ` +
+ +
+ `, + ); + expectEqualIgnoreLeadingWhitespace( + transform('#### header 4'), + ` +
+ +
+ `, + ); + }); + + it('generates links', () => { + expect(transform('[some text](something "some title")')).toContain( + 'some text', + ); + expect(transform('[some text](#some-hash "some title")\n ### some hash')).toContain( + 'some text', + ); + expect(transform('[some text](https://google.com)')).toContain( + 'some text', + ); + }); + + it('generates html using new API', () => { + const result = transform(``); + expectEqualIgnoreLeadingWhitespace( + result, + '
', + ); + }); + + it('generates html using new API with no region', () => { + const result = transform(``); + expectEqualIgnoreLeadingWhitespace( + result, + '
', + ); + }); + + it('generates html using new API with no file and no region', () => { + const result = transform(``); + expectEqualIgnoreLeadingWhitespace( + result, + '
' + + '
' + + '
', + ); + }); + + it('generates html using old API', () => { + expect(transform('')).toEqual( + '
' + + '
' + + '
', + ); + }); + + it('does not allow id links with no matching id element', () => { + spyOn(console, 'error'); + spyOn(process, 'exit'); + transform('[text](#does-not-exist)'); + expect((console.error as jasmine.Spy).calls.allArgs()).toEqual([ + [jasmine.stringMatching(/Could not process file: test\.html/)], + [jasmine.stringMatching(/Found link to "does-not-exist"\. This heading does not exist/)], + ]); + expect(process.exit).toHaveBeenCalledWith(1); + }); + + function expectEqualIgnoreLeadingWhitespace(actual: string, expected: string) { + expect(stripLeadingWhitespace(actual.trim())).toEqual(stripLeadingWhitespace(expected.trim())); + } + + function stripLeadingWhitespace(s: string) { + return s.replace(/^\s*/gm, ''); + } +}); diff --git a/tools/markdown-to-html/docs-marked-renderer.spec.ts b/tools/markdown-to-html/docs-marked-renderer.spec.ts deleted file mode 100644 index 69b6f68fcfe9..000000000000 --- a/tools/markdown-to-html/docs-marked-renderer.spec.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {DocsMarkdownRenderer} from './docs-marked-renderer'; - -describe('DocsMarkdownRenderer', () => { - let renderer: DocsMarkdownRenderer; - beforeEach(() => { - renderer = new DocsMarkdownRenderer(); - }); - - it('generates regular headings for h1 and h2', () => { - expect(renderer.heading('a', 1, 'ignored')).toBe('

a

'); - expect(renderer.heading('b', 2, 'ignored')).toContain('

{ - const heading3 = renderer.heading('heading text', 3, 'link-id'); - expectEqualIgnoreLeadingWhitespace( - heading3, - ` -

- `, - ); - const heading4 = renderer.heading('heading text', 4, 'second-link-id'); - expectEqualIgnoreLeadingWhitespace( - heading4, - ` - - `, - ); - }); - - it('handles duplicate ids for headings', () => { - expect(renderer.heading('first', 3, 'id')).toContain('id="id"'); - expect(renderer.heading('second', 3, 'id')).toContain('id="id-1"'); - }); - - it('generates links', () => { - expect(renderer.link('something', 'some title', 'some text')).toEqual( - 'some text', - ); - expect(renderer.link('guide/something', 'some title', 'some text')).toEqual( - 'some text', - ); - expect(renderer.link('#some-hash', 'some title', 'some text')).toEqual( - 'some text', - ); - expect(renderer.link('https://google.com', 'some title', 'some text')).toEqual( - 'some text', - ); - }); - - it('generates html using new API', () => { - const result = renderer.html(``); - expectEqualIgnoreLeadingWhitespace( - result, - `
`, - ); - }); - - it('generates html using new API with no region', () => { - const result = renderer.html(``); - expectEqualIgnoreLeadingWhitespace( - result, - `
`, - ); - }); - - it('generates html using new API with no file and no region', () => { - const result = renderer.html(``); - expectEqualIgnoreLeadingWhitespace( - result, - `
`, - ); - }); - - it('generates html using old API', () => { - expect(renderer.html('')).toEqual( - '
', - ); - }); - - it('allows id links with matching id element', () => { - let output = renderer.link('#my-id', 'link title', 'link text'); - output += renderer.heading('heading text', 3, 'my-id'); - const result = renderer.finalizeOutput(output, 'filename.html'); - expect(result).toEqual(jasmine.stringMatching(/
{ - spyOn(console, 'error'); - spyOn(process, 'exit'); - let output = renderer.link('#my-id', 'link title', 'link text'); - renderer.finalizeOutput(output, 'filename.html'); - expect((console.error as jasmine.Spy).calls.allArgs()).toEqual([ - [jasmine.stringMatching(/Could not process file: filename.html.*/)], - [jasmine.stringMatching(/.*Found link to "my-id". This heading does not exist./)], - ]); - expect(process.exit).toHaveBeenCalledWith(1); - }); - - function expectEqualIgnoreLeadingWhitespace(actual: string, expected: string) { - expect(stripLeadingWhitespace(actual)).toEqual(stripLeadingWhitespace(expected)); - } - - function stripLeadingWhitespace(s: string) { - return s.replace(/^\s*/gm, ''); - } -}); diff --git a/tools/markdown-to-html/transform-markdown.mts b/tools/markdown-to-html/transform-markdown.mts new file mode 100644 index 000000000000..aca89ec5b93a --- /dev/null +++ b/tools/markdown-to-html/transform-markdown.mts @@ -0,0 +1,31 @@ +/** + * Script that will be used by the markdown_to_html Bazel rule in order to transform + * multiple markdown files into the equivalent HTML output. + */ + +import {readFileSync, writeFileSync} from 'fs'; +import {marked} from 'marked'; +import {join} from 'path'; +import {DocsMarkdownRenderer} from './docs-marked-renderer.mjs'; + +// Custom markdown renderer for transforming markdown files for the docs. +const markdownRenderer = new DocsMarkdownRenderer(); + +// Setup our custom docs renderer by default. +marked.setOptions({renderer: markdownRenderer}); + +// The script expects the bazel-bin path as first argument. All remaining arguments will be +// considered as markdown input files that need to be transformed. +const [bazelBinPath, ...inputFiles] = process.argv.slice(2); + +// Walk through each input file and write transformed markdown output to the specified +// Bazel bin directory. +inputFiles.forEach(inputPath => { + const outputPath = join(bazelBinPath, `${inputPath}.html`); + const htmlOutput = markdownRenderer.finalizeOutput( + marked.parse(readFileSync(inputPath, 'utf8'), {async: false}), + inputPath, + ); + + writeFileSync(outputPath, htmlOutput); +}); diff --git a/tools/markdown-to-html/transform-markdown.ts b/tools/markdown-to-html/transform-markdown.ts deleted file mode 100644 index 3fd96933e2d2..000000000000 --- a/tools/markdown-to-html/transform-markdown.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Script that will be used by the markdown_to_html Bazel rule in order to transform - * multiple markdown files into the equivalent HTML output. - */ - -import {readFileSync, writeFileSync} from 'fs'; -import {marked} from 'marked'; -import {join} from 'path'; -import {DocsMarkdownRenderer} from './docs-marked-renderer'; - -// Custom markdown renderer for transforming markdown files for the docs. -const markdownRenderer = new DocsMarkdownRenderer(); - -// Setup our custom docs renderer by default. -marked.setOptions({renderer: markdownRenderer}); - -if (require.main === module) { - // The script expects the bazel-bin path as first argument. All remaining arguments will be - // considered as markdown input files that need to be transformed. - const [bazelBinPath, ...inputFiles] = process.argv.slice(2); - - // Walk through each input file and write transformed markdown output to the specified - // Bazel bin directory. - inputFiles.forEach(inputPath => { - const outputPath = join(bazelBinPath, `${inputPath}.html`); - const htmlOutput = markdownRenderer.finalizeOutput( - marked.parse(readFileSync(inputPath, 'utf8'), {async: false}), - inputPath, - ); - - writeFileSync(outputPath, htmlOutput); - }); -} diff --git a/tools/region-parser/BUILD.bazel b/tools/region-parser/BUILD.bazel index 5af8f96805c5..099901cd88ee 100644 --- a/tools/region-parser/BUILD.bazel +++ b/tools/region-parser/BUILD.bazel @@ -4,6 +4,6 @@ package(default_visibility = ["//visibility:public"]) ts_project( name = "region-parser", - srcs = glob(["**/*.ts"]), + srcs = glob(["**/*.mts"]), tsconfig = "//tools:tsconfig", ) diff --git a/tools/region-parser/region-matchers/block-c.ts b/tools/region-parser/region-matchers/block-c.mts similarity index 100% rename from tools/region-parser/region-matchers/block-c.ts rename to tools/region-parser/region-matchers/block-c.mts diff --git a/tools/region-parser/region-matchers/html.ts b/tools/region-parser/region-matchers/html.mts similarity index 100% rename from tools/region-parser/region-matchers/html.ts rename to tools/region-parser/region-matchers/html.mts diff --git a/tools/region-parser/region-matchers/inline-c.ts b/tools/region-parser/region-matchers/inline-c.mts similarity index 100% rename from tools/region-parser/region-matchers/inline-c.ts rename to tools/region-parser/region-matchers/inline-c.mts diff --git a/tools/region-parser/region-parser.ts b/tools/region-parser/region-parser.mts similarity index 96% rename from tools/region-parser/region-parser.ts rename to tools/region-parser/region-parser.mts index 4e3afd1d6021..b184911b2031 100644 --- a/tools/region-parser/region-parser.ts +++ b/tools/region-parser/region-parser.mts @@ -1,6 +1,6 @@ -import {blockC} from './region-matchers/block-c'; -import {html} from './region-matchers/html'; -import {inlineC} from './region-matchers/inline-c'; +import {blockC} from './region-matchers/block-c.mjs'; +import {html} from './region-matchers/html.mjs'; +import {inlineC} from './region-matchers/inline-c.mjs'; export type Region = {lines: string[]; open: boolean}; export type RegionMap = {[regionName: string]: Region}; diff --git a/tools/tsconfig.json b/tools/tsconfig.json index 905f67a9f954..810d8d3a6e3f 100644 --- a/tools/tsconfig.json +++ b/tools/tsconfig.json @@ -1,5 +1,5 @@ { - "include": ["./**/*.ts"], + "include": ["./**/*.mts", "./**/*.ts"], "compilerOptions": { "target": "es2020", "module": "Node16", @@ -12,5 +12,5 @@ "downlevelIteration": true, "types": ["node"] }, - "exclude": ["**/*.spec.ts", "adev-api-extraction/**"] + "exclude": ["**/*.spec.ts", "**/*.spec.mts", "adev-api-extraction/**"] } diff --git a/tsconfig.json b/tsconfig.json index fc54afa77aa8..84b08be1dcab 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -53,6 +53,7 @@ "e2e/**/*.ts", "test/**/*.ts", "tools/**/*.ts", + "tools/**/*.mts", "integration/**/*.ts", "scripts/**/*.ts", "docs/**/*.ts"