Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion adev/shared-docs/index.bzl
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
load("//adev/shared-docs/pipeline:_guides.bzl", _generate_guides = "generate_guides")
load("//adev/shared-docs/pipeline:_stackblitz.bzl", _generate_stackblitz = "generate_stackblitz")
load("//adev/shared-docs/pipeline:_navigation.bzl", _generate_nav_items = "generate_nav_items")
load("//adev/shared-docs/pipeline:_playground.bzl", _generate_playground = "generate_playground")
load("//adev/shared-docs/pipeline:_stackblitz.bzl", _generate_stackblitz = "generate_stackblitz")
load("//adev/shared-docs/pipeline:_tutorial.bzl", _generate_tutorial = "generate_tutorial")

generate_guides = _generate_guides
generate_stackblitz = _generate_stackblitz
generate_playground = _generate_playground
generate_tutorial = _generate_tutorial
generate_nav_items = _generate_nav_items
19 changes: 19 additions & 0 deletions adev/shared-docs/pipeline/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,24 @@ esbuild_esm_bundle(
],
)

esbuild_esm_bundle(
name = "navigation-bundle",
entry_point = "//adev/shared-docs/pipeline/navigation:index.ts",
output = "navigation.mjs",
platform = "node",
target = "es2022",
visibility = ["//visibility:public"],
deps = [
"//adev/shared-docs/pipeline/navigation",
],
)

exports_files([
"_guides.bzl",
"_stackblitz.bzl",
"_playground.bzl",
"_tutorial.bzl",
"_navigation.bzl",
"BUILD.bazel",
])

Expand Down Expand Up @@ -160,3 +173,9 @@ nodejs_binary(
entry_point = "//adev/shared-docs/pipeline:tutorial.mjs",
visibility = ["//visibility:public"],
)

nodejs_binary(
name = "navigation",
entry_point = "//adev/shared-docs/pipeline:navigation.mjs",
visibility = ["//visibility:public"],
)
61 changes: 61 additions & 0 deletions adev/shared-docs/pipeline/_navigation.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
load("@build_bazel_rules_nodejs//:providers.bzl", "run_node")

def _generate_nav_items(ctx):
"""Implementation of the navigation items data generator rule"""

# Set the arguments for the actions inputs and output location.
args = ctx.actions.args()

# Use a param file because we may have a large number of inputs.
args.set_param_file_format("multiline")
args.use_param_file("%s", use_always = True)

# Pass the set of source files.
args.add_joined(ctx.files.srcs, join_with = ",")

# Add BUILD file path to the arguments.
args.add(ctx.label.package)

# Add the nav item generation strategy to thte arguments.
args.add(ctx.attr.strategy)

# File declaration of the generated JSON file.
json_output = ctx.actions.declare_file("routes.json")

# Add the path to the output file to the arguments.
args.add(json_output.path)

run_node(
ctx = ctx,
inputs = depset(ctx.files.srcs),
executable = "_generate_nav_items",
outputs = [json_output],
arguments = [args],
)

# The return value describes what the rule is producing. In this case we need to specify
# the "DefaultInfo" with the output json file.
return [DefaultInfo(files = depset([json_output]))]

generate_nav_items = rule(
# Point to the starlark function that will execute for this rule.
implementation = _generate_nav_items,
doc = """Rule that generates navigation items data.""",

# The attributes that can be set to this rule.
attrs = {
"srcs": attr.label_list(
doc = """Markdown files that represent the page contents.""",
allow_empty = False,
allow_files = True,
),
"strategy": attr.string(
doc = """Represents the navigation items generation strategy.""",
),
"_generate_nav_items": attr.label(
default = Label("//adev/shared-docs/pipeline:navigation"),
executable = True,
cfg = "exec",
),
},
)
36 changes: 36 additions & 0 deletions adev/shared-docs/pipeline/navigation/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
load("//tools:defaults.bzl", "ts_library")

package(default_visibility = ["//visibility:public"])

ts_library(
name = "lib",
srcs = glob(
[
"*.ts",
],
exclude = [
"index.ts",
],
),
deps = [
"//adev/shared-docs/interfaces",
"@npm//@types/node",
"@npm//@webcontainer/api",
"@npm//fast-glob",
],
)

ts_library(
name = "navigation",
srcs = [
"index.ts",
],
visibility = [
"//adev/shared-docs:__subpackages__",
],
deps = [
":lib",
"//adev/shared-docs/interfaces",
"@npm//@types/node",
],
)
26 changes: 26 additions & 0 deletions adev/shared-docs/pipeline/navigation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*!
* @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 {readFileSync, writeFileSync} from 'fs';
import {generateNavItems} from './nav-items-gen';
import {getNavItemGenStrategy} from './strategies';

async function main() {
const [paramFilePath] = process.argv.slice(2);
const rawParamLines = readFileSync(paramFilePath, {encoding: 'utf8'}).split('\n');
const [joinedSrcs, packageDir, strategy, outputFilePath] = rawParamLines;

const srcs = joinedSrcs.split(',');

// Generate navigation data
const navData = await generateNavItems(srcs, getNavItemGenStrategy(strategy, packageDir));

writeFileSync(outputFilePath, JSON.stringify(navData));
}

await main();
59 changes: 59 additions & 0 deletions adev/shared-docs/pipeline/navigation/nav-items-gen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*!
* @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 fs from 'fs';
import readline from 'readline';
import {basename, dirname, resolve} from 'path';

import {NavigationItem} from '../../interfaces';
import {NavigationItemGenerationStrategy} from './types';

/**
* Generate navigations items by a provided strategy.
*
* @param mdFilesPaths Paths to the Markdown files that represent the page contents
* @param strategy Strategy
* @returns An array with navigation items
*/
export async function generateNavItems(
mdFilesPaths: string[],
strategy: NavigationItemGenerationStrategy,
): Promise<NavigationItem[]> {
const navItems: NavigationItem[] = [];
const {labelGeneratorFn, pathPrefix, contentPath} = strategy;

for (const path of mdFilesPaths) {
const fullPath = resolve(dirname(path), basename(path));
const name = path.split('/').pop()?.replace('.md', '')!;
const firstLine = await getMdFileHeading(fullPath);

navItems.push({
label: labelGeneratorFn(name, firstLine),
path: `${pathPrefix}/${name}`,
contentPath: `${contentPath}/${name}`,
});
}

return navItems;
}

/** Extract the first heading from a Markdown file. */
async function getMdFileHeading(filePath: string): Promise<string> {
const readStream = fs.createReadStream(filePath);
const rl = readline.createInterface({input: readStream});

for await (const line of rl) {
if (line.trim().startsWith('#')) {
rl.close();
readStream.destroy();
return line.replace(/^#+[ \t]+/, '');
}
}

return '';
}
54 changes: 54 additions & 0 deletions adev/shared-docs/pipeline/navigation/strategies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*!
* @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 {NavigationItemGenerationStrategy, Strategy} from './types';

// Should point to the website content.
// Update, if the location is updated or shared-docs is extracted from `adev/`.
const CONTENT_FOLDER_PATH = 'adev/src/content/';

// Ensure that all Strategy-ies are part of SUPPORTED_STRATEGIES by using a key-typed object.
const strategiesObj: {[key in Strategy]: null} = {errors: null, 'extended-diagnostics': null};
const SUPPORTED_STRATEGIES = Object.keys(strategiesObj);

/** Get navigation item generation strategy by a provided strategy string. */
export function getNavItemGenStrategy(
strategy: string,
packageDir: string,
): NavigationItemGenerationStrategy {
if (SUPPORTED_STRATEGIES.indexOf(strategy) === -1) {
throw new Error(
`Unsupported NavigationItem generation strategy "${strategy}". Supported: ${SUPPORTED_STRATEGIES.join(', ')}`,
);
}

switch (strategy as Strategy) {
case 'errors':
return errorsStrategy(packageDir);
case 'extended-diagnostics':
return extendedDiagnosticsStrategy(packageDir);
}
}

// "Errors" navigation items generation strategy
function errorsStrategy(packageDir: string): NavigationItemGenerationStrategy {
return {
pathPrefix: 'errors',
contentPath: packageDir.replace(CONTENT_FOLDER_PATH, ''),
labelGeneratorFn: (fileName, firstLine) => fileName + ': ' + firstLine,
};
}

// "Extended diagnostics" items generation strategy
function extendedDiagnosticsStrategy(packageDir: string): NavigationItemGenerationStrategy {
return {
pathPrefix: 'extended-diagnostics',
contentPath: packageDir.replace(CONTENT_FOLDER_PATH, ''),
labelGeneratorFn: (fileName, firstLine) => fileName + ': ' + firstLine,
};
}
17 changes: 17 additions & 0 deletions adev/shared-docs/pipeline/navigation/test/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")

package(default_visibility = ["//adev/shared-docs/pipeline/navigation:__subpackages__"])

ts_library(
name = "unit_test_lib",
testonly = True,
srcs = glob(["*.spec.ts"]),
deps = [
"//adev/shared-docs/pipeline/navigation:lib",
],
)

jasmine_node_test(
name = "unit_tests",
deps = [":unit_test_lib"],
)
50 changes: 50 additions & 0 deletions adev/shared-docs/pipeline/navigation/test/nav-items-gen.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* @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 {generateNavItems} from '../nav-items-gen';
import {NavigationItemGenerationStrategy} from '../types';
import fs from 'fs';
import readline from 'readline';

const readlineInterfaceMock = {
close: () => {},
async *[Symbol.asyncIterator]() {
yield '<!-- Comment -->';
yield 'Some random text';
yield '## Heading';
yield 'Some text';
},
};

describe('generateNavItems', () => {
it('should test the default case', async () => {
spyOn(fs, 'createReadStream').and.returnValue({destroy: () => null} as any);
spyOn(readline, 'createInterface').and.returnValue(readlineInterfaceMock as any);

const strategy: NavigationItemGenerationStrategy = {
pathPrefix: 'page',
contentPath: 'content/directory',
labelGeneratorFn: (fileName, firstLine) => fileName + ' // ' + firstLine,
};

const navItems = await generateNavItems(['directory/home.md', 'directory/about.md'], strategy);

expect(navItems).toEqual([
{
label: 'home // Heading',
path: 'page/home',
contentPath: 'content/directory/home',
},
{
label: 'about // Heading',
path: 'page/about',
contentPath: 'content/directory/about',
},
]);
});
});
22 changes: 22 additions & 0 deletions adev/shared-docs/pipeline/navigation/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*!
* @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
*/

/**
* `NavigationItem` generation strategy
*/
export type NavigationItemGenerationStrategy = {
/** App route path prefix. */
pathPrefix: string;
/** Content path where the source files are kept. */
contentPath: string;
/** Page/route label generator function. */
labelGeneratorFn: (fileName: string, firstLine: string) => string;
};

/** Strategy for navigation item generation. */
export type Strategy = 'errors' | 'extended-diagnostics';
1 change: 0 additions & 1 deletion adev/shared-docs/pipeline/tutorials/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ ts_library(
":editor",
"//adev/shared-docs/interfaces",
"@npm//@types/node",
"@npm//fast-glob",
],
)

Expand Down
2 changes: 0 additions & 2 deletions adev/shared-docs/utils/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ ts_library(
"//adev/shared-docs/providers",
"//packages/core",
"//packages/router",
"@npm//@types/node",
"@npm//@webcontainer/api",
"@npm//fflate",
],
)
Loading