refactor(docs-infra): build adev application using local generated assets (#53511)
Use local generated assets to build adev application. PR Close #53511
@@ -38,7 +38,3 @@ integration/typings_test_ts49/node_modules
|
||||
integration/typings_test_ts50/node_modules
|
||||
integration/typings_test_ts51/node_modules
|
||||
integration/typings_test_ts52/node_modules
|
||||
|
||||
|
||||
# Temporarily ignore all of adev
|
||||
adev/
|
||||
@@ -1,2 +0,0 @@
|
||||
/src/assets/tutorials
|
||||
/src/assets/previews
|
||||
@@ -1,24 +1,27 @@
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect")
|
||||
load("//:packages.bzl", "link_packages")
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
load("@npm//@angular-devkit/architect-cli:index.bzl", "architect")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# All source and configuration files required to build the docs app
|
||||
APPLICATION_FILES = [
|
||||
"//:angular.json",
|
||||
"adev/tsconfig.app.json",
|
||||
"adev/tsconfig.json",
|
||||
"adev/tsconfig.worker.json",
|
||||
"angular.json",
|
||||
"tsconfig.app.json",
|
||||
"tsconfig.json",
|
||||
"tsconfig.worker.json",
|
||||
] + glob(
|
||||
["adev/src/**/*"],
|
||||
["src/**/*"],
|
||||
exclude = [
|
||||
"adev/src/**/*.spec.ts",
|
||||
"src/**/*.spec.ts",
|
||||
# Temporarily exclude generated sources produced by the non-bazel
|
||||
# build until the whole project is built by bazel and this directory
|
||||
# isn't needed.
|
||||
"adev/src/generated/**/*",
|
||||
"src/generated/**/*",
|
||||
],
|
||||
)
|
||||
|
||||
APPLICATION_DEPS = [
|
||||
APPLICATION_DEPS = link_packages([
|
||||
"@npm//@angular-devkit/build-angular",
|
||||
"@npm//@angular/animations",
|
||||
"@npm//@angular/cdk",
|
||||
@@ -38,7 +41,33 @@ APPLICATION_DEPS = [
|
||||
"@npm//ogl",
|
||||
"@npm//rxjs",
|
||||
"@npm//typescript",
|
||||
]
|
||||
"//adev/src/assets/images",
|
||||
"//adev/src/assets/textures",
|
||||
"//adev/src/assets/previews",
|
||||
"//adev/src/assets:tutorials",
|
||||
"//adev/src/assets/icons",
|
||||
"//adev/src/assets:api",
|
||||
"//adev/src/assets:content",
|
||||
"@npm//@typescript/vfs",
|
||||
"@npm//@codemirror/state",
|
||||
"@npm//@codemirror/view",
|
||||
"@npm//@codemirror/language",
|
||||
"@npm//@codemirror/commands",
|
||||
"@npm//@codemirror/search",
|
||||
"@npm//@codemirror/autocomplete",
|
||||
"@npm//@codemirror/lint",
|
||||
"@npm//@codemirror/lang-html",
|
||||
"@npm//@codemirror/lang-angular",
|
||||
"@npm//@codemirror/lang-css",
|
||||
"@npm//@codemirror/lang-sass",
|
||||
"@npm//@codemirror/lang-javascript",
|
||||
"@npm//@lezer/highlight",
|
||||
"@npm//@lezer/javascript",
|
||||
"@npm//@lezer/common",
|
||||
"@npm//xterm",
|
||||
"@npm//xterm-addon-fit",
|
||||
"@npm//angular-split",
|
||||
])
|
||||
|
||||
copy_to_bin(
|
||||
name = "application_files_bin",
|
||||
@@ -56,9 +85,6 @@ architect(
|
||||
":application_files_bin",
|
||||
],
|
||||
output_dir = True,
|
||||
# When building with local packages (--config=aio_local_deps), RBE complains about
|
||||
# the input tree being too large (> 70,000 files).
|
||||
tags = ["no-remote-exec"],
|
||||
)
|
||||
|
||||
architect(
|
||||
@@ -73,7 +99,4 @@ architect(
|
||||
data = APPLICATION_DEPS + [
|
||||
":application_files_bin",
|
||||
],
|
||||
# When building with local packages (--config=aio_local_deps), RBE complains about
|
||||
# the input tree being too large (> 70,000 files).
|
||||
tags = ["no-remote-exec"],
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"$schema": "../node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": ".",
|
||||
"projects": {
|
||||
@@ -12,30 +12,33 @@
|
||||
"changeDetection": "OnPush"
|
||||
}
|
||||
},
|
||||
"root": "adev",
|
||||
"sourceRoot": "adev/src",
|
||||
"root": ".",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "adev",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"externalDependencies": [
|
||||
"path"
|
||||
],
|
||||
"outputPath": "dist/angular-dev",
|
||||
"index": "adev/src/index.html",
|
||||
"browser": "adev/src/main.ts",
|
||||
"server": "adev/src/main.server.ts",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"server": "src/main.server.ts",
|
||||
// TODO(josephperrott): enabled prerender
|
||||
"prerender": false,
|
||||
"polyfills": ["adev/src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "adev/tsconfig.app.json",
|
||||
"polyfills": ["src/polyfills.ts", "zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"adev/src/favicon.ico",
|
||||
"adev/src/robots.txt",
|
||||
"adev/src/assets"
|
||||
"src/favicon.ico",
|
||||
"src/robots.txt",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": ["@angular/docs/styles/global-styles.scss"],
|
||||
"scripts": [],
|
||||
"webWorkerTsConfig": "adev/tsconfig.worker.json",
|
||||
"webWorkerTsConfig": "tsconfig.worker.json",
|
||||
"optimization": {
|
||||
"fonts": {
|
||||
// TODO(josephperrott): enabled inline scripts
|
||||
@@ -1,116 +0,0 @@
|
||||
/*!
|
||||
* @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
|
||||
*/
|
||||
/* tslint:disable:no-console */
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {readFileSync} from 'fs';
|
||||
import {glob} from 'glob';
|
||||
import {join} from 'path';
|
||||
|
||||
import {SUB_NAVIGATION_DATA} from '../../src/app/sub-navigation-data';
|
||||
|
||||
// PATHS
|
||||
const PROJECT_FOLDER_PATH = join(__dirname, '../../src');
|
||||
|
||||
// SKIP Files
|
||||
const SKIPED_ORPHAN = ['kitchen-sink', 'error', 'examples'];
|
||||
|
||||
main();
|
||||
|
||||
async function main() {
|
||||
const contentDir = join(PROJECT_FOLDER_PATH, 'content');
|
||||
const mdFiles = await retrieveAllMarkdownFiles(contentDir);
|
||||
|
||||
const allowedPaths = new Set([
|
||||
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getPaths(item)),
|
||||
SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getPaths(item)),
|
||||
SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getPaths(item)),
|
||||
]);
|
||||
|
||||
const contentPaths = new Set([
|
||||
...SUB_NAVIGATION_DATA.docs.flatMap((item: any) => getContentPaths(item)),
|
||||
...SUB_NAVIGATION_DATA.reference.flatMap((item: any) => getContentPaths(item)),
|
||||
...SUB_NAVIGATION_DATA.footer.flatMap((item: any) => getContentPaths(item)),
|
||||
]);
|
||||
|
||||
// Regular expression to match relative links
|
||||
const regex = /\]\((?!https?:\/\/)([^)]+)\)/g;
|
||||
|
||||
const orphanFiles: string[] = [];
|
||||
for (const {path} of mdFiles) {
|
||||
if (
|
||||
!contentPaths.has(path.slice(0, -3)) &&
|
||||
!path.startsWith('tutorials') &&
|
||||
!SKIPED_ORPHAN.some((skip) => path.startsWith(skip))
|
||||
) {
|
||||
orphanFiles.push(path.slice(0, -3));
|
||||
}
|
||||
}
|
||||
if (orphanFiles.length > 0) {
|
||||
console.log('======== ORPHAN FILES =========\n');
|
||||
orphanFiles.forEach((file) => console.warn(file));
|
||||
}
|
||||
|
||||
const deadLinks: {path: string; links: string[]}[] = [];
|
||||
for (const {content, path} of mdFiles) {
|
||||
let match;
|
||||
const matches = [];
|
||||
while ((match = regex.exec(content)) !== null) {
|
||||
const relativeLink = match[1].split('#')[0];
|
||||
if (!allowedPaths.has(relativeLink) && relativeLink.startsWith('guide')) {
|
||||
matches.push(relativeLink);
|
||||
}
|
||||
}
|
||||
if (matches.length) {
|
||||
deadLinks.push({path, links: matches});
|
||||
}
|
||||
}
|
||||
if (deadLinks.length > 0) {
|
||||
console.warn('\n\n======== DEAD RELATIVE LINKS =========\n');
|
||||
deadLinks.forEach(({path, links}) => {
|
||||
console.warn(path);
|
||||
links.forEach((path) => console.log(`\t ${path}`));
|
||||
});
|
||||
}
|
||||
|
||||
if (deadLinks.length > 0 || orphanFiles.length > 0) {
|
||||
throw new Error('Check fails');
|
||||
}
|
||||
}
|
||||
|
||||
function getPaths(navItem: NavigationItem): string[] {
|
||||
return [
|
||||
...(navItem.path ? [navItem.path] : []),
|
||||
...(navItem.children ?? []).flatMap((item) => getPaths(item)),
|
||||
];
|
||||
}
|
||||
|
||||
function getContentPaths(navItem: NavigationItem): string[] {
|
||||
return [
|
||||
...(navItem.contentPath ? [navItem.contentPath] : []),
|
||||
...(navItem.children ?? []).flatMap((item) => getContentPaths(item)),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search the provided directory for all markdown files and
|
||||
* asyncronously load them.
|
||||
*/
|
||||
async function retrieveAllMarkdownFiles(
|
||||
baseDir: string,
|
||||
): Promise<{path: string; content: string}[]> {
|
||||
const files = await glob('**/*.md', {
|
||||
root: baseDir,
|
||||
cwd: baseDir,
|
||||
ignore: ['**/node_modules/**'],
|
||||
});
|
||||
|
||||
return files.map((fullPathToFile) => ({
|
||||
path: fullPathToFile,
|
||||
content: readFileSync(join(baseDir, fullPathToFile), {encoding: 'utf-8'}),
|
||||
}));
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
# Tutorial Scripts
|
||||
|
||||
The tutorials scripts are responsible for generating the tutorials source code and metadata by reading the tutorials content from [`src/content/tutorials`](/src/content/tutorials).
|
||||
|
||||
_See [the tutorials content README](/src/content/tutorials/README.md) for more information about the tutorials content._
|
||||
|
||||
For each tutorial, the following JSON files are generated and stored at `src/assets/tutorials`:
|
||||
|
||||
- `source-code/<tutorial-pathname>.json`: contains the tutorial source code in the [WebContainer](https://webcontainers.io) `FileSystemTree` format. It's
|
||||
used to mount the tutorial files in the WebContainer. See the WebContainer docs for more info: <https://webcontainers.io/guides/working-with-the-file-system>
|
||||
- `metadata/<tutorial-pathname>.json`: the tutorial metadata has all the information necessary to manage the tutorial in app.
|
||||
It contains the tutorial `config.json`, the previous and next step, the next tutorial, and the code editor file paths and contents.
|
||||
- `routes.json`: defines all routes for the tutorials, used at [`src/app/sub-navigation-data.ts`](/src/app/sub-navigation-data.ts) to populate the tutorials nav.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
yarn generate-tutorials
|
||||
```
|
||||
@@ -1,84 +0,0 @@
|
||||
/*!
|
||||
* @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 {NavigationItem} from '@angular/docs';
|
||||
|
||||
import {PagePrefix} from '../../src/app/core/enums/pages';
|
||||
|
||||
import type {TutorialNavigationData, TutorialNavigationItem, TutorialStep} from './tutorials-types';
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {TUTORIALS_ROUTES_NODE_PATH} from './utils/node-constants';
|
||||
|
||||
/** the step is used only in this function to sort the nav items */
|
||||
export type TutorialNavigationItemWithStep = TutorialNavigationItem & {
|
||||
tutorialData: TutorialNavigationData & {
|
||||
step: TutorialStep['step'];
|
||||
};
|
||||
};
|
||||
|
||||
export async function buildTutorialsNavigationItems(
|
||||
initialNavigationItems: TutorialNavigationItemWithStep[],
|
||||
) {
|
||||
const sortedInitialNavigationItems = initialNavigationItems.sort((a, b) => {
|
||||
if (!a.tutorialData?.step || !b.tutorialData?.step) return 0;
|
||||
|
||||
if (a.tutorialData.step < b.tutorialData.step) return -1;
|
||||
else if (a.tutorialData.step > b.tutorialData.step) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const addTutorialPagePrefixToNavItemPath = (path: string) => `${PagePrefix.TUTORIALS}/${path}`;
|
||||
|
||||
const navItemMap = new Map<NavigationItem['path'], TutorialNavigationItem>();
|
||||
|
||||
for (const navItem of sortedInitialNavigationItems) {
|
||||
const pathParts = navItem.path.split('/');
|
||||
const topLevelPath = pathParts[0];
|
||||
const topLevelNavItem = navItemMap.get(topLevelPath);
|
||||
|
||||
const isNavItemTopLevelPath = pathParts.length === 1;
|
||||
|
||||
const newNavItem: TutorialNavigationItem = {
|
||||
path: addTutorialPagePrefixToNavItemPath(navItem.path),
|
||||
label: navItem.label,
|
||||
contentPath: navItem.contentPath,
|
||||
tutorialData: navItem.tutorialData,
|
||||
};
|
||||
|
||||
if (!topLevelNavItem) {
|
||||
// create new map entry
|
||||
navItemMap.set(
|
||||
topLevelPath,
|
||||
isNavItemTopLevelPath ? newNavItem : ({children: [newNavItem]} as TutorialNavigationItem),
|
||||
);
|
||||
} else if (isNavItemTopLevelPath) {
|
||||
// add top level path to existing map entry
|
||||
navItemMap.set(topLevelPath, {
|
||||
...newNavItem,
|
||||
...topLevelNavItem,
|
||||
});
|
||||
} else if (!topLevelNavItem.children) {
|
||||
// create children in existing map entry
|
||||
topLevelNavItem.children = [newNavItem];
|
||||
|
||||
navItemMap.set(topLevelPath, topLevelNavItem);
|
||||
} else {
|
||||
// add children to existing map entry
|
||||
topLevelNavItem.children.push(newNavItem);
|
||||
|
||||
navItemMap.set(topLevelPath, topLevelNavItem);
|
||||
}
|
||||
}
|
||||
|
||||
const tutorialNavigationItems = Array.from(navItemMap.values());
|
||||
|
||||
await createDirectoryAndWriteFile(
|
||||
TUTORIALS_ROUTES_NODE_PATH,
|
||||
JSON.stringify(tutorialNavigationItems),
|
||||
);
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
/*!
|
||||
* @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 {mkdir, rm, writeFile} from 'fs/promises';
|
||||
import {basename, join, sep} from 'path';
|
||||
|
||||
import {
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
PLAYGROUND_ROUTE_NODE_PATH,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_COMMON_NODE_PATH,
|
||||
TUTORIALS_METADATA_NODE_PATH,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_SOURCE_CODE_NODE_PATH,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import {createDirectoryAndWriteFile, getFilesContents, globWithCwdPath} from './utils/filesystem';
|
||||
|
||||
import {
|
||||
TutorialNavigationItemWithStep,
|
||||
buildTutorialsNavigationItems,
|
||||
} from './generate-tutorials-routes';
|
||||
|
||||
import {copyCommonAssetsToAppAssets, getTutorialCommonFiles} from './tutorial-type/common';
|
||||
import {getHomepagePlaygroundFiles} from './tutorial-type/homepage';
|
||||
import {getIntroAndStepsTutorialFiles} from './tutorial-type/intro-and-steps';
|
||||
import {getTutorialPlaygroundFiles, isPlaygroundRouteData} from './tutorial-type/playground';
|
||||
import {PlaygroundFiles, TutorialFiles} from './tutorials-types';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
} from './utils/web-constants';
|
||||
|
||||
main();
|
||||
|
||||
/**
|
||||
* Builds and stores the tutorials source codes and metadata into JSON files.
|
||||
* These files are later used to load the tutorials into the WebContainer file system
|
||||
* and manage the tutorials in the embedded editor.
|
||||
*
|
||||
* @see https://webcontainers.io/guides/working-with-the-file-system
|
||||
*/
|
||||
export async function main() {
|
||||
const consoleTimeLabel = 'Tutorials files build done in';
|
||||
|
||||
// Informs the developers that this script is running and how much time it took
|
||||
console.info('Building tutorials files...');
|
||||
console.time(consoleTimeLabel);
|
||||
|
||||
const tutorialsFiles = await getTutorialsFiles();
|
||||
|
||||
// Clean up the directory before writing the files
|
||||
await rm(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}).catch(() => {
|
||||
// `rm` throws if the directory doesn't exist, meaning this script is
|
||||
// being executed for the first time
|
||||
});
|
||||
await Promise.all([
|
||||
mkdir(TUTORIALS_ASSETS_NODE_PATH, {recursive: true}),
|
||||
mkdir(TUTORIALS_SOURCE_CODE_NODE_PATH, {recursive: true}),
|
||||
mkdir(TUTORIALS_METADATA_NODE_PATH, {recursive: true}),
|
||||
]);
|
||||
|
||||
const writeFilesPromises: Promise<void>[] = [];
|
||||
const tutorialsRoutes: TutorialNavigationItemWithStep[] = [];
|
||||
|
||||
tutorialsFiles.forEach(({sourceCode, metadata, route, sourceCodeZip}, tutorialName) => {
|
||||
if (sourceCode) {
|
||||
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.json');
|
||||
const fileContents = JSON.stringify(sourceCode);
|
||||
|
||||
if (tutorialName.includes(sep)) {
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
|
||||
} else {
|
||||
writeFilesPromises.push(writeFile(filePath, fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
const filePath = join(TUTORIALS_METADATA_NODE_PATH, tutorialName + '.json');
|
||||
const fileContents = JSON.stringify(metadata);
|
||||
|
||||
if (tutorialName.includes(sep)) {
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, fileContents));
|
||||
} else {
|
||||
writeFilesPromises.push(writeFile(filePath, fileContents));
|
||||
}
|
||||
}
|
||||
|
||||
if (route) {
|
||||
if (isPlaygroundRouteData(route)) {
|
||||
writeFilesPromises.push(
|
||||
createDirectoryAndWriteFile(PLAYGROUND_ROUTE_NODE_PATH, JSON.stringify(route)),
|
||||
);
|
||||
} else {
|
||||
tutorialsRoutes.push({
|
||||
...route,
|
||||
path: tutorialName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceCodeZip) {
|
||||
const filePath = join(TUTORIALS_SOURCE_CODE_NODE_PATH, tutorialName + '.zip');
|
||||
writeFilesPromises.push(createDirectoryAndWriteFile(filePath, sourceCodeZip));
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
...writeFilesPromises,
|
||||
copyCommonAssetsToAppAssets(),
|
||||
buildTutorialsNavigationItems(tutorialsRoutes),
|
||||
]);
|
||||
|
||||
console.timeEnd(consoleTimeLabel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the tutorials source code and generate a Map representing the files
|
||||
* and contents that will be stored in the filesystem.
|
||||
*
|
||||
* Those files are later used by the `/tutorials` pages.
|
||||
*
|
||||
* The map key is the project name and the value contain the contents for each
|
||||
* .json file.
|
||||
*/
|
||||
async function getTutorialsFiles(): Promise<Map<string, TutorialFiles | PlaygroundFiles>> {
|
||||
const [tutorialsProjects, commonTutorialFiles] = await Promise.all([
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
ignore: [...(GLOB_OPTIONS.ignore as string[]), '*.md', TUTORIALS_COMMON_DIRECTORY],
|
||||
maxDepth: 1,
|
||||
cwd: TUTORIALS_PROJECT_PATH,
|
||||
}),
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: TUTORIALS_COMMON_NODE_PATH,
|
||||
nodir: true,
|
||||
}),
|
||||
]);
|
||||
|
||||
const tutorialsFiles = new Map<string, TutorialFiles | PlaygroundFiles>();
|
||||
|
||||
// tutorials depends on the common project files so here we handle it first
|
||||
const commonFilesContents = await getFilesContents(commonTutorialFiles);
|
||||
|
||||
tutorialsFiles.set(
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
await getTutorialCommonFiles(commonTutorialFiles, commonFilesContents),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
tutorialsProjects.map(async (tutorialPath) => {
|
||||
const tutorialName = basename(tutorialPath);
|
||||
|
||||
const [tutorialDirectories, tutorialFiles] = await Promise.all([
|
||||
globWithCwdPath('**/', {...GLOB_OPTIONS, cwd: tutorialPath}),
|
||||
globWithCwdPath('**', {...GLOB_OPTIONS, nodir: true, cwd: tutorialPath}),
|
||||
]);
|
||||
|
||||
try {
|
||||
if (tutorialName === TUTORIALS_PLAYGROUND_DIRECTORY) {
|
||||
const playgroundFiles = await getTutorialPlaygroundFiles(commonFilesContents);
|
||||
|
||||
playgroundFiles.forEach((playgroundFiles, playgroundName) => {
|
||||
tutorialsFiles.set(playgroundName, playgroundFiles);
|
||||
});
|
||||
} else if (tutorialName === TUTORIALS_HOMEPAGE_DIRECTORY) {
|
||||
const homepageFiles = await getHomepagePlaygroundFiles(commonFilesContents);
|
||||
tutorialsFiles.set(TUTORIALS_HOMEPAGE_DIRECTORY, homepageFiles);
|
||||
} else if (
|
||||
tutorialDirectories.some((directory) => directory.includes(STEPS_DIRECTORY)) &&
|
||||
tutorialDirectories.some((directory) => directory.includes(INTRO_DIRECTORY))
|
||||
) {
|
||||
const tutorialStepsFiles = await getIntroAndStepsTutorialFiles(
|
||||
tutorialName,
|
||||
tutorialDirectories,
|
||||
tutorialFiles,
|
||||
commonFilesContents,
|
||||
);
|
||||
|
||||
tutorialStepsFiles.forEach((stepFiles, stepName) => {
|
||||
tutorialsFiles.set(stepName, stepFiles);
|
||||
});
|
||||
} else {
|
||||
throw `Could not find "${INTRO_DIRECTORY}" and/or "${STEPS_DIRECTORY}" directories.`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`\n❌ Failed to parse tutorial at ${TUTORIALS_PROJECT_PATH}/${tutorialName}`);
|
||||
console.error(`\t${error}\n`);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return tutorialsFiles;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*!
|
||||
* @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 {join} from 'path';
|
||||
import {glob} from 'glob';
|
||||
import {input} from '@inquirer/prompts';
|
||||
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
import {parseConfig} from './tutorials-config';
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from './utils/node-constants';
|
||||
import {validatePathAnswer} from './utils/cli';
|
||||
|
||||
createTemplate();
|
||||
|
||||
async function createTemplate() {
|
||||
const existingTemplates = await glob('**/', {
|
||||
...GLOB_OPTIONS,
|
||||
absolute: false,
|
||||
cwd: TUTORIALS_PLAYGROUND_NODE_PATH,
|
||||
maxDepth: 1,
|
||||
});
|
||||
|
||||
const path = await askTemplatePath(existingTemplates);
|
||||
const title = await input({message: 'What is the title for the template?'});
|
||||
|
||||
const templatePath = `${TUTORIALS_PLAYGROUND_NODE_PATH}/${path}`;
|
||||
|
||||
const config = await parseConfig({
|
||||
type: TutorialType.EDITOR_ONLY,
|
||||
title,
|
||||
openFiles: ['src/main.ts'],
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
createDirectoryAndWriteFile(join(templatePath, CONFIG_FILE), JSON.stringify(config, null, 2)),
|
||||
createDirectoryAndWriteFile(join(templatePath, 'src', 'main.ts'), ''),
|
||||
]);
|
||||
|
||||
console.info(`\n✅ Template created at ${templatePath}`);
|
||||
}
|
||||
|
||||
function askTemplatePath(existingTemplates: string[]) {
|
||||
const placeholder = 'path-to-template';
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the template?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingTemplates, placeholder),
|
||||
});
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
/*!
|
||||
* @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 {join} from 'path';
|
||||
import {select, input, confirm} from '@inquirer/prompts';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {createDirectoryAndWriteFile} from './utils/filesystem';
|
||||
import {
|
||||
CONFIG_FILE,
|
||||
CONTENT_PLACEHOLDER,
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIAL_CONTENT_FILENAME,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import type {TutorialConfig} from './tutorials-types';
|
||||
import {parseConfig} from './tutorials-config';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
TutorialType,
|
||||
} from './utils/web-constants';
|
||||
import {validatePathAnswer} from './utils/cli';
|
||||
|
||||
type TutorialFields = TutorialConfig & {
|
||||
path: string;
|
||||
};
|
||||
|
||||
type TutorialToCreate = {
|
||||
intro: TutorialFields;
|
||||
steps: Array<TutorialFields & {step: number}>;
|
||||
};
|
||||
|
||||
createTutorial();
|
||||
|
||||
async function createTutorial() {
|
||||
const existingTutorials = await glob('**/', {
|
||||
ignore: [
|
||||
...(GLOB_OPTIONS.ignore as string[]),
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
],
|
||||
cwd: TUTORIALS_PROJECT_PATH,
|
||||
maxDepth: 1,
|
||||
});
|
||||
|
||||
const introPath = await askTutorialPath(existingTutorials);
|
||||
const introType = await askTutorialType('What is the type of the tutorial intro?');
|
||||
const introTitle = await input({message: 'What is the title for the tutorial intro?'});
|
||||
const introNextTutorial = await askAboutNextTutorial(existingTutorials);
|
||||
|
||||
const tutorialToCreate: TutorialToCreate = {
|
||||
intro: {
|
||||
type: introType as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: introTitle,
|
||||
path: introPath,
|
||||
openFiles: [''],
|
||||
nextTutorial: introNextTutorial,
|
||||
},
|
||||
steps: [],
|
||||
};
|
||||
|
||||
let addMoreSteps = true;
|
||||
let step = 1;
|
||||
|
||||
while (addMoreSteps) {
|
||||
console.info(`\nStep ${step}`);
|
||||
const path = await askTutorialStepPath(tutorialToCreate.steps);
|
||||
const type = await askTutorialType('What is the type of the step?');
|
||||
const title = await input({message: 'What is the title of the step?'});
|
||||
|
||||
tutorialToCreate.steps.push({
|
||||
step,
|
||||
type: type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
path,
|
||||
title,
|
||||
openFiles: [''],
|
||||
});
|
||||
|
||||
step++;
|
||||
|
||||
console.info();
|
||||
addMoreSteps = await confirm({message: 'Would you like to add another step?', default: true});
|
||||
}
|
||||
|
||||
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${tutorialToCreate.intro.path}`;
|
||||
|
||||
await Promise.all([
|
||||
createIntroFiles(tutorialToCreate.intro),
|
||||
...createStepsFiles(tutorialPath, tutorialToCreate.steps),
|
||||
]);
|
||||
|
||||
console.info(`\n✅ Tutorial created at ${tutorialPath}`);
|
||||
}
|
||||
|
||||
function askTutorialType(message: string = 'What is the type of the tutorial?') {
|
||||
return select<TutorialConfig['type']>({
|
||||
message: message,
|
||||
choices: [
|
||||
{
|
||||
name: 'CLI only: a tutorial for the Angular CLI in-browser unsing the embedded terminal',
|
||||
value: TutorialType.CLI,
|
||||
},
|
||||
{name: 'Local app: a tutorial for building an app locally', value: TutorialType.LOCAL},
|
||||
{
|
||||
name: 'Embedded editor: a tutorial for building an app in-browser using the embedded editor',
|
||||
value: TutorialType.EDITOR,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function askAboutNextTutorial(
|
||||
existingTutorials: string[],
|
||||
message: string = 'What should be the next tutorial?',
|
||||
) {
|
||||
return select({
|
||||
message,
|
||||
choices: [
|
||||
...existingTutorials.map((tutorial) => ({name: tutorial, value: tutorial})),
|
||||
{name: 'None', value: undefined},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
function askTutorialPath(existingTutorials: string[]) {
|
||||
const placeholder = 'path-to-tutorial';
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the tutorial?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingTutorials, placeholder),
|
||||
});
|
||||
}
|
||||
|
||||
function askTutorialStepPath(createdSteps: TutorialToCreate['steps']) {
|
||||
const placeholder = 'path-to-step';
|
||||
|
||||
const existingSteps = createdSteps.map((step) => step.path);
|
||||
|
||||
return input({
|
||||
message: 'What is the path of the step?',
|
||||
default: placeholder,
|
||||
validate: (answer) => validatePathAnswer(answer, existingSteps, placeholder),
|
||||
});
|
||||
}
|
||||
|
||||
async function createIntroFiles(intro: TutorialToCreate['intro']): Promise<Promise<void>[]> {
|
||||
const tutorialPath = `${TUTORIALS_PROJECT_PATH}/${intro.path}`;
|
||||
|
||||
const config = await parseConfig({
|
||||
type: intro.type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: intro.title,
|
||||
openFiles: intro.openFiles,
|
||||
nextTutorial: intro.nextTutorial,
|
||||
});
|
||||
|
||||
return [
|
||||
createDirectoryAndWriteFile(
|
||||
join(tutorialPath, INTRO_DIRECTORY, CONFIG_FILE),
|
||||
JSON.stringify(config, null, 2),
|
||||
),
|
||||
createDirectoryAndWriteFile(
|
||||
join(tutorialPath, INTRO_DIRECTORY, TUTORIAL_CONTENT_FILENAME),
|
||||
CONTENT_PLACEHOLDER,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
function createStepsFiles(
|
||||
tutorialPath: string,
|
||||
steps: TutorialToCreate['steps'],
|
||||
): Promise<Promise<void>[]>[] {
|
||||
const tutorialStepsPath = join(tutorialPath, STEPS_DIRECTORY);
|
||||
|
||||
return steps
|
||||
.map(async (step) => {
|
||||
const stepPath = join(tutorialStepsPath, `${step.step}-${step.path}`);
|
||||
|
||||
const config = await parseConfig({
|
||||
type: step.type as any, // Note: casting to any here as a workaround for the enum type error
|
||||
title: step.title,
|
||||
openFiles: step.openFiles,
|
||||
});
|
||||
|
||||
return [
|
||||
createDirectoryAndWriteFile(`${stepPath}/${CONFIG_FILE}`, JSON.stringify(config, null, 2)),
|
||||
createDirectoryAndWriteFile(
|
||||
`${stepPath}/${TUTORIAL_CONTENT_FILENAME}`,
|
||||
CONTENT_PLACEHOLDER,
|
||||
),
|
||||
];
|
||||
})
|
||||
.flat();
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
/*!
|
||||
* @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 {copyFile, mkdir} from 'fs/promises';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {filesAndContentsToRecord} from '../utils/filesystem';
|
||||
import {TUTORIALS_COMMON_DIRECTORY} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
import type {FileAndContent, TutorialFiles} from '../tutorials-types';
|
||||
import {TUTORIALS_COMMON_ASSETS_DEST, TUTORIALS_COMMON_ASSETS_SRC} from '../utils/node-constants';
|
||||
|
||||
const BEFORE_COMMON_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_COMMON_DIRECTORY}\/`);
|
||||
|
||||
export async function getTutorialCommonFiles(
|
||||
files: string[],
|
||||
filesContents: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const filesPath = files.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
|
||||
|
||||
const filesContentsWithoutPath = filesContents.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTree = getFileSystemTree(
|
||||
filesPath,
|
||||
filesAndContentsToRecord(filesContentsWithoutPath),
|
||||
);
|
||||
|
||||
return {sourceCode: fileSystemTree};
|
||||
}
|
||||
|
||||
export function getCleanFilePath(path: string) {
|
||||
return path.replace(BEFORE_COMMON_PATH_REGEX, '');
|
||||
}
|
||||
|
||||
export async function copyCommonAssetsToAppAssets() {
|
||||
const [commonAssets] = await Promise.all([
|
||||
// List of files for the assets from the tutorial common directory/app.
|
||||
glob(['**/*.png', '**/*.jpg'], {
|
||||
cwd: TUTORIALS_COMMON_ASSETS_SRC,
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
}),
|
||||
|
||||
// Ensure the common directory for tutorial assets exists.
|
||||
mkdir(TUTORIALS_COMMON_ASSETS_DEST, {recursive: true}),
|
||||
]);
|
||||
|
||||
await Promise.all(
|
||||
commonAssets.map((asset) =>
|
||||
copyFile(asset, asset.replace(TUTORIALS_COMMON_ASSETS_SRC, TUTORIALS_COMMON_ASSETS_DEST)),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
globWithCwdPath,
|
||||
} from '../utils/filesystem';
|
||||
import {
|
||||
GLOB_OPTIONS,
|
||||
TUTORIALS_COMMON_NODE_PATH,
|
||||
TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
} from '../utils/node-constants';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
} from '../tutorials-types';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
import {TUTORIALS_HOMEPAGE_DIRECTORY, TutorialType} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
const BEFORE_HOMEPAGE_PATH_REGEX = new RegExp(`.*\/${TUTORIALS_HOMEPAGE_DIRECTORY}\/`);
|
||||
|
||||
export async function getHomepagePlaygroundFiles(
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const homepagePlaygroundFiles = await globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
nodir: true,
|
||||
});
|
||||
|
||||
const config = await getTutorialConfig(homepagePlaygroundFiles);
|
||||
|
||||
if (config.type !== TutorialType.EDITOR_ONLY)
|
||||
throw new Error(`Homepage must be of type "${TutorialType.EDITOR_ONLY}"`);
|
||||
|
||||
const filesContents = await getFilesContents(homepagePlaygroundFiles);
|
||||
|
||||
const tutorialFiles = getTutorialFiles(filesContents, commonFiles);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(
|
||||
TUTORIALS_HOMEPAGE_NODE_PATH,
|
||||
config.openFiles,
|
||||
Object.keys(tutorialFiles),
|
||||
);
|
||||
} else {
|
||||
config.openFiles = Object.keys(tutorialFiles);
|
||||
}
|
||||
|
||||
const hasPackageJson = homepagePlaygroundFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(homepagePlaygroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
|
||||
}
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
homepagePlaygroundFiles,
|
||||
commonFiles.map(({path}) => path),
|
||||
getCleanFilePath,
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCode: getSourceCode(homepagePlaygroundFiles, filesContents),
|
||||
metadata: {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
dependencies,
|
||||
tutorialFiles,
|
||||
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getSourceCode(tutorialFiles: string[], filesContents: FileAndContent[]) {
|
||||
// TODO: reuse this as it's duplicated in getTutorialFiles
|
||||
const fileSystemTreeContents = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove steps paths from file contents
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTreeFiles = tutorialFiles
|
||||
.filter((filename) => shouldUseFileInWebContainer(filename))
|
||||
.map(getCleanFilePath);
|
||||
|
||||
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
|
||||
}
|
||||
|
||||
function getTutorialFiles(
|
||||
filesContents: FileAndContent[],
|
||||
commonFiles: FileAndContent[],
|
||||
): FileAndContentRecord {
|
||||
const playgroundFilesForCodeEditor = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(getCleanFilePath(path)))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const commonFilesForCodeEditor = commonFiles
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
return {
|
||||
...filesAndContentsToRecord(commonFilesForCodeEditor),
|
||||
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
|
||||
};
|
||||
}
|
||||
|
||||
function getCleanFilePath(path: string) {
|
||||
if (path.includes(TUTORIALS_COMMON_NODE_PATH)) return getCleanCommonFilePath(path);
|
||||
|
||||
return path.replace(BEFORE_HOMEPAGE_PATH_REGEX, '');
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename, dirname, join} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
getZipBuffer,
|
||||
globWithCwdPath,
|
||||
recordToFilesAndContents,
|
||||
} from '../utils/filesystem';
|
||||
import {
|
||||
ANSWER_DIRECTORY,
|
||||
GLOB_OPTIONS,
|
||||
INTRO_DIRECTORY,
|
||||
STEPS_DIRECTORY,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
} from '../utils/node-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
import {TutorialNavigationItemWithStep} from '../generate-tutorials-routes';
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import type {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
TutorialConfig,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
TutorialStep,
|
||||
} from '../tutorials-types';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_DIST_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
TutorialType,
|
||||
} from '../utils/web-constants';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
|
||||
export const TUTORIALS_DIRECTORY_REGEX = /^[a-z-0-9]*$/;
|
||||
export const STEPS_DIRECTORY_REGEX = /^([0-9]|[0-9][0-9])-[A-Za-z-0-9]*$/;
|
||||
const BEFORE_STEP_PATH_REGEX = new RegExp(`.*\/${STEPS_DIRECTORY}\/[^\/]+\/`);
|
||||
const BEFORE_INTRO_PATH_REGEX = new RegExp(`.*\/${INTRO_DIRECTORY}\/`);
|
||||
|
||||
export async function getIntroAndStepsTutorialFiles(
|
||||
tutorialName: string,
|
||||
tutorialDirectories: string[],
|
||||
tutorialFiles: string[],
|
||||
commonTutorialFiles: FileAndContent[],
|
||||
): Promise<Map<string, TutorialFiles>> {
|
||||
const tutorialSteps = getTutorialSteps(tutorialName, tutorialDirectories);
|
||||
|
||||
const introAndStepsFiles = new Map<TutorialStep['url'], TutorialFiles>();
|
||||
|
||||
const tutorialCommonFiles = tutorialFiles.filter((file) =>
|
||||
isCommonTutorialFile(tutorialName, file),
|
||||
);
|
||||
|
||||
const introFiles = await getTutorialIntroFiles(
|
||||
tutorialName,
|
||||
[
|
||||
...tutorialCommonFiles,
|
||||
...tutorialFiles.filter((file) => file.includes(`/${INTRO_DIRECTORY}/`)),
|
||||
],
|
||||
tutorialSteps,
|
||||
commonTutorialFiles,
|
||||
);
|
||||
|
||||
introAndStepsFiles.set(tutorialName, introFiles);
|
||||
|
||||
await Promise.all(
|
||||
tutorialSteps.map(async (step) => {
|
||||
const stepFiles = tutorialFiles.filter((file) => file.includes(step.path));
|
||||
|
||||
let nextTutorial;
|
||||
if (introFiles.route && 'tutorialData' in introFiles.route) {
|
||||
nextTutorial = introFiles.route.tutorialData.nextTutorial;
|
||||
}
|
||||
|
||||
const stepSourceCodeAndMetadata = await getStepFiles(
|
||||
[...tutorialCommonFiles, ...stepFiles],
|
||||
{
|
||||
...step,
|
||||
nextTutorial,
|
||||
},
|
||||
commonTutorialFiles,
|
||||
);
|
||||
|
||||
introAndStepsFiles.set(step.url, stepSourceCodeAndMetadata);
|
||||
}),
|
||||
);
|
||||
|
||||
return introAndStepsFiles;
|
||||
}
|
||||
|
||||
async function getTutorialIntroFiles(
|
||||
tutorialName: string,
|
||||
introTutorialFiles: string[],
|
||||
tutorialSteps: TutorialStep[],
|
||||
commonFilesContents: FileAndContent[],
|
||||
) {
|
||||
return getStepFiles(
|
||||
introTutorialFiles,
|
||||
{
|
||||
step: 0,
|
||||
name: tutorialName,
|
||||
path: dirname(introTutorialFiles[0]),
|
||||
nextStep: tutorialSteps.find((step) => step.step === 0 || step.step === 1)?.url, // assume the first tutorialSteps array item is the first step
|
||||
url: tutorialName,
|
||||
},
|
||||
commonFilesContents,
|
||||
);
|
||||
}
|
||||
|
||||
async function getStepFiles(
|
||||
stepFiles: string[],
|
||||
step: TutorialStep,
|
||||
commonFilesContents: FileAndContent[],
|
||||
): Promise<TutorialFiles> {
|
||||
const config = await getTutorialConfig(stepFiles);
|
||||
|
||||
// if src is present in the config, add files to the step files
|
||||
if (config.src) {
|
||||
const srcFiles = await getExternalTutorialFiles(step, config.src);
|
||||
|
||||
stepFiles.push(...srcFiles);
|
||||
}
|
||||
|
||||
const filesContents = await getFilesContents(stepFiles);
|
||||
|
||||
const route: Omit<TutorialNavigationItemWithStep, 'path'> = {
|
||||
label: config.title,
|
||||
|
||||
contentPath: getTutorialContentPath(stepFiles),
|
||||
|
||||
tutorialData: {
|
||||
type: config.type,
|
||||
title: config.title,
|
||||
|
||||
step: step.step,
|
||||
nextStep: step.nextStep,
|
||||
previousStep: step.previousStep,
|
||||
nextTutorial: config.nextTutorial ?? step.nextTutorial,
|
||||
},
|
||||
};
|
||||
|
||||
if (config.type === TutorialType.LOCAL) {
|
||||
const hasFilesToDownload =
|
||||
filesContents.filter(({path}) => shouldUseFileInWebContainer(path)).length > 0;
|
||||
|
||||
if (!hasFilesToDownload) {
|
||||
return {route};
|
||||
} else {
|
||||
const filesToDownload = getFilesToDownload(commonFilesContents, filesContents);
|
||||
|
||||
route.tutorialData.sourceCodeZipPath = join(
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
step.url + '.zip',
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCodeZip: await getZipBuffer(filesToDownload),
|
||||
route,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const tutorialFiles = getTutorialFiles(filesContents);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(step.path, config.openFiles, Object.keys(tutorialFiles));
|
||||
} else if (config.type === TutorialType.EDITOR) {
|
||||
config.openFiles = Object.keys(tutorialFiles);
|
||||
}
|
||||
|
||||
const hasPackageJson = stepFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(stepFiles, packageJson, getPackageJsonFromFiles(commonFilesContents));
|
||||
}
|
||||
|
||||
const fileSystemTreeContents = filesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({path: getCleanFilePath(path), content}));
|
||||
|
||||
const fileSystemTreeFiles = stepFiles.filter(shouldUseFileInWebContainer).map(getCleanFilePath);
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
stepFiles,
|
||||
commonFilesContents.map(({path}) => path),
|
||||
getCleanFilePath,
|
||||
);
|
||||
|
||||
const metadata: TutorialMetadata = {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
tutorialFiles,
|
||||
answerFiles: await getAnswerFiles(step, filesContents, config.answerSrc),
|
||||
openFiles: config.openFiles ?? Object.keys(tutorialFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(tutorialFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
dependencies,
|
||||
};
|
||||
|
||||
return {
|
||||
metadata,
|
||||
sourceCode: getFileSystemTree(
|
||||
fileSystemTreeFiles,
|
||||
filesAndContentsToRecord(fileSystemTreeContents),
|
||||
),
|
||||
route,
|
||||
};
|
||||
}
|
||||
|
||||
export function getTutorialSteps(
|
||||
tutorialName: string,
|
||||
tutorialDirectories: string[],
|
||||
): TutorialStep[] {
|
||||
const stepsDirectories = tutorialDirectories.filter((directory) => {
|
||||
const isStepDirectory = directory.includes(`${tutorialName}/${STEPS_DIRECTORY}/`);
|
||||
if (!isStepDirectory) return false;
|
||||
|
||||
const stepPathParts = directory.split('/');
|
||||
|
||||
// steps directory is always right after STEPS_DIRECTORY in the path, e.g. `learn-angular/steps/1-getting-started`
|
||||
const stepDirectoryIndex = stepPathParts.indexOf(STEPS_DIRECTORY) + 1;
|
||||
const step = stepPathParts[stepDirectoryIndex];
|
||||
|
||||
return step === stepPathParts.at(-1);
|
||||
});
|
||||
|
||||
const steps: TutorialStep[] = [];
|
||||
|
||||
for (const stepDirectoryPath of stepsDirectories) {
|
||||
const stepDirectory = basename(stepDirectoryPath);
|
||||
|
||||
// force the tutorials steps to follow the pattern steps/{step}-{name}
|
||||
if (!STEPS_DIRECTORY_REGEX.test(stepDirectory))
|
||||
throw `Invalid step directory '${stepDirectory}'. The step directory must start with a number > 0, followed by a hyphen and a name. For example: '1-intro'`;
|
||||
|
||||
const [step, ...nameParts] = stepDirectory.replace(`${STEPS_DIRECTORY}/`, '').split('-');
|
||||
const name = nameParts.join('-');
|
||||
|
||||
steps.push({
|
||||
step: Number(step),
|
||||
name,
|
||||
path: stepDirectoryPath,
|
||||
url: join(tutorialName, name),
|
||||
});
|
||||
}
|
||||
|
||||
return steps.map((step) => {
|
||||
const previousStep = steps.find(({step: previousStep}) => previousStep === step.step - 1);
|
||||
const nextStep = steps.find(({step: nextStep}) => nextStep === step.step + 1);
|
||||
|
||||
return {
|
||||
...step,
|
||||
nextStep: nextStep?.url,
|
||||
previousStep: previousStep?.url ?? tutorialName,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter files that won't be used in the WebContainer.
|
||||
*/
|
||||
function getTutorialFiles(
|
||||
tutorialFilesContents: FileAndContent[],
|
||||
): Record<FileAndContent['path'], FileAndContent['content']> {
|
||||
const tutorialFiles = tutorialFilesContents
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
return filesAndContentsToRecord(tutorialFiles);
|
||||
}
|
||||
|
||||
async function getAnswerFiles(
|
||||
step: TutorialStep,
|
||||
filesContents: FileAndContent[],
|
||||
answerSrc: TutorialConfig['answerSrc'],
|
||||
): Promise<FileAndContentRecord> {
|
||||
const removeAnswerPathFromFiles = (filesAndContents: FileAndContent[]) =>
|
||||
filesAndContents.map(({path, content}) => ({
|
||||
path: path.replace(`${ANSWER_DIRECTORY}/`, ''),
|
||||
content,
|
||||
}));
|
||||
|
||||
if (answerSrc) {
|
||||
const answerSrcFiles = await getExternalTutorialFiles(step, answerSrc);
|
||||
|
||||
const answerFiles = await getFilesContents(answerSrcFiles);
|
||||
|
||||
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
|
||||
}
|
||||
|
||||
const answerFiles = filesContents.filter(({path}) => path.includes(`${ANSWER_DIRECTORY}/`));
|
||||
|
||||
return getTutorialFiles(removeAnswerPathFromFiles(answerFiles));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get files external to the current tutorial directory.
|
||||
*/
|
||||
async function getExternalTutorialFiles(
|
||||
step: TutorialStep,
|
||||
relativePath: string,
|
||||
): Promise<string[]> {
|
||||
const externalFilesPath = join(step.path, relativePath);
|
||||
|
||||
const files = await globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
cwd: externalFilesPath,
|
||||
nodir: true,
|
||||
});
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function getTutorialContentPath(stepFiles: string[]) {
|
||||
const tutorialContentPath = stepFiles.find((filename) => filename.endsWith('README.md'));
|
||||
|
||||
if (!tutorialContentPath) {
|
||||
const tutorialPath = dirname(stepFiles[0]);
|
||||
|
||||
throw `Tutorial at ${tutorialPath} is missing a README.md for content`;
|
||||
}
|
||||
|
||||
const contentPathWithDistPath = tutorialContentPath.replace(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_DIST_PATH,
|
||||
);
|
||||
|
||||
return contentPathWithDistPath.replace(/\.[^.]+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean the file path by making it relative to the project root.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* getCleanFilePath('path-to-tutorial/intro/src/app/app.component.ts') => 'src/app/app.component.ts'
|
||||
* getCleanFilePath('path-to-tutorial/steps/1-step/src/app/app.component.ts') => 'src/app/app.component.ts'
|
||||
* ```
|
||||
*/
|
||||
function getCleanFilePath(path: string) {
|
||||
if (path.includes(TUTORIALS_COMMON_DIRECTORY)) return getCleanCommonFilePath(path);
|
||||
if (path.includes(STEPS_DIRECTORY)) return path.replace(BEFORE_STEP_PATH_REGEX, '');
|
||||
|
||||
return path.replace(BEFORE_INTRO_PATH_REGEX, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge common project files with the step files to get the final files to download.
|
||||
*/
|
||||
function getFilesToDownload(
|
||||
commonFilesContents: FileAndContent[],
|
||||
stepFilesWithoutAnswer: FileAndContent[],
|
||||
) {
|
||||
const commonFilesWithoutPath = commonFilesContents.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const stepFilesWithoutPath = stepFilesWithoutAnswer
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
// use object to easily override common files with step files
|
||||
const filesToDownload = {
|
||||
...filesAndContentsToRecord(commonFilesWithoutPath),
|
||||
...filesAndContentsToRecord(stepFilesWithoutPath),
|
||||
};
|
||||
|
||||
return recordToFilesAndContents(filesToDownload);
|
||||
}
|
||||
|
||||
function isCommonTutorialFile(tutorialPath: string, filePath: string) {
|
||||
return filePath.includes(join(tutorialPath, TUTORIALS_COMMON_DIRECTORY));
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
/*!
|
||||
* @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 {glob} from 'glob';
|
||||
import {basename, join, sep} from 'path';
|
||||
|
||||
import {
|
||||
filesAndContentsToRecord,
|
||||
getFilesContents,
|
||||
getPackageJsonFromFiles,
|
||||
globWithCwdPath,
|
||||
} from '../utils/filesystem';
|
||||
import {CONFIG_FILE, GLOB_OPTIONS, TUTORIALS_PLAYGROUND_NODE_PATH} from '../utils/node-constants';
|
||||
import {
|
||||
DEFAULT_PLAYGROUND_TEMPLATE,
|
||||
STARTER_PLAYGROUND_TEMPLATE,
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
TutorialType,
|
||||
} from '../utils/web-constants';
|
||||
import {getFileSystemTree, shouldUseFileInWebContainer} from '../utils/webcontainers';
|
||||
|
||||
import {getTutorialConfig, validateOpenFilesConfig} from '../tutorials-config';
|
||||
import {
|
||||
FileAndContent,
|
||||
FileAndContentRecord,
|
||||
PlaygroundFiles,
|
||||
PlaygroundRouteData,
|
||||
PlaygroundTemplate,
|
||||
TutorialConfig,
|
||||
TutorialFiles,
|
||||
TutorialMetadata,
|
||||
} from '../tutorials-types';
|
||||
import {getAllFiles, validatePackageJson} from '../utils/metadata';
|
||||
import {getCleanFilePath as getCleanCommonFilePath} from './common';
|
||||
|
||||
// used to avoid recreating a regex in loops
|
||||
const beforePlaygroundPathRegexCache = new Map<string, RegExp>();
|
||||
|
||||
export async function getTutorialPlaygroundFiles(
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<Map<string, PlaygroundFiles>> {
|
||||
const cwd = TUTORIALS_PLAYGROUND_NODE_PATH;
|
||||
|
||||
const [playgroundProjects, playgroundFiles] = await Promise.all([
|
||||
glob('*/', {
|
||||
ignore: [TUTORIALS_COMMON_DIRECTORY],
|
||||
cwd,
|
||||
}),
|
||||
|
||||
globWithCwdPath('**', {
|
||||
...GLOB_OPTIONS,
|
||||
nodir: true,
|
||||
cwd,
|
||||
}),
|
||||
]);
|
||||
|
||||
const playgroundCommonFiles = playgroundFiles.filter(isPlaygroundCommonFile);
|
||||
|
||||
const playgroundFilesMap = new Map<string, PlaygroundFiles>();
|
||||
|
||||
const templates: PlaygroundTemplate[] = [];
|
||||
|
||||
await Promise.all(
|
||||
playgroundProjects.map(async (project) => {
|
||||
const projectFiles = playgroundFiles.filter((file) =>
|
||||
file.startsWith(`${cwd}${sep}${project}${sep}`),
|
||||
);
|
||||
|
||||
if (!projectFiles.length) {
|
||||
throw new Error(
|
||||
`Project at ${join(TUTORIALS_PLAYGROUND_NODE_PATH, project)} has no files.`,
|
||||
);
|
||||
}
|
||||
|
||||
const projectFilesAndPlaygroundCommonFiles = [...playgroundCommonFiles, ...projectFiles];
|
||||
|
||||
const projectPath = join(TUTORIALS_PLAYGROUND_DIRECTORY, project);
|
||||
|
||||
const {sourceCode, metadata, route} = await getPlaygroundFiles(
|
||||
projectPath,
|
||||
projectFilesAndPlaygroundCommonFiles,
|
||||
commonFiles,
|
||||
);
|
||||
|
||||
// store only sourceCode and metadata per playground template
|
||||
playgroundFilesMap.set(projectPath, {sourceCode, metadata});
|
||||
|
||||
// compose playground templates from each project route object
|
||||
if (route) templates.push(...route!.templates);
|
||||
}),
|
||||
);
|
||||
|
||||
const defaultTemplate = templates.find((template) =>
|
||||
template.path.endsWith(DEFAULT_PLAYGROUND_TEMPLATE),
|
||||
);
|
||||
if (!defaultTemplate) {
|
||||
throw new Error(`Invalid default playground template path '${DEFAULT_PLAYGROUND_TEMPLATE}'`);
|
||||
}
|
||||
|
||||
const starterTemplate = templates.find((template) =>
|
||||
template.path.endsWith(STARTER_PLAYGROUND_TEMPLATE),
|
||||
);
|
||||
if (!starterTemplate) {
|
||||
throw new Error(`Invalid starter playground template path '${STARTER_PLAYGROUND_TEMPLATE}'`);
|
||||
}
|
||||
|
||||
playgroundFilesMap.set(TUTORIALS_PLAYGROUND_DIRECTORY, {
|
||||
route: {
|
||||
templates: templates.sort((a, b) => {
|
||||
const aPath = a.path.toLowerCase();
|
||||
const bPath = b.path.toLowerCase();
|
||||
|
||||
if (aPath < bPath) return -1;
|
||||
if (aPath > bPath) return 1;
|
||||
return 0;
|
||||
}),
|
||||
defaultTemplate,
|
||||
starterTemplate,
|
||||
},
|
||||
});
|
||||
|
||||
return playgroundFilesMap;
|
||||
}
|
||||
|
||||
async function getPlaygroundFiles(
|
||||
project: string,
|
||||
playgroundFiles: string[],
|
||||
commonFiles: FileAndContent[],
|
||||
): Promise<PlaygroundFiles> {
|
||||
const config = await getTutorialConfig(playgroundFiles);
|
||||
|
||||
if (config.type !== TutorialType.EDITOR_ONLY)
|
||||
throw new Error(`Playground must be of type "${TutorialType.EDITOR_ONLY}"`);
|
||||
|
||||
const filesContents = await getFilesContents(playgroundFiles);
|
||||
|
||||
const projectFiles = getProjectFiles(project, config.openFiles, filesContents, commonFiles);
|
||||
|
||||
if (config.openFiles) {
|
||||
validateOpenFilesConfig(
|
||||
TUTORIALS_PLAYGROUND_NODE_PATH,
|
||||
config.openFiles,
|
||||
Object.keys(projectFiles),
|
||||
);
|
||||
}
|
||||
|
||||
const hasPackageJson = playgroundFiles.some((file) => basename(file) === 'package.json');
|
||||
|
||||
let dependencies: TutorialMetadata['dependencies'];
|
||||
|
||||
if (hasPackageJson) {
|
||||
const packageJson = getPackageJsonFromFiles(filesContents);
|
||||
|
||||
dependencies = {
|
||||
...packageJson.dependencies,
|
||||
...packageJson.devDependencies,
|
||||
};
|
||||
|
||||
validatePackageJson(playgroundFiles, packageJson, getPackageJsonFromFiles(commonFiles));
|
||||
}
|
||||
|
||||
const allFiles = getAllFiles(
|
||||
playgroundFiles,
|
||||
commonFiles.map(({path}) => path),
|
||||
(path: string) => getCleanFilePath(project, path),
|
||||
);
|
||||
|
||||
return {
|
||||
sourceCode: getSourceCode(project, playgroundFiles, filesContents),
|
||||
metadata: {
|
||||
type: config.type,
|
||||
allFiles,
|
||||
dependencies,
|
||||
tutorialFiles: projectFiles,
|
||||
openFiles: config.openFiles ?? Object.keys(projectFiles),
|
||||
hiddenFiles: config.openFiles
|
||||
? Object.keys(projectFiles).filter((filename) => !config.openFiles!.includes(filename))
|
||||
: [],
|
||||
},
|
||||
route: {
|
||||
templates: [{path: project, label: config.title}],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getProjectFiles(
|
||||
project: string,
|
||||
openFiles: TutorialConfig['openFiles'],
|
||||
playgroundFilesContents: FileAndContent[],
|
||||
commonFiles: FileAndContent[],
|
||||
): FileAndContentRecord {
|
||||
const playgroundFilesForCodeEditor = playgroundFilesContents
|
||||
// remove files that should not be shown in the code editor
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove playground path from file paths
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(project, path),
|
||||
content,
|
||||
}));
|
||||
|
||||
if (!openFiles) return filesAndContentsToRecord(playgroundFilesForCodeEditor);
|
||||
|
||||
const commonFilesForCodeEditor = commonFiles
|
||||
// remove files that should not be shown in the code editor
|
||||
.filter(({path}) => shouldUseFileInWebContainer(getCleanCommonFilePath(path)))
|
||||
// remove common path from file paths
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanCommonFilePath(path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const commonAndPlaygroundFiles = {
|
||||
...filesAndContentsToRecord(commonFilesForCodeEditor),
|
||||
...filesAndContentsToRecord(playgroundFilesForCodeEditor),
|
||||
};
|
||||
|
||||
const openFilesConfigWithContents: FileAndContentRecord = {};
|
||||
|
||||
for (const openFile of openFiles) {
|
||||
if (commonAndPlaygroundFiles[openFile]) {
|
||||
openFilesConfigWithContents[openFile] = commonAndPlaygroundFiles[openFile];
|
||||
} else {
|
||||
throw `At ${TUTORIALS_PLAYGROUND_NODE_PATH}/${CONFIG_FILE}.\n\tCould not find file "${openFile}" in playground or common files`;
|
||||
}
|
||||
}
|
||||
|
||||
return openFilesConfigWithContents;
|
||||
}
|
||||
|
||||
export function getSourceCode(
|
||||
project: string,
|
||||
tutorialFiles: string[],
|
||||
filesContents: FileAndContent[],
|
||||
) {
|
||||
const fileSystemTreeContents = filesContents
|
||||
// filter out config files
|
||||
.filter(({path}) => shouldUseFileInWebContainer(path))
|
||||
// remove steps paths from file contents
|
||||
.map(({path, content}) => ({
|
||||
path: getCleanFilePath(project, path),
|
||||
content,
|
||||
}));
|
||||
|
||||
const fileSystemTreeFiles = tutorialFiles
|
||||
.filter(shouldUseFileInWebContainer)
|
||||
.map((filename) => getCleanFilePath(project, filename));
|
||||
|
||||
return getFileSystemTree(fileSystemTreeFiles, filesAndContentsToRecord(fileSystemTreeContents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a regex that matches the string before the project name in a path.
|
||||
*
|
||||
* This is used to remove the project name from the path, keeping only the path
|
||||
* relative to the project root.
|
||||
*/
|
||||
function getBeforePlaygroundProjectRegex(project: string): RegExp {
|
||||
const projectRegex = beforePlaygroundPathRegexCache.get(project);
|
||||
if (projectRegex) return projectRegex;
|
||||
|
||||
const newRegex = new RegExp(`.*\/${project}\/`);
|
||||
beforePlaygroundPathRegexCache.set(project, newRegex);
|
||||
|
||||
return newRegex;
|
||||
}
|
||||
|
||||
function getCleanFilePath(project: string, path: string) {
|
||||
if (isPlaygroundCommonFile(path)) {
|
||||
return getCleanCommonFilePath(path);
|
||||
}
|
||||
|
||||
return path.replace(getBeforePlaygroundProjectRegex(project), '');
|
||||
}
|
||||
|
||||
export function isPlaygroundRouteData(
|
||||
routeData: PlaygroundRouteData | TutorialFiles['route'],
|
||||
): routeData is PlaygroundRouteData {
|
||||
return (routeData as PlaygroundRouteData).templates !== undefined;
|
||||
}
|
||||
|
||||
function isPlaygroundCommonFile(path: string) {
|
||||
return path.includes(TUTORIALS_COMMON_DIRECTORY);
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename, join} from 'path';
|
||||
import {readdir} from 'fs/promises';
|
||||
import {glob} from 'glob';
|
||||
|
||||
import {getFileContents} from './utils/filesystem';
|
||||
import {
|
||||
CONFIG_FILE,
|
||||
CONFIG_KEYS,
|
||||
REQUIRED_CONFIGS,
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
} from './utils/node-constants';
|
||||
|
||||
import type {TutorialConfig} from './tutorials-types';
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
|
||||
export async function getTutorialConfig(tutorialFiles: string[]): Promise<TutorialConfig> {
|
||||
const configFilePath = tutorialFiles.find((file) => basename(file) === CONFIG_FILE);
|
||||
|
||||
if (!configFilePath) throw `Can't find ${CONFIG_FILE}`;
|
||||
|
||||
const configFileContents = await getFileContents(configFilePath);
|
||||
|
||||
let config: TutorialConfig;
|
||||
|
||||
try {
|
||||
config = JSON.parse(String(configFileContents));
|
||||
config = await parseConfig(config);
|
||||
|
||||
return config;
|
||||
} catch (err) {
|
||||
throw `${configFilePath.replace(`${TUTORIALS_PROJECT_PATH}/`, '')}: ${err}`;
|
||||
}
|
||||
}
|
||||
|
||||
export async function parseConfig(config: TutorialConfig): Promise<TutorialConfig> {
|
||||
const tutorialConfigKeys = Object.keys(config);
|
||||
|
||||
REQUIRED_CONFIGS.forEach((requiredConfig) => {
|
||||
if (!tutorialConfigKeys.includes(requiredConfig)) {
|
||||
throw `missing required config '${requiredConfig}'`;
|
||||
}
|
||||
});
|
||||
|
||||
for (const key of tutorialConfigKeys) {
|
||||
// @ts-expect-error
|
||||
if (!CONFIG_KEYS.includes(key)) throw `invalid config '${key}'`;
|
||||
}
|
||||
|
||||
if (config.nextTutorial) {
|
||||
if (typeof config.nextTutorial !== 'string') {
|
||||
throw '"nextTutorial" must be a string';
|
||||
}
|
||||
|
||||
const path = join(TUTORIALS_PROJECT_PATH, config.nextTutorial);
|
||||
|
||||
// check if nextTutorial directory exists
|
||||
try {
|
||||
await readdir(path);
|
||||
} catch (error) {
|
||||
throw `the nextTutorial "${config.nextTutorial}" doesn't exist at ${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.src) {
|
||||
if (typeof config.src !== 'string') {
|
||||
throw '"src" must be a string';
|
||||
}
|
||||
|
||||
// check if src directory exists
|
||||
const srcFiles = await glob(`**/${config.src}`, {cwd: TUTORIALS_PROJECT_PATH});
|
||||
|
||||
if (!srcFiles.length) throw `could not find "src" "${config.src}"`;
|
||||
}
|
||||
|
||||
if (config.answerSrc) {
|
||||
if (typeof config.answerSrc !== 'string') {
|
||||
throw '"answerSrc" must be a string';
|
||||
}
|
||||
|
||||
// check if answerSrc directory exists
|
||||
const answerSrcFiles = await glob(`**/${config.answerSrc}`, {cwd: TUTORIALS_PROJECT_PATH});
|
||||
|
||||
if (!answerSrcFiles.length) throw `could not find "answerSrc" "${config.answerSrc}"`;
|
||||
}
|
||||
|
||||
switch (config.type) {
|
||||
case TutorialType.EDITOR:
|
||||
assertOpenFiles(config.openFiles);
|
||||
|
||||
return config;
|
||||
|
||||
case TutorialType.CLI:
|
||||
return config;
|
||||
|
||||
case TutorialType.LOCAL:
|
||||
return config;
|
||||
|
||||
case TutorialType.EDITOR_ONLY:
|
||||
assertOpenFiles(config.openFiles);
|
||||
|
||||
return config;
|
||||
|
||||
default:
|
||||
throw 'invalid config';
|
||||
}
|
||||
}
|
||||
|
||||
function assertOpenFiles(
|
||||
openFiles: TutorialConfig['openFiles'],
|
||||
): asserts openFiles is string[] | undefined {
|
||||
if (openFiles === undefined) return;
|
||||
|
||||
const errorMessage = '"openFiles" must be an array of strings';
|
||||
|
||||
if (!Array.isArray(openFiles)) {
|
||||
throw errorMessage;
|
||||
}
|
||||
|
||||
for (const openFile of openFiles) {
|
||||
if (typeof openFile !== 'string') throw errorMessage;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if openFiles exist in tutorial files.
|
||||
*/
|
||||
export function validateOpenFilesConfig(
|
||||
tutorialPath: string,
|
||||
openFiles: NonNullable<TutorialConfig['openFiles']>,
|
||||
tutorialFiles: string[],
|
||||
) {
|
||||
for (const openFile of openFiles) {
|
||||
if (!tutorialFiles.includes(openFile)) {
|
||||
throw `At: ${tutorialPath}/${CONFIG_FILE}\n\tCould not find "${openFile}" in the tutorial files`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*!
|
||||
* @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 type {FileSystemTree} from '@webcontainer/api';
|
||||
import type {TutorialNavigationItemWithStep} from './generate-tutorials-routes';
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
import {TutorialType} from './utils/web-constants';
|
||||
|
||||
/**
|
||||
* Represents the contents of the tutorial files to be generated by the build script
|
||||
*/
|
||||
export type TutorialFiles = {
|
||||
sourceCode?: FileSystemTree;
|
||||
metadata?: TutorialMetadata;
|
||||
sourceCodeZip?: Buffer;
|
||||
route?: Omit<TutorialNavigationItemWithStep, 'path'>;
|
||||
};
|
||||
|
||||
export type PlaygroundFiles = {
|
||||
sourceCode?: FileSystemTree;
|
||||
metadata?: TutorialMetadata;
|
||||
sourceCodeZip?: undefined;
|
||||
route?: PlaygroundRouteData;
|
||||
};
|
||||
|
||||
/** Represents the contents of the tutorial config file */
|
||||
export type TutorialMetadata = {
|
||||
type: TutorialConfig['type'];
|
||||
|
||||
/** a record of all tutorials filenames and its contents */
|
||||
tutorialFiles: FileAndContentRecord;
|
||||
|
||||
/** a record of filenames and contents for the tutorial answer */
|
||||
answerFiles?: FileAndContentRecord;
|
||||
|
||||
/** files that are part of the project but are not visible in the code editor */
|
||||
hiddenFiles: string[];
|
||||
|
||||
/**
|
||||
* All files in the project, used to find the difference between new and old projects
|
||||
* when changing projects
|
||||
*/
|
||||
allFiles: string[];
|
||||
|
||||
openFiles: NonNullable<TutorialConfig['openFiles']>;
|
||||
|
||||
/** whether a package.json exists */
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type TutorialStep = {
|
||||
step: number;
|
||||
name: string;
|
||||
path: string;
|
||||
url: string;
|
||||
nextStep?: TutorialStep['url'];
|
||||
previousStep?: TutorialStep['url'];
|
||||
nextTutorial?: string;
|
||||
};
|
||||
|
||||
export type TutorialConfig =
|
||||
| EditorTutorialConfig
|
||||
| LocalTutorialConfig
|
||||
| CliTutorialConfig
|
||||
| EditorOnlyTutorialConfig;
|
||||
|
||||
export interface TutorialConfigBase {
|
||||
type: TutorialType;
|
||||
|
||||
/** The tutorial title */
|
||||
title: string;
|
||||
|
||||
/** The name of the tutorial folder that will be started after the current one ends. */
|
||||
nextTutorial?: string;
|
||||
|
||||
/** The path to the tutorial src folder when it's external to the tutorial */
|
||||
src?: string;
|
||||
|
||||
/** The path to the tutorial answer folder when it's external to the tutorial */
|
||||
answerSrc?: string;
|
||||
|
||||
/** An array of files to be open in the editor */
|
||||
openFiles?: string[];
|
||||
}
|
||||
|
||||
/** Represents a tutorial config with all the embedded editor components enabled */
|
||||
export interface EditorTutorialConfig extends TutorialConfigBase {
|
||||
type: TutorialType.EDITOR;
|
||||
}
|
||||
|
||||
/** Represents a tutorial config that won't use the embedded editor */
|
||||
export interface LocalTutorialConfig extends TutorialConfigBase {
|
||||
type: TutorialType.LOCAL;
|
||||
|
||||
// fields that must be undefined for local app tutorials
|
||||
openFiles?: undefined;
|
||||
src?: undefined;
|
||||
answerSrc?: undefined;
|
||||
}
|
||||
|
||||
/** Represents a tutorial config that supports only the interactive terminal for the Angular CLI */
|
||||
export type CliTutorialConfig = Omit<LocalTutorialConfig, 'type'> & {
|
||||
type: TutorialType.CLI;
|
||||
};
|
||||
|
||||
export type EditorOnlyTutorialConfig = Omit<EditorTutorialConfig, 'type'> & {
|
||||
type: TutorialType.EDITOR_ONLY;
|
||||
};
|
||||
|
||||
export type FileAndContent = {
|
||||
path: string;
|
||||
content: string | Buffer;
|
||||
};
|
||||
|
||||
export type FileAndContentRecord = Record<FileAndContent['path'], FileAndContent['content']>;
|
||||
|
||||
export type TutorialNavigationItem = {
|
||||
path: NonNullable<NavigationItem['path']>;
|
||||
label: NonNullable<NavigationItem['label']>;
|
||||
children?: TutorialNavigationItem[];
|
||||
parent?: TutorialNavigationItem;
|
||||
contentPath?: string;
|
||||
tutorialData: TutorialNavigationData;
|
||||
};
|
||||
|
||||
export type TutorialNavigationData = {
|
||||
type: TutorialConfig['type'];
|
||||
title: TutorialConfig['title'];
|
||||
nextStep?: string;
|
||||
previousStep?: string;
|
||||
nextTutorial?: string;
|
||||
sourceCodeZipPath?: string;
|
||||
};
|
||||
|
||||
export type PlaygroundRouteData = {
|
||||
templates: PlaygroundTemplate[];
|
||||
defaultTemplate?: PlaygroundTemplate;
|
||||
starterTemplate?: PlaygroundTemplate;
|
||||
};
|
||||
|
||||
export type PlaygroundTemplate = Required<Pick<NavigationItem, 'path' | 'label'>>;
|
||||
|
||||
// Note: only the fields being used are defined in this type
|
||||
export interface PackageJson {
|
||||
dependencies: Record<string, string>;
|
||||
devDependencies: Record<string, string>;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*!
|
||||
* @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 {glob} from 'glob';
|
||||
import {TUTORIALS_CONTENT_NODE_PATH} from './utils/node-constants';
|
||||
import {dirname} from 'path';
|
||||
import {execSync} from 'child_process';
|
||||
|
||||
main();
|
||||
|
||||
/**
|
||||
* This script updates the package-lock.json files for all tutorials
|
||||
* after manually changing the dependencies in the package.json files
|
||||
*/
|
||||
async function main() {
|
||||
const tutorialsPackageJsons = await glob('**/package.json', {
|
||||
ignore: ['**/node_modules'],
|
||||
absolute: true,
|
||||
cwd: TUTORIALS_CONTENT_NODE_PATH,
|
||||
});
|
||||
|
||||
for (const path of tutorialsPackageJsons) {
|
||||
const directory = dirname(path);
|
||||
|
||||
console.info(`\nUpdating lock file at ${directory}\n`);
|
||||
execSync('npm install --package-lock-only', {
|
||||
cwd: directory,
|
||||
stdio: [null, null, 'inherit'],
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*!
|
||||
* @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 {TUTORIALS_DIRECTORY_REGEX} from '../tutorial-type/intro-and-steps';
|
||||
|
||||
export function validatePathAnswer(
|
||||
answer: string,
|
||||
existingPaths: string[],
|
||||
placeholder: string,
|
||||
): string | true {
|
||||
if (answer === placeholder) {
|
||||
return 'Please define the path';
|
||||
}
|
||||
|
||||
if (!TUTORIALS_DIRECTORY_REGEX.test(answer)) {
|
||||
return `The path must be in the format of "${placeholder}"`;
|
||||
}
|
||||
|
||||
if (existingPaths.includes(answer)) {
|
||||
return `'${answer}' already exists`;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename, dirname} from 'path';
|
||||
import {mkdir, readFile, writeFile} from 'fs/promises';
|
||||
import {GlobOptionsWithFileTypesFalse, glob} from 'glob';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
import type {FileAndContent, FileAndContentRecord, PackageJson} from '../tutorials-types';
|
||||
|
||||
const fileContentsCache = new Map<string, string | Buffer>();
|
||||
|
||||
export async function getFileContents(path: string): Promise<string | Buffer> {
|
||||
if (fileContentsCache.get(path)) {
|
||||
return fileContentsCache.get(path)!;
|
||||
}
|
||||
|
||||
const fileBuffer = await readFile(path);
|
||||
|
||||
if (isBufferImage(fileBuffer)) {
|
||||
fileContentsCache.set(path, fileBuffer);
|
||||
return fileBuffer;
|
||||
}
|
||||
|
||||
fileContentsCache.set(path, fileBuffer.toString());
|
||||
return fileBuffer.toString();
|
||||
}
|
||||
|
||||
export async function getFilesContents(files: string[]): Promise<FileAndContent[]> {
|
||||
const filesContents: FileAndContent[] = [];
|
||||
|
||||
await Promise.all(
|
||||
files.map((path) =>
|
||||
getFileContents(path).then((fileContents) => {
|
||||
filesContents.push({path, content: fileContents});
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
return filesContents;
|
||||
}
|
||||
|
||||
export async function createDirectoryAndWriteFile(path: string, fileContents: string | Buffer) {
|
||||
await mkdir(dirname(path), {recursive: true}).catch(() => {
|
||||
// ignore error if directory already exists
|
||||
});
|
||||
|
||||
await writeFile(path, fileContents);
|
||||
}
|
||||
|
||||
export function filesAndContentsToRecord(filesAndContents: FileAndContent[]): FileAndContentRecord {
|
||||
return Object.fromEntries(filesAndContents.map(({path, content}) => [path, content]));
|
||||
}
|
||||
|
||||
export function recordToFilesAndContents(filesRecord: FileAndContentRecord): FileAndContent[] {
|
||||
return Object.entries(filesRecord).map(([path, content]) => ({path, content}));
|
||||
}
|
||||
|
||||
export async function getZipBuffer(files: FileAndContent[]): Promise<Buffer> {
|
||||
const zip = new JSZip();
|
||||
|
||||
for (const file of files) {
|
||||
zip.file(file.path, file.content, {binary: true});
|
||||
}
|
||||
|
||||
return await zip.generateAsync({type: 'nodebuffer'});
|
||||
}
|
||||
|
||||
export async function globWithCwdPath(
|
||||
pattern: string | string[],
|
||||
optionsWithCwd: Omit<GlobOptionsWithFileTypesFalse, 'cwd'> & {cwd: string},
|
||||
) {
|
||||
const files = await glob(pattern, optionsWithCwd);
|
||||
|
||||
return getFilesWithCwdPath(optionsWithCwd.cwd, files).sort();
|
||||
}
|
||||
|
||||
export function getFilesWithCwdPath(cwdPath: string, files: string[]) {
|
||||
return files.map((file) => file.substring(file.indexOf(cwdPath)));
|
||||
}
|
||||
|
||||
function isBufferImage(buffer: Buffer) {
|
||||
return isBufferPng(buffer) || isBufferJpg(buffer) || isBufferGif(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a PNG file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferPng(buffer: Buffer) {
|
||||
const pngSignature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, pngSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a JPG file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferJpg(buffer: Buffer) {
|
||||
const jpgSignature = [0xff, 0xd8, 0xff];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, jpgSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check initial bytes of the buffer(aka magic numbers) to see if it's a GIF file.
|
||||
*
|
||||
* See https://en.wikipedia.org/wiki/Magic_number_(programming)#Magic_numbers_in_files
|
||||
*/
|
||||
function isBufferGif(buffer: Buffer) {
|
||||
const gifSignature = [0x47, 0x49, 0x46];
|
||||
|
||||
return bufferMatchInitialBytes(buffer, gifSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the initial bytes of a buffer matches the expected bytes.
|
||||
*/
|
||||
function bufferMatchInitialBytes(buffer: Buffer, expectedInitialBytes: number[]) {
|
||||
for (const [index, byte] of expectedInitialBytes.entries()) {
|
||||
if (buffer[index] !== byte) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function getPackageJsonFromFiles(filesAndContents: FileAndContent[]): PackageJson {
|
||||
const packageJson = filesAndContents.find(({path}) => basename(path) === 'package.json');
|
||||
|
||||
if (!packageJson)
|
||||
throw new Error(`Could not find ${dirname(filesAndContents[0].path)}/package.json`);
|
||||
|
||||
let parsedPackageJsonContent: PackageJson;
|
||||
|
||||
try {
|
||||
parsedPackageJsonContent = JSON.parse(packageJson.content as string);
|
||||
} catch (e) {
|
||||
throw new Error(`Could not JSON.parse ${dirname(filesAndContents[0].path)}/package.json`);
|
||||
}
|
||||
|
||||
return parsedPackageJsonContent;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename, dirname} from 'path';
|
||||
import {PackageJson} from '../tutorials-types';
|
||||
import {shouldUseFileInWebContainer} from './webcontainers';
|
||||
|
||||
export function validatePackageJson(
|
||||
files: string[],
|
||||
projectPackageJson: PackageJson,
|
||||
commonPackageJson: PackageJson,
|
||||
): void {
|
||||
if (!files.some((file) => basename(file) === 'package-lock.json')) {
|
||||
throw new Error(
|
||||
`Tutorial at ${dirname(
|
||||
files[0],
|
||||
)} has a package.json but is missing a package-lock.json. A lock file is required for faster installs.`,
|
||||
);
|
||||
}
|
||||
|
||||
const commonDependencies = {
|
||||
...commonPackageJson.dependencies,
|
||||
...commonPackageJson.devDependencies,
|
||||
};
|
||||
|
||||
const projectDependencies = {
|
||||
...projectPackageJson.dependencies,
|
||||
...projectPackageJson.devDependencies,
|
||||
};
|
||||
|
||||
const projectDependenciesNames = Object.keys(projectDependencies);
|
||||
|
||||
for (const dependency of Object.keys(commonDependencies)) {
|
||||
if (!projectDependenciesNames.includes(dependency)) {
|
||||
throw new Error(
|
||||
`Tutorial at ${dirname(files[0])} is missing '${dependency}' as a dependency.`,
|
||||
);
|
||||
} else {
|
||||
const commonDependencyVersion = commonDependencies[dependency];
|
||||
const projectDependencyVersion = projectDependencies[dependency];
|
||||
|
||||
if (commonDependencyVersion !== projectDependencyVersion) {
|
||||
throw new Error(
|
||||
`${dirname(
|
||||
files[0],
|
||||
)} has a different version of '${dependency}' than the common package.json.
|
||||
The common version is ${commonDependencyVersion}.
|
||||
The project version is ${projectDependencyVersion}.
|
||||
|
||||
Please update the project package.json to match the common package.json, or update both.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getAllFiles(
|
||||
projectFiles: string[],
|
||||
commonFiles: string[],
|
||||
getCleanFilePath: (file: string) => string,
|
||||
) {
|
||||
const commonAndProjectPaths = [...commonFiles, ...projectFiles]
|
||||
.map((file) => getCleanFilePath(file))
|
||||
.filter(shouldUseFileInWebContainer);
|
||||
|
||||
const uniquePaths = Array.from(new Set(commonAndProjectPaths));
|
||||
|
||||
return uniquePaths;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*!
|
||||
* @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 {join} from 'path';
|
||||
import type {TutorialConfigBase} from '../tutorials-types';
|
||||
import {
|
||||
TUTORIALS_ASSETS_METADATA_DIRECTORY,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
} from './web-constants';
|
||||
import {GlobOptionsWithFileTypesFalse} from 'glob';
|
||||
|
||||
export const CONTENT_PATH = join('projects', 'angular-dev', 'src', 'content');
|
||||
export const TUTORIALS_PROJECT_PATH = join(CONTENT_PATH, 'tutorials');
|
||||
export const TUTORIALS_ASSETS_NODE_PATH = join(
|
||||
'projects',
|
||||
'angular-dev',
|
||||
'src',
|
||||
'assets',
|
||||
'tutorials',
|
||||
);
|
||||
|
||||
export const TUTORIALS_PLAYGROUND_NODE_PATH = join(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_PLAYGROUND_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_HOMEPAGE_NODE_PATH = join(
|
||||
TUTORIALS_PROJECT_PATH,
|
||||
TUTORIALS_HOMEPAGE_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_COMMON_NODE_PATH = join(TUTORIALS_PROJECT_PATH, TUTORIALS_COMMON_DIRECTORY);
|
||||
|
||||
/** The common location where the tutorial assets are stored. */
|
||||
export const TUTORIALS_COMMON_ASSETS_SRC = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
|
||||
|
||||
/** The common location for the tutorial assets to be served from. */
|
||||
export const TUTORIALS_COMMON_ASSETS_DEST = join(TUTORIALS_ASSETS_NODE_PATH, 'common');
|
||||
|
||||
export const TUTORIALS_SOURCE_CODE_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_METADATA_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_METADATA_DIRECTORY,
|
||||
);
|
||||
export const TUTORIALS_CONTENT_NODE_PATH = join(
|
||||
'projects',
|
||||
'angular-dev',
|
||||
'src',
|
||||
'content',
|
||||
'tutorials',
|
||||
);
|
||||
export const TUTORIALS_ROUTES_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
'tutorials.json',
|
||||
);
|
||||
export const PLAYGROUND_ROUTE_NODE_PATH = join(
|
||||
TUTORIALS_ASSETS_NODE_PATH,
|
||||
TUTORIALS_ASSETS_ROUTES_DIRECTORY,
|
||||
'playground.json',
|
||||
);
|
||||
export const TUTORIALS_COMMON_ASSETS_PATH = join(TUTORIALS_COMMON_NODE_PATH, 'src', 'assets');
|
||||
|
||||
export const IGNORED_BASENAMES = [
|
||||
'.DS_Store',
|
||||
'LICENSE',
|
||||
'.gitignore',
|
||||
'.editorconfig',
|
||||
'.gitkeep',
|
||||
'favicon.ico',
|
||||
];
|
||||
export const IGNORED_EXTENSIONS = ['.map'];
|
||||
export const IGNORED_DIRECTORIES = ['.git'];
|
||||
|
||||
export const TUTORIAL_CONTENT_FILENAME = 'README.md';
|
||||
export const ANSWER_DIRECTORY = 'answer';
|
||||
export const INTRO_DIRECTORY = 'intro';
|
||||
export const STEPS_DIRECTORY = 'steps';
|
||||
export const CONFIG_FILE = 'config.json';
|
||||
export const CONFIG_KEYS: Array<keyof TutorialConfigBase> = [
|
||||
'title',
|
||||
'type',
|
||||
'nextTutorial',
|
||||
'src',
|
||||
'answerSrc',
|
||||
'openFiles',
|
||||
];
|
||||
export const REQUIRED_CONFIGS: Array<keyof Pick<TutorialConfigBase, 'title' | 'type'>> = [
|
||||
'title',
|
||||
'type',
|
||||
];
|
||||
|
||||
export const CONTENT_PLACEHOLDER = 'Insert content here';
|
||||
|
||||
export const GLOB_OPTIONS: GlobOptionsWithFileTypesFalse = {
|
||||
dot: false,
|
||||
absolute: true,
|
||||
posix: true,
|
||||
ignore: [
|
||||
'.',
|
||||
'**/node_modules/**',
|
||||
...IGNORED_BASENAMES.map((ignoredBasename) => `**/${ignoredBasename}`),
|
||||
...IGNORED_EXTENSIONS.map((ext) => `**/*${ext}`),
|
||||
...IGNORED_DIRECTORIES.map((dir) => `**/${dir}/**`),
|
||||
],
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
/*!
|
||||
* @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
|
||||
*/
|
||||
export const TUTORIALS_ASSETS_WEB_PATH = '/assets/tutorials';
|
||||
export const TUTORIALS_DIST_PATH = 'tutorials';
|
||||
|
||||
export const TUTORIALS_PLAYGROUND_DIRECTORY = 'playground';
|
||||
|
||||
/** the playground template that loads by default when entering the /playground page */
|
||||
export const DEFAULT_PLAYGROUND_TEMPLATE = '0-minigame';
|
||||
export const STARTER_PLAYGROUND_TEMPLATE = '3-hello-world';
|
||||
|
||||
export const TUTORIALS_HOMEPAGE_DIRECTORY = 'homepage';
|
||||
export const TUTORIALS_COMMON_DIRECTORY = 'common';
|
||||
export const TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY = 'source-code';
|
||||
export const TUTORIALS_ASSETS_METADATA_DIRECTORY = 'metadata';
|
||||
export const TUTORIALS_ASSETS_ROUTES_DIRECTORY = 'routes';
|
||||
|
||||
export const TUTORIALS_SOURCE_CODE_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_SOURCE_CODE_DIRECTORY}`;
|
||||
export const TUTORIALS_METADATA_WEB_PATH = `${TUTORIALS_ASSETS_WEB_PATH}/${TUTORIALS_ASSETS_METADATA_DIRECTORY}`;
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
/*!
|
||||
* @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 {basename, dirname, extname} from 'path';
|
||||
import {DirectoryNode, FileNode, FileSystemTree} from '@webcontainer/api';
|
||||
import {ANSWER_DIRECTORY, CONFIG_FILE} from './node-constants';
|
||||
import {FileAndContent, FileAndContentRecord} from '../tutorials-types';
|
||||
|
||||
/**
|
||||
* Create a WebContainer's FileSystemTree from a list of files and its contents
|
||||
*/
|
||||
export function getFileSystemTree(files: string[], filesContents: FileAndContentRecord) {
|
||||
const fileSystemTree: FileSystemTree = {};
|
||||
|
||||
for (let filepath of files) {
|
||||
const dir = dirname(filepath);
|
||||
const filename = basename(filepath);
|
||||
|
||||
if (dir === '.') {
|
||||
const fileNode: FileNode = {file: {contents: filesContents[filepath]}};
|
||||
|
||||
fileSystemTree[filename] = fileNode;
|
||||
} else {
|
||||
const dirParts = dir.split('/');
|
||||
|
||||
buildFileSystemTree(fileSystemTree, dirParts, filename, filesContents[filepath]);
|
||||
}
|
||||
}
|
||||
|
||||
return fileSystemTree;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a WebContainer's file system tree object recursively, mutating the
|
||||
* `fileSystemTree` parameter.
|
||||
*
|
||||
* @see https://webcontainers.io/api#filesystemtree
|
||||
*/
|
||||
export function buildFileSystemTree(
|
||||
fileSystemTree: FileSystemTree,
|
||||
fileDirectories: string[],
|
||||
filename: FileAndContent['path'],
|
||||
fileContents: FileAndContent['content'],
|
||||
): void {
|
||||
if (fileDirectories.length === 1) {
|
||||
const directory = fileDirectories[0];
|
||||
|
||||
const fileNode: FileNode = {file: {contents: fileContents}};
|
||||
|
||||
fileSystemTree[directory] = {
|
||||
...fileSystemTree[directory],
|
||||
|
||||
directory: {
|
||||
...(fileSystemTree[directory]
|
||||
? (fileSystemTree[directory] as DirectoryNode).directory
|
||||
: undefined),
|
||||
|
||||
[filename]: fileNode,
|
||||
},
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const nextDirectory = fileDirectories.shift();
|
||||
if (!nextDirectory) return;
|
||||
|
||||
if (!fileSystemTree[nextDirectory]) {
|
||||
fileSystemTree[nextDirectory] = {directory: {}};
|
||||
}
|
||||
|
||||
buildFileSystemTree(
|
||||
(fileSystemTree[nextDirectory] as DirectoryNode).directory,
|
||||
fileDirectories,
|
||||
filename,
|
||||
fileContents,
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldUseFileInWebContainer(filename: string) {
|
||||
return (
|
||||
basename(filename) !== CONFIG_FILE &&
|
||||
['.md', '.png', '.jpg'].includes(extname(filename)) === false &&
|
||||
!filename.includes(`${ANSWER_DIRECTORY}/`)
|
||||
);
|
||||
}
|
||||
@@ -13,8 +13,7 @@ import {BehaviorSubject} from 'rxjs';
|
||||
import {TutorialMetadata} from '@angular/docs';
|
||||
import {
|
||||
TUTORIALS_COMMON_DIRECTORY,
|
||||
TUTORIALS_METADATA_WEB_PATH,
|
||||
TUTORIALS_SOURCE_CODE_WEB_PATH,
|
||||
TUTORIALS_ASSETS_WEB_PATH
|
||||
} from './constants';
|
||||
|
||||
/**
|
||||
@@ -133,15 +132,15 @@ export class EmbeddedTutorialManager {
|
||||
async fetchCommonFiles(): Promise<FileSystemTree> {
|
||||
if (this.commonFilesystemTree() !== null) return this.commonFilesystemTree() as FileSystemTree;
|
||||
|
||||
const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
|
||||
//const commonFiles = await this.fetchTutorialSourceCode(TUTORIALS_COMMON_DIRECTORY);
|
||||
|
||||
this.tutorialFilesystemTree.set(commonFiles);
|
||||
//this.tutorialFilesystemTree.set(commonFiles);
|
||||
|
||||
return commonFiles;
|
||||
return {};
|
||||
}
|
||||
|
||||
private async fetchTutorialSourceCode(tutorial: string): Promise<FileSystemTree> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_SOURCE_CODE_WEB_PATH}/${tutorial}.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/source-code.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing source code for tutorial ${tutorial}`);
|
||||
|
||||
@@ -149,7 +148,7 @@ export class EmbeddedTutorialManager {
|
||||
}
|
||||
|
||||
private async fetchTutorialMetadata(tutorial: string): Promise<TutorialMetadata> {
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_METADATA_WEB_PATH}/${tutorial}.json`);
|
||||
const tutorialSourceCode = await fetch(`${TUTORIALS_ASSETS_WEB_PATH}/${tutorial}/metadata.json`);
|
||||
|
||||
if (!tutorialSourceCode.ok) throw new Error(`Missing metadata for ${tutorial}`);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ import {ClickOutside, NavigationList} from '@angular/docs';
|
||||
import {injectAsync} from '../../core/services/inject-async';
|
||||
import {EmbeddedTutorialManager} from '../../editor/index';
|
||||
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/routes/playground.json';
|
||||
import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playground/routes.json';
|
||||
import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu';
|
||||
|
||||
@Component({
|
||||
|
||||
@@ -214,14 +214,14 @@ export default class Tutorial implements AfterViewInit {
|
||||
this.shouldRenderContent.set(routeData.type !== TutorialType.EDITOR_ONLY);
|
||||
|
||||
this.nextStepPath = routeData.nextStep
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.nextStep}`
|
||||
? `/${routeData.nextStep}`
|
||||
: undefined;
|
||||
this.previousStepPath = routeData.previousStep
|
||||
? `/${PagePrefix.TUTORIALS}/${routeData.previousStep}`
|
||||
? `/${routeData.previousStep}`
|
||||
: undefined;
|
||||
|
||||
this.nextTutorialPath.set(
|
||||
routeData.nextTutorial ? `/${PagePrefix.TUTORIALS}/${routeData.nextTutorial}` : null,
|
||||
routeData.nextTutorial ? `/${routeData.nextTutorial}` : null,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
|
||||
import {NavigationItem} from '@angular/docs';
|
||||
|
||||
import TUTORIALS_SUB_NAVIGATION_DATA_JSON from '../../src/assets/tutorials/routes/tutorials.json';
|
||||
import FIRST_APP_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/first-app/routes.json';
|
||||
import LEARN_ANGULAR_TUTORIAL_NAV_DATA from '../../src/assets/tutorials/learn-angular/routes.json';
|
||||
|
||||
import {DefaultPage} from './core/enums/pages';
|
||||
import {getApiNavigationItems} from './features/references/helpers/manifest.helper';
|
||||
@@ -937,7 +938,8 @@ const DOCS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
];
|
||||
|
||||
export const TUTORIALS_SUB_NAVIGATION_DATA: NavigationItem[] = [
|
||||
...TUTORIALS_SUB_NAVIGATION_DATA_JSON,
|
||||
FIRST_APP_TUTORIAL_NAV_DATA,
|
||||
LEARN_ANGULAR_TUTORIAL_NAV_DATA,
|
||||
{
|
||||
path: DefaultPage.TUTORIALS,
|
||||
contentPath: 'tutorials/home',
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
load("@aspect_bazel_lib//lib:copy_to_directory.bzl", "copy_to_directory")
|
||||
|
||||
package(default_visibility = ["//adev:__subpackages__"])
|
||||
|
||||
copy_to_directory(
|
||||
name = "content",
|
||||
srcs = [
|
||||
"//adev/src/content/best-practices",
|
||||
"//adev/src/content/best-practices/runtime-performance",
|
||||
"//adev/src/content/ecosystem",
|
||||
"//adev/src/content/ecosystem/service-workers",
|
||||
"//adev/src/content/guide",
|
||||
"//adev/src/content/guide/animations",
|
||||
"//adev/src/content/guide/components",
|
||||
"//adev/src/content/guide/di",
|
||||
"//adev/src/content/guide/directives",
|
||||
"//adev/src/content/guide/forms",
|
||||
"//adev/src/content/guide/http",
|
||||
"//adev/src/content/guide/i18n",
|
||||
"//adev/src/content/guide/ngmodules",
|
||||
"//adev/src/content/guide/pipes",
|
||||
"//adev/src/content/guide/routing",
|
||||
"//adev/src/content/guide/signals",
|
||||
"//adev/src/content/guide/templates",
|
||||
"//adev/src/content/guide/testing",
|
||||
"//adev/src/content/introduction",
|
||||
"//adev/src/content/introduction/essentials",
|
||||
"//adev/src/content/reference",
|
||||
"//adev/src/content/reference/configs",
|
||||
"//adev/src/content/reference/errors",
|
||||
"//adev/src/content/reference/extended-diagnostics",
|
||||
"//adev/src/content/reference/migrations",
|
||||
"//adev/src/content/tools",
|
||||
"//adev/src/content/tools/cli",
|
||||
"//adev/src/content/tools/libraries",
|
||||
"//adev/src/content/tutorials",
|
||||
"//adev/src/content/tutorials/first-app:first-app-guides",
|
||||
"//adev/src/content/tutorials/learn-angular:learn-angular-guides",
|
||||
"//packages/animations:animations_docs",
|
||||
"//packages/animations/browser:animations_browser_docs",
|
||||
"//packages/animations/browser/testing:animations_browser_testing_docs",
|
||||
"//packages/common:common_docs",
|
||||
"//packages/common/http:http_docs",
|
||||
"//packages/common/http/testing:http_testing_docs",
|
||||
"//packages/common/testing:common_testing_docs",
|
||||
"//packages/common/upgrade:common_upgrade_docs",
|
||||
"//packages/core:core_docs",
|
||||
"//packages/core/rxjs-interop:core_rxjs-interop_docs",
|
||||
"//packages/core/testing:core_testing_docs",
|
||||
"//packages/elements:elements_docs",
|
||||
"//packages/forms:forms_docs",
|
||||
"//packages/localize:localize_docs",
|
||||
"//packages/platform-browser:platform-browser_docs",
|
||||
"//packages/platform-browser-dynamic:platform-browser_dynamic_docs",
|
||||
"//packages/platform-browser-dynamic/testing:platform-browser_dynamic_testing_docs",
|
||||
"//packages/platform-browser/animations:platform-browser_animations_docs",
|
||||
"//packages/platform-browser/animations/async:platform-browser_animations_async_docs",
|
||||
"//packages/platform-browser/testing:platform-browser_testing_docs",
|
||||
"//packages/platform-server:platform-server_docs",
|
||||
"//packages/platform-server/testing:platform-server_testing_docs",
|
||||
"//packages/router:router_docs",
|
||||
"//packages/router/testing:router_testing_docs",
|
||||
"//packages/router/upgrade:router_upgrade_docs",
|
||||
"//packages/service-worker:service-worker_docs",
|
||||
"//packages/upgrade:upgrade_docs",
|
||||
"//packages/upgrade/static:upgrade_static_docs",
|
||||
"//packages/upgrade/static/testing:upgrade_static_testing_docs",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/src/content": "",
|
||||
"packages/**/": "api/",
|
||||
},
|
||||
)
|
||||
|
||||
copy_to_directory(
|
||||
name = "tutorials",
|
||||
srcs = [
|
||||
"//adev/src/content/tutorials/first-app",
|
||||
"//adev/src/content/tutorials/learn-angular",
|
||||
"//adev/src/content/tutorials/playground",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"adev/**/tutorials/*/": "",
|
||||
},
|
||||
)
|
||||
|
||||
copy_to_directory(
|
||||
name = "api",
|
||||
srcs = [
|
||||
"//packages:docs_api_manifest",
|
||||
],
|
||||
replace_prefixes = {
|
||||
"**/": "",
|
||||
},
|
||||
)
|
||||
@@ -1,736 +0,0 @@
|
||||
{
|
||||
"@angular/animations": [
|
||||
{"name": "AnimationEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationPlayer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimateTimings", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "AnimationBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AnimationOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimateChildOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationMetadataType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "AnimationFactory", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NoopAnimationPlayer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AUTO_STYLE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "AnimationMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationTriggerMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStateMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationTransitionMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationReferenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationQueryMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationKeyframesSequenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStyleMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateChildMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationAnimateRefMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationSequenceMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationGroupMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationQueryOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AnimationStaggerMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "trigger", "type": "function", "isDeprecated": false},
|
||||
{"name": "animate", "type": "function", "isDeprecated": false},
|
||||
{"name": "group", "type": "function", "isDeprecated": false},
|
||||
{"name": "sequence", "type": "function", "isDeprecated": false},
|
||||
{"name": "style", "type": "function", "isDeprecated": false},
|
||||
{"name": "state", "type": "function", "isDeprecated": false},
|
||||
{"name": "keyframes", "type": "function", "isDeprecated": false},
|
||||
{"name": "transition", "type": "function", "isDeprecated": false},
|
||||
{"name": "animation", "type": "function", "isDeprecated": false},
|
||||
{"name": "animateChild", "type": "function", "isDeprecated": false},
|
||||
{"name": "useAnimation", "type": "function", "isDeprecated": false},
|
||||
{"name": "query", "type": "function", "isDeprecated": false},
|
||||
{"name": "stagger", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/animations/browser": [
|
||||
{"name": "NoopAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AnimationDriver", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/animations/browser/testing": [
|
||||
{"name": "MockAnimationDriver", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MockAnimationPlayer", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common": [
|
||||
{"name": "DatePipeConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "XhrFactory", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "registerLocaleData", "type": "function", "isDeprecated": false},
|
||||
{"name": "JsonPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "ViewportScroller", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgIf", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "I18nSelectPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "LowerCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "SlicePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "NgLocalization", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CommonModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "NgTemplateOutlet", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PlatformLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgStyle", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgComponentOutlet", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isPlatformBrowser", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgPlural", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NumberFormatStyle", "type": "enum", "isDeprecated": false},
|
||||
{"name": "ImageLoaderConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "KeyValue", "type": "interface", "isDeprecated": false},
|
||||
{"name": "I18nPluralPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "PopStateEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HashLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForOfContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DOCUMENT", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DecimalPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "Location", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isPlatformServer", "type": "function", "isDeprecated": false},
|
||||
{"name": "KeyValuePipe", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Plural", "type": "enum", "isDeprecated": false},
|
||||
{"name": "provideCloudflareLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DATE_PIPE_DEFAULT_TIMEZONE", "type": "constant", "isDeprecated": true},
|
||||
{"name": "isPlatformWorkerApp", "type": "function", "isDeprecated": true},
|
||||
{"name": "ImageLoader", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgFor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForOf", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideImgixLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormStyle", "type": "enum", "isDeprecated": false},
|
||||
{"name": "provideImageKitLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgSwitch", "type": "directive", "isDeprecated": false},
|
||||
{"name": "provideCloudinaryLoader", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isPlatformWorkerUi", "type": "function", "isDeprecated": true},
|
||||
{"name": "NgLocaleLocalization", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PRECONNECT_CHECK_BLOCKLIST", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgClass", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "formatDate", "type": "function", "isDeprecated": false},
|
||||
{"name": "TranslationWidth", "type": "enum", "isDeprecated": false},
|
||||
{"name": "IMAGE_LOADER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormatWidth", "type": "enum", "isDeprecated": false},
|
||||
{"name": "DATE_PIPE_DEFAULT_OPTIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DatePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "AsyncPipe", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "APP_BASE_HREF", "type": "constant", "isDeprecated": false},
|
||||
{"name": "PathLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgPluralCase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LOCATION_INITIALIZED", "type": "constant", "isDeprecated": false},
|
||||
{"name": "LocationChangeEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NumberSymbol", "type": "enum", "isDeprecated": false},
|
||||
{"name": "LocationChangeListener", "type": "interface", "isDeprecated": false},
|
||||
{"name": "BrowserPlatformLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PercentPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "NgOptimizedImage", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "formatCurrency", "type": "function", "isDeprecated": false},
|
||||
{"name": "WeekDay", "type": "enum", "isDeprecated": false},
|
||||
{"name": "getLocaleId", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDayPeriods", "type": "function", "isDeprecated": false},
|
||||
{"name": "CurrencyPipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "formatPercent", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgSwitchCase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleDayNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleMonthNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "formatNumber", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleEraNames", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgSwitchDefault", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleFirstDayOfWeek", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgIfContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "getLocaleWeekEndRange", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDateFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleTimeFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "TitleCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "getLocaleDateTimeFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleNumberSymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "UpperCasePipe", "type": "pipe", "isDeprecated": false},
|
||||
{"name": "getLocaleNumberFormat", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencySymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencyName", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleCurrencyCode", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocalePluralCase", "type": "constant", "isDeprecated": false},
|
||||
{"name": "getLocaleExtraDayPeriodRules", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleExtraDayPeriods", "type": "function", "isDeprecated": false},
|
||||
{"name": "getLocaleDirection", "type": "function", "isDeprecated": false},
|
||||
{"name": "Time", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "getCurrencySymbol", "type": "function", "isDeprecated": false},
|
||||
{"name": "getNumberOfCurrencyDigits", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/http": [
|
||||
{"name": "HttpContextToken", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpParameterCodec", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpEventType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HttpHeaders", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpHandler", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpUrlEncodingCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpClientXsrfModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpInterceptor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpTransferCacheOptions", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpFeatureKind", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HttpProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "FetchBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpXsrfTokenExtractor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpXhrBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpDownloadProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "provideHttpClient", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpUploadProgressEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpClient", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpSentEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpRequest", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpClientModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpUserEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "JsonpClientBackend", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpHandlerFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpEvent", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "HttpClientJsonpModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "HttpResponseBase", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HttpParamsOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HttpInterceptorFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withInterceptors", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpParams", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withInterceptorsFromDi", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpHeaderResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withXsrfConfiguration", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withNoXsrfProtection", "type": "function", "isDeprecated": false},
|
||||
{"name": "withJsonpSupport", "type": "function", "isDeprecated": false},
|
||||
{"name": "withRequestsMadeViaParent", "type": "function", "isDeprecated": false},
|
||||
{"name": "HTTP_INTERCEPTORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "HttpErrorResponse", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withFetch", "type": "function", "isDeprecated": false},
|
||||
{"name": "HttpStatusCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "JsonpInterceptor", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/testing": [
|
||||
{"name": "MockLocationStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SpyLocation", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideLocationMocks", "type": "function", "isDeprecated": false},
|
||||
{"name": "MockPlatformLocationConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "MOCK_PLATFORM_LOCATION_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MockPlatformLocation", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/common/upgrade": [
|
||||
{"name": "UrlCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LocationUpgradeConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "$locationShim", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "LOCATION_UPGRADE_CONFIGURATION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "LocationUpgradeModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AngularJSUrlCodec", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "$locationShimProvider", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core": [
|
||||
{"name": "SecurityContext", "type": "enum", "isDeprecated": false},
|
||||
{"name": "SchemaMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewEncapsulation", "type": "enum", "isDeprecated": false},
|
||||
{"name": "Version", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ChangeDetectionStrategy", "type": "enum", "isDeprecated": false},
|
||||
{"name": "PipeTransform", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ErrorHandler", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DoBootstrap", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RendererType2", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ApplicationConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Signal", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "untracked", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ApplicationModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "Sanitizer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ProviderToken", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CreateComputedOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "assertNotInReactiveContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectionToken", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ForwardRefFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "WritableSignal", "type": "interface", "isDeprecated": false},
|
||||
{"name": "platformCore", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ENVIRONMENT_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgModuleRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueDiffer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DestroyRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "StateKey", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "getModuleFactory", "type": "function", "isDeprecated": true},
|
||||
{"name": "RendererFactory2", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgIterable", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ComponentRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "QueryList", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "computed", "type": "function", "isDeprecated": false},
|
||||
{"name": "createComponent", "type": "function", "isDeprecated": false},
|
||||
{"name": "mergeApplicationConfig", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectableProvider", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "isSignal", "type": "function", "isDeprecated": false},
|
||||
{"name": "INJECTOR", "type": "constant", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "runInInjectionContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "IterableDiffer", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ChangeDetectorRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Injector", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderPhase", "type": "enum", "isDeprecated": false},
|
||||
{"name": "forwardRef", "type": "function", "isDeprecated": false},
|
||||
{"name": "EffectCleanupFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "makeStateKey", "type": "function", "isDeprecated": false},
|
||||
{"name": "CUSTOM_ELEMENTS_SCHEMA", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TemplateRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ValueEqualityFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgZone", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ModuleWithComponentFactories", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "ComponentFactoryResolver", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "DefaultIterableDiffer", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "getNgModuleById", "type": "function", "isDeprecated": false},
|
||||
{"name": "createNgModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "ElementRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CreateSignalOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "EmbeddedViewRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DebugEventListener", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IterableChanges", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NO_ERRORS_SCHEMA", "type": "constant", "isDeprecated": false},
|
||||
{"name": "asNativeElements", "type": "function", "isDeprecated": false},
|
||||
{"name": "APP_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "signal", "type": "function", "isDeprecated": false},
|
||||
{"name": "KeyValueChanges", "type": "interface", "isDeprecated": false},
|
||||
{"name": "TransferState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Compiler", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "RendererStyleFlags2", "type": "enum", "isDeprecated": false},
|
||||
{"name": "Renderer2", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "makeEnvironmentProviders", "type": "function", "isDeprecated": false},
|
||||
{"name": "DebugNode", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Attribute", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "Inject", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "PLATFORM_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Query", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ImportProvidersSource", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "PLATFORM_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "createNgModuleRef", "type": "constant", "isDeprecated": true},
|
||||
{"name": "NgModuleFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "assertInInjectionContext", "type": "function", "isDeprecated": false},
|
||||
{"name": "importProvidersFrom", "type": "function", "isDeprecated": false},
|
||||
{"name": "resolveForwardRef", "type": "function", "isDeprecated": false},
|
||||
{"name": "PACKAGE_ROOT_URL", "type": "constant", "isDeprecated": true},
|
||||
{"name": "Testability", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueChangeRecord", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LOCALE_ID", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ComponentFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "ANIMATION_MODULE_TYPE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Optional", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "KeyValueDifferFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewContainerRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "CSP_NONCE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Injectable", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "EnvironmentInjector", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "KeyValueDiffers", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IterableChangeRecord", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DebugElement", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "AfterRenderRef", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ComponentMirror", "type": "interface", "isDeprecated": false},
|
||||
{"name": "DEFAULT_CURRENCY_CODE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "APP_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "CompilerOptions", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "APP_BOOTSTRAP_LISTENER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TrackByFunction", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Self", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "ApplicationInitStatus", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "COMPILER_OPTIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "inject", "type": "function", "isDeprecated": false},
|
||||
{"name": "EventEmitter", "type": "constant", "isDeprecated": false},
|
||||
{"name": "CompilerFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "TRANSLATIONS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "reflectComponentType", "type": "function", "isDeprecated": false},
|
||||
{"name": "SkipSelf", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "TRANSLATIONS_FORMAT", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MissingTranslationStrategy", "type": "enum", "isDeprecated": false},
|
||||
{"name": "afterRender", "type": "function", "isDeprecated": false},
|
||||
{"name": "ContentChildren", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "IterableDifferFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgModule", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "EffectRef", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Host", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "createEnvironmentInjector", "type": "function", "isDeprecated": false},
|
||||
{"name": "IterableDiffers", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "CreateEffectOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgProbeToken", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "createPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "effect", "type": "function", "isDeprecated": false},
|
||||
{"name": "afterNextRender", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestabilityRegistry", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ContentChild", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "GetTestability", "type": "interface", "isDeprecated": false},
|
||||
{"name": "setTestabilityGetter", "type": "function", "isDeprecated": false},
|
||||
{"name": "Directive", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "createPlatformFactory", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewChildren", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "assertPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "destroyPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "getPlatform", "type": "function", "isDeprecated": false},
|
||||
{"name": "NgZoneOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ViewChild", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "isStandalone", "type": "function", "isDeprecated": false},
|
||||
{"name": "BootstrapOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "PlatformRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Component", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "Pipe", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "getDebugNode", "type": "function", "isDeprecated": false},
|
||||
{"name": "Predicate", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Input", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "ApplicationRef", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Output", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "HostBinding", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "HostListener", "type": "decorator", "isDeprecated": false},
|
||||
{"name": "provideZoneChangeDetection", "type": "function", "isDeprecated": false},
|
||||
{"name": "@if", "type": "block", "isDeprecated": false},
|
||||
{"name": "@for", "type": "block", "isDeprecated": false},
|
||||
{"name": "@switch", "type": "block", "isDeprecated": false},
|
||||
{"name": "@defer", "type": "block", "isDeprecated": false},
|
||||
{"name": "ng-content", "type": "element", "isDeprecated": false},
|
||||
{"name": "ng-template", "type": "element", "isDeprecated": false},
|
||||
{"name": "ng-container", "type": "element", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core/rxjs-interop": [
|
||||
{"name": "ToObservableOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "takeUntilDestroyed", "type": "function", "isDeprecated": false},
|
||||
{"name": "ToSignalOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "toObservable", "type": "function", "isDeprecated": false},
|
||||
{"name": "toSignal", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/core/testing": [
|
||||
{"name": "waitForAsync", "type": "function", "isDeprecated": false},
|
||||
{"name": "MetadataOverride", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ComponentFixture", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "resetFakeAsyncZone", "type": "function", "isDeprecated": false},
|
||||
{"name": "DeferBlockFixture", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "TestComponentRenderer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "fakeAsync", "type": "function", "isDeprecated": false},
|
||||
{"name": "ComponentFixtureAutoDetect", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ComponentFixtureNoNgZone", "type": "constant", "isDeprecated": false},
|
||||
{"name": "TestModuleMetadata", "type": "interface", "isDeprecated": false},
|
||||
{"name": "async", "type": "function", "isDeprecated": true},
|
||||
{"name": "tick", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestBedStatic", "type": "interface", "isDeprecated": false},
|
||||
{"name": "TestEnvironmentOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ModuleTeardownOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "flush", "type": "function", "isDeprecated": false},
|
||||
{"name": "discardPeriodicTasks", "type": "function", "isDeprecated": false},
|
||||
{"name": "flushMicrotasks", "type": "function", "isDeprecated": false},
|
||||
{"name": "getTestBed", "type": "function", "isDeprecated": false},
|
||||
{"name": "TestBed", "type": "constant", "isDeprecated": false},
|
||||
{"name": "inject", "type": "function", "isDeprecated": false},
|
||||
{"name": "InjectSetupWrapper", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withModule", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/elements": [
|
||||
{"name": "NgElementStrategyEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgElementStrategy", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElementConstructor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElementStrategyFactory", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgElement", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "WithProperties", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NgElementConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "createCustomElement", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/forms": [
|
||||
{"name": "ControlValueAccessor", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ControlContainer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgControl", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Form", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AbstractFormGroupDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControlDirective", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "FormControlState", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RangeValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormsModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "CheckboxControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NumberValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControlOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NgModelGroup", "type": "directive", "isDeprecated": false},
|
||||
{"name": "SelectControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormGroupName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "COMPOSITION_BUFFER_MODE", "type": "constant", "isDeprecated": false},
|
||||
{"name": "DefaultValueAccessor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgForm", "type": "directive", "isDeprecated": false},
|
||||
{"name": "ValidationErrors", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "FormControlDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormArray", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SelectMultipleControlValueAccessor", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormGroupDirective", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControlName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "Validator", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SetDisabledStateOption", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "FormControlStatus", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ReactiveFormsModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "FormGroup", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ControlConfig", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NG_VALIDATORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "NgModel", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NgControlStatus", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NgControlStatusGroup", "type": "directive", "isDeprecated": false},
|
||||
{"name": "RadioControlValueAccessor", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NG_ASYNC_VALIDATORS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormArrayName", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControlOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "FormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Validators", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NgSelectOption", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MaxValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NG_VALUE_ACCESSOR", "type": "constant", "isDeprecated": false},
|
||||
{"name": "MinValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AsyncValidator", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RequiredValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "CheckboxRequiredValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "AbstractControl", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EmailValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "FormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ValidatorFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "AsyncValidatorFn", "type": "interface", "isDeprecated": false},
|
||||
{"name": "MinLengthValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NonNullableFormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MaxLengthValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "PatternValidator", "type": "directive", "isDeprecated": false},
|
||||
{"name": "UntypedFormBuilder", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UntypedFormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormControl", "type": "constant", "isDeprecated": false},
|
||||
{"name": "UntypedFormArray", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormArray", "type": "constant", "isDeprecated": false},
|
||||
{"name": "UntypedFormGroup", "type": "constant", "isDeprecated": false},
|
||||
{"name": "isFormGroup", "type": "constant", "isDeprecated": false},
|
||||
{"name": "FormRecord", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "isFormRecord", "type": "constant", "isDeprecated": false}
|
||||
],
|
||||
"@angular/localize": [
|
||||
{"name": "loadTranslations", "type": "function", "isDeprecated": false},
|
||||
{"name": "clearTranslations", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser": [
|
||||
{"name": "By", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "MetaDefinition", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "enableDebugTools", "type": "function", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "Title", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EVENT_MANAGER_PLUGINS", "type": "constant", "isDeprecated": false},
|
||||
{"name": "EventManager", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HydrationFeatureKind", "type": "enum", "isDeprecated": false},
|
||||
{"name": "HydrationFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "makeStateKey", "type": "constant", "isDeprecated": true},
|
||||
{"name": "disableDebugTools", "type": "function", "isDeprecated": false},
|
||||
{"name": "SafeValue", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Meta", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "SafeHtml", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SafeStyle", "type": "interface", "isDeprecated": false},
|
||||
{"name": "withNoHttpTransferCache", "type": "function", "isDeprecated": false},
|
||||
{"name": "HAMMER_GESTURE_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "SafeScript", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ApplicationConfig", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "HammerLoader", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "SafeUrl", "type": "interface", "isDeprecated": false},
|
||||
{"name": "REMOVE_STYLES_ON_COMPONENT_DESTROY", "type": "constant", "isDeprecated": false},
|
||||
{"name": "SafeResourceUrl", "type": "interface", "isDeprecated": false},
|
||||
{"name": "HAMMER_LOADER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "withHttpTransferCacheOptions", "type": "function", "isDeprecated": false},
|
||||
{"name": "bootstrapApplication", "type": "function", "isDeprecated": false},
|
||||
{"name": "DomSanitizer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "TransferState", "type": "constant", "isDeprecated": false},
|
||||
{"name": "HammerGestureConfig", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "StateKey", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "EventManagerPlugin", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideClientHydration", "type": "function", "isDeprecated": false},
|
||||
{"name": "createApplication", "type": "function", "isDeprecated": false},
|
||||
{"name": "provideProtractorTestingSupport", "type": "function", "isDeprecated": false},
|
||||
{"name": "platformBrowser", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "HammerModule", "type": "ng_module", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser-dynamic": [
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "JitCompilerFactory", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "RESOURCE_CACHE_PROVIDER", "type": "constant", "isDeprecated": true},
|
||||
{"name": "platformBrowserDynamic", "type": "constant", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser-dynamic/testing": [
|
||||
{"name": "platformBrowserDynamicTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserDynamicTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser/animations": [
|
||||
{"name": "BrowserAnimationsModuleConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "BrowserAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideAnimations", "type": "function", "isDeprecated": false},
|
||||
{"name": "NoopAnimationsModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideNoopAnimations", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-browser/testing": [
|
||||
{"name": "platformBrowserTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BrowserTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-server": [
|
||||
{"name": "PlatformConfig", "type": "interface", "isDeprecated": false},
|
||||
{"name": "PlatformState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "provideServerRendering", "type": "function", "isDeprecated": false},
|
||||
{"name": "ServerTransferStateModule", "type": "ng_module", "isDeprecated": true},
|
||||
{"name": "INITIAL_CONFIG", "type": "constant", "isDeprecated": false},
|
||||
{"name": "BEFORE_APP_SERIALIZED", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ServerModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "platformServer", "type": "constant", "isDeprecated": false},
|
||||
{"name": "platformDynamicServer", "type": "constant", "isDeprecated": true},
|
||||
{"name": "renderModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "renderApplication", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/platform-server/testing": [
|
||||
{"name": "platformServerTesting", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ServerTestingModule", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router": [
|
||||
{"name": "UrlHandlingStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "mapToCanMatch", "type": "function", "isDeprecated": false},
|
||||
{"name": "VERSION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "PRIMARY_OUTLET", "type": "constant", "isDeprecated": false},
|
||||
{"name": "OutletContext", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DetachedRouteHandle", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "TitleStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "IsActiveMatchOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "OnSameUrlNavigation", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "createUrlTreeFromSnapshot", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterState", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadingStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLinkActive", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ChildrenOutletContexts", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLink", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterLinkWithHref", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "EventType", "type": "enum", "isDeprecated": false},
|
||||
{"name": "InitialNavigation", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ViewTransitionsFeatureOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "Params", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "mapToCanActivate", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterOutletContract", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadAllModules", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ParamMap", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ROUTES", "type": "constant", "isDeprecated": false},
|
||||
{"name": "RouterEvent", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "mapToCanActivateChild", "type": "function", "isDeprecated": false},
|
||||
{"name": "NoPreloading", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ViewTransitionInfo", "type": "interface", "isDeprecated": false},
|
||||
{"name": "provideRouter", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterPreloader", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterConfigOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "mapToCanDeactivate", "type": "function", "isDeprecated": false},
|
||||
{"name": "BaseRouteReuseStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DeprecatedGuard", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "NavigationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlCreationOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "mapToResolve", "type": "function", "isDeprecated": false},
|
||||
{"name": "Router", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DefaultTitleStrategy", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Routes", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "convertToParamMap", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "defaultUrlMatcher", "type": "function", "isDeprecated": false},
|
||||
{"name": "UrlMatchResult", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterFeature", "type": "interface", "isDeprecated": false},
|
||||
{"name": "ActivatedRoute", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlMatcher", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterOutlet", "type": "directive", "isDeprecated": false},
|
||||
{"name": "NavigationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideRoutes", "type": "function", "isDeprecated": true},
|
||||
{"name": "Data", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ResolveData", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NavigationCancellationCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "DefaultExport", "type": "interface", "isDeprecated": false},
|
||||
{"name": "InMemoryScrollingOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "InMemoryScrollingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "LoadChildrenCallback", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withInMemoryScrolling", "type": "function", "isDeprecated": false},
|
||||
{"name": "NavigationExtras", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NavigationSkippedCode", "type": "enum", "isDeprecated": false},
|
||||
{"name": "NavigationCancel", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Navigation", "type": "interface", "isDeprecated": false},
|
||||
{"name": "LoadChildren", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "QueryParamsHandling", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UrlTree", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ExtraOptions", "type": "interface", "isDeprecated": false},
|
||||
{"name": "RunGuardsAndResolvers", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "NavigationSkipped", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Route", "type": "interface", "isDeprecated": false},
|
||||
{"name": "NavigationError", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlSegmentGroup", "type": "undecorated_class", "isDeprecated": false},
|
||||
{
|
||||
"name": "EnabledBlockingInitialNavigationFeature",
|
||||
"type": "type_alias",
|
||||
"isDeprecated": false
|
||||
},
|
||||
{"name": "ROUTER_INITIALIZER", "type": "constant", "isDeprecated": false},
|
||||
{"name": "RoutesRecognized", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "InitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UrlSegment", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "GuardsCheckStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withEnabledBlockingInitialNavigation", "type": "function", "isDeprecated": false},
|
||||
{"name": "ROUTER_CONFIGURATION", "type": "constant", "isDeprecated": false},
|
||||
{"name": "ActivatedRouteSnapshot", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "GuardsCheckEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ResolveStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "UrlSerializer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DisabledInitialNavigationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ResolveEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DefaultUrlSerializer", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withDisabledInitialNavigation", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouteConfigLoadStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouteConfigLoadEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "DebugTracingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "ChildActivationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "withDebugTracing", "type": "function", "isDeprecated": false},
|
||||
{"name": "ChildActivationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterStateSnapshot", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ActivationStart", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ActivationEnd", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "PreloadingFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withPreloading", "type": "function", "isDeprecated": false},
|
||||
{"name": "Scroll", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "Event", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "RouterConfigurationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withRouterConfig", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterHashLocationFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withHashLocation", "type": "function", "isDeprecated": false},
|
||||
{"name": "NavigationErrorHandlerFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withNavigationErrorHandler", "type": "function", "isDeprecated": false},
|
||||
{"name": "ViewTransitionsFeature", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "withComponentInputBinding", "type": "function", "isDeprecated": false},
|
||||
{"name": "CanActivate", "type": "interface", "isDeprecated": true},
|
||||
{"name": "withViewTransitions", "type": "function", "isDeprecated": false},
|
||||
{"name": "RouterFeatures", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanActivateFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanActivateChild", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanActivateChildFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanDeactivate", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanDeactivateFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanMatch", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanMatchFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "Resolve", "type": "interface", "isDeprecated": true},
|
||||
{"name": "ResolveFn", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "CanLoad", "type": "interface", "isDeprecated": true},
|
||||
{"name": "CanLoadFn", "type": "type_alias", "isDeprecated": true},
|
||||
{"name": "NavigationBehaviorOptions", "type": "interface", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router/testing": [
|
||||
{"name": "RouterTestingModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "RouterTestingHarness", "type": "undecorated_class", "isDeprecated": false}
|
||||
],
|
||||
"@angular/router/upgrade": [
|
||||
{"name": "RouterUpgradeInitializer", "type": "constant", "isDeprecated": false},
|
||||
{"name": "locationSyncBootstrapListener", "type": "function", "isDeprecated": false},
|
||||
{"name": "setUpLocationSync", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/service-worker": [
|
||||
{"name": "SwUpdate", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "ServiceWorkerModule", "type": "ng_module", "isDeprecated": false},
|
||||
{"name": "SwPush", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "NoNewVersionDetectedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionDetectedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionInstallationFailedEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionReadyEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "VersionEvent", "type": "type_alias", "isDeprecated": false},
|
||||
{"name": "UnrecoverableStateEvent", "type": "interface", "isDeprecated": false},
|
||||
{"name": "SwRegistrationOptions", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "provideServiceWorker", "type": "function", "isDeprecated": false}
|
||||
],
|
||||
"@angular/upgrade": [
|
||||
{"name": "UpgradeAdapter", "type": "undecorated_class", "isDeprecated": true},
|
||||
{"name": "UpgradeAdapterRef", "type": "undecorated_class", "isDeprecated": true}
|
||||
],
|
||||
"@angular/upgrade/static": [
|
||||
{"name": "UpgradeModule", "type": "undecorated_class", "isDeprecated": false},
|
||||
{"name": "downgradeModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "UpgradeComponent", "type": "directive", "isDeprecated": false}
|
||||
],
|
||||
"@angular/upgrade/static/testing": [
|
||||
{"name": "createAngularJSTestingModule", "type": "function", "isDeprecated": false},
|
||||
{"name": "createAngularTestingModule", "type": "function", "isDeprecated": false}
|
||||
]
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
**/*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "icons",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "images",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
# TODO(josephperrott): Generate previews from source instead of using already generated files
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "previews",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
/**
|
||||
******************************************************************************
|
||||
* DO NOT MANUALLY EDIT THIS FILE. THIS FILE IS AUTOMATICALLY GENERATED.
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Map of the previews components, values are functions which returns the promise of the component type, which will be displayed as preview in the ExampleViewer component.
|
||||
* Keys has to be equal to paths written down in the docs markdown files.
|
||||
*/
|
||||
export const PREVIEWS_COMPONENTS_MAP: Record<string, () => Promise<Type<unknown>>> = {
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin")
|
||||
|
||||
exports_files(
|
||||
glob(["*"]),
|
||||
)
|
||||
|
||||
copy_to_bin(
|
||||
name = "textures",
|
||||
srcs = glob(["*"]),
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"currentVersion": "stable",
|
||||
"historicalVersionsLinkPattern": "https://v{{version}}.angular.dev",
|
||||
"mainVersions": [
|
||||
{
|
||||
"version": "stable",
|
||||
"url": "https://angular.dev"
|
||||
},
|
||||
{
|
||||
"version": "rc",
|
||||
"url": "https://rc.angular.dev"
|
||||
},
|
||||
{
|
||||
"version": "next",
|
||||
"url": "https://next.angular.dev"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -72,4 +72,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.avoid.ts",
|
||||
"//adev/src/content/examples/styleguide:src/09-01/app/heroes/shared/hero-button/hero-button.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -11,4 +11,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/service-worker-getting-started:src/app/log-update.service.ts",
|
||||
"//adev/src/content/examples/service-worker-getting-started:src/app/prompt-update.service.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -15,4 +15,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/security:src/app/inner-html-binding.component.ts",
|
||||
"//adev/src/content/examples/ssr:server.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -31,4 +31,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/animations:src/app/open-close.component.ts",
|
||||
"//adev/src/content/examples/animations:src/app/status-slider.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:components.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/assets/images:dependency_injection.svg",
|
||||
"//adev/src/content/examples/resolution-modifiers:src/app/self/self.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -22,4 +22,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/built-in-directives:src/app/item-switch.component.ts",
|
||||
"//adev/src/content/examples/structural-directives:src/app/app.component.html",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -49,4 +49,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.html",
|
||||
"//adev/src/content/examples/reactive-forms:src/app/profile-editor/profile-editor.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -19,4 +19,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/i18n:src/app/app.component.ts",
|
||||
"//adev/src/content/examples/i18n:src/main.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -7,4 +7,5 @@ generate_guides(
|
||||
]),
|
||||
data = [
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -14,4 +14,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/pipes:src/app/hero-async-message.component.ts",
|
||||
"//adev/src/content/examples/pipes:src/app/power-booster.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -18,4 +18,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/routing-with-urlmatcher:src/app/app.routes.ts",
|
||||
"//adev/src/content/examples/routing-with-urlmatcher:src/app/profile/profile.component.html",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:signals.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -27,4 +27,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.html",
|
||||
"//adev/src/content/examples/two-way-binding:src/app/sizer/sizer.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -45,4 +45,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/testing:src/testing/async-observable-helpers.ts",
|
||||
"//adev/src/content/examples/testing:src/testing/index.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -15,4 +15,5 @@ generate_guides(
|
||||
"//adev/src/assets/images:templates.svg",
|
||||
"//adev/src/assets/images:what_is_angular.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/assets/images:roadmap.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/angular-compiler-options:tsconfig.app.json",
|
||||
"//adev/src/content/examples/angular-compiler-options:tsconfig.json",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -9,4 +9,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/errors:cyclic-imports/child.component.ts",
|
||||
"//adev/src/content/examples/errors:cyclic-imports/parent.component.ts",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -5,4 +5,5 @@ generate_guides(
|
||||
srcs = glob([
|
||||
"*.md",
|
||||
]),
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -18,4 +18,5 @@ generate_guides(
|
||||
"//adev/src/content/examples/schematics-for-libraries:projects/my-lib/schematics/ng-add/index.ts",
|
||||
"//adev/src/content/examples/schematics-for-libraries:projects/my-lib/tsconfig.schematics.json",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -8,4 +8,5 @@ generate_guides(
|
||||
data = [
|
||||
"//adev/src/content/examples/angular-linker-plugin:webpack.config.mjs",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
load("@npm//@angular/docs:index.bzl", "generate_guides")
|
||||
|
||||
generate_guides(
|
||||
name = "tutorials",
|
||||
srcs = glob(
|
||||
[
|
||||
"*.md",
|
||||
],
|
||||
exclude = [
|
||||
"README.md",
|
||||
],
|
||||
),
|
||||
data = [
|
||||
"//adev/src/assets/images:learn-angular-browser.svg",
|
||||
"//adev/src/assets/images:learn-angular-local.svg",
|
||||
],
|
||||
visibility = ["//adev:__subpackages__"],
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
/bazel-out
|
||||
|
||||
# Node
|
||||
/node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# Miscellaneous
|
||||
/.angular/cache
|
||||
.sass-cache/
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -1,99 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"first-app": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"inlineTemplate": true,
|
||||
"inlineStyle": true,
|
||||
"style": "scss",
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:class": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:guard": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:interceptor": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:pipe": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:resolver": {
|
||||
"skipTests": true
|
||||
},
|
||||
"@schematics/angular:service": {
|
||||
"skipTests": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/first-app",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": ["src/favicon.ico", "src/assets"],
|
||||
"styles": ["src/styles.css"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "first-app:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "first-app:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"buildTarget": "first-app:build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "angular.dev",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "NG_BUILD_PARALLEL_TS=0 ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/common": "^17.1.0-next",
|
||||
"@angular/compiler": "^17.1.0-next",
|
||||
"@angular/core": "^17.1.0-next",
|
||||
"@angular/platform-browser": "^17.1.0-next",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.14.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^17.1.0-next",
|
||||
"@angular/cli": "^17.1.0-next",
|
||||
"@angular/compiler-cli": "^17.1.0-next",
|
||||
"typescript": "~5.2.0"
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
/*!
|
||||
* @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 {ApplicationConfig} from '@angular/core';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [],
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 223 236"><g clip-path="url(#a)"><path fill="url(#b)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"/><path fill="url(#c)" d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"/></g><defs><linearGradient id="b" x1="49.009" x2="225.829" y1="213.75" y2="129.722" gradientUnits="userSpaceOnUse"><stop stop-color="#E40035"/><stop offset=".24" stop-color="#F60A48"/><stop offset=".352" stop-color="#F20755"/><stop offset=".494" stop-color="#DC087D"/><stop offset=".745" stop-color="#9717E7"/><stop offset="1" stop-color="#6C00F5"/></linearGradient><linearGradient id="c" x1="41.025" x2="156.741" y1="28.344" y2="160.344" gradientUnits="userSpaceOnUse"><stop stop-color="#FF31D9"/><stop offset="1" stop-color="#FF5BE1" stop-opacity="0"/></linearGradient><clipPath id="a"><path fill="#fff" d="M0 0h223v236H0z"/></clipPath></defs></svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 147 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 180 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 258 KiB |
|
Before Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 948 B |
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Common</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,13 +0,0 @@
|
||||
/*!
|
||||
* @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 {bootstrapApplication} from '@angular/platform-browser';
|
||||
import {appConfig} from './app/app.config';
|
||||
import {AppComponent} from './app/app.component';
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) => console.error(err));
|
||||
@@ -1,4 +0,0 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
body {
|
||||
font-family: 'Be Vietnam Pro', sans-serif;
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": ["src/main.ts"],
|
||||
"include": ["src/**/*.d.ts"]
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/* To learn more about this file see: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
"lib": ["ES2022", "dom"]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true,
|
||||
"_enabledBlockTypes": ["if", "for"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
load("@npm//@angular/docs:index.bzl", "generate_guides", "generate_tutorial")
|
||||
|
||||
package(default_visibility = ["//adev:__subpackages__"])
|
||||
|
||||
generate_guides(
|
||||
name = "first-app-guides",
|
||||
srcs = glob(["**/*.md"]),
|
||||
data = [
|
||||
":files",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "files",
|
||||
srcs = glob(
|
||||
["**/*"],
|
||||
exclude = ["**/*.md"],
|
||||
),
|
||||
)
|
||||
|
||||
generate_tutorial(
|
||||
name = "first-app",
|
||||
tutorial_srcs = ":files",
|
||||
)
|
||||
@@ -87,18 +87,18 @@ In your IDE:
|
||||
|
||||
1. In `index.html`, replace the `<title>` element with this code to update the title of the app.
|
||||
|
||||
<docs-code header="Replace in src/index.html" path="first-app/steps/01-hello-world/src/index.html" visibleLines="[5]"/>
|
||||
<docs-code header="Replace in src/index.html" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/index.html" visibleLines="[5]"/>
|
||||
|
||||
Then, save the changes you just made to `index.html`.
|
||||
|
||||
1. Next, open `first-app/src/app/app.component.ts`.
|
||||
1. In `app.component.ts`, in the `@Component` definition, replace the `template` line with this code to change the text in the app component.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[7]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[7]"/>
|
||||
|
||||
1. In `app.component.ts`, in the `AppComponent` class definition, replace the `title` line with this code to change the component title.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[11]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/01-hello-world/src/app/app.component.ts" visibleLines="[11]"/>
|
||||
|
||||
Then, save the changes you made to `app.component.ts`.
|
||||
|
||||
|
||||
@@ -66,15 +66,15 @@ In the **Edit** pane of your IDE:
|
||||
1. Open `app.component.ts` in the editor.
|
||||
1. In `app.component.ts`, import `HomeComponent` by adding this line to the file level imports.
|
||||
|
||||
<docs-code header="Import HomeComponent in src/app/app.component.ts" path="first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[2]"/>
|
||||
<docs-code header="Import HomeComponent in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[2]"/>
|
||||
|
||||
1. In `app.component.ts`, in `@Component`, update the `imports` array property and add `HomeComponent`.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[7,9]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[7,9]"/>
|
||||
|
||||
1. In `app.component.ts`, in `@Component`, update the `template` property to include the following HTML code.
|
||||
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[10,19]"/>
|
||||
<docs-code header="Replace in src/app/app.component.ts" path="adev/src/content/tutorials/first-app/steps/03-HousingLocation/src/app/app.component.ts" visibleLines="[10,19]"/>
|
||||
|
||||
1. Save your changes to `app.component.ts`.
|
||||
1. If `ng serve` is running, the app should update.
|
||||
@@ -99,13 +99,13 @@ In the **Edit** pane of your IDE:
|
||||
1. In the `first-app` directory, open `home.component.ts` in the editor.
|
||||
1. In `home.component.ts`, in `@Component`, update the `template` property with this code.
|
||||
|
||||
<docs-code header="Replace in src/app/home/home.component.ts" path="first-app/steps/03-HousingLocation/src/app/home/home.component.ts" visibleLines="[10,17]"/>
|
||||
<docs-code header="Replace in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/03-HousingLocation/src/app/home/home.component.ts" visibleLines="[10,17]"/>
|
||||
|
||||
1. Next, open `home.component.css` in the editor and update the content with these styles.
|
||||
|
||||
Note: In the browser, these can go in `src/app/home/home.component.ts` in the `styles` array.
|
||||
|
||||
<docs-code header="Replace in src/app/home/home.component.css" path="first-app/steps/03-HousingLocation/src/app/home/home.component.css"/>
|
||||
<docs-code header="Replace in src/app/home/home.component.css" path="adev/src/content/tutorials/first-app/steps/03-HousingLocation/src/app/home/home.component.css"/>
|
||||
|
||||
1. Confirm that the app builds without error.
|
||||
You should find the filter query box and button in your app and they should be styled.
|
||||
|
||||
@@ -47,15 +47,15 @@ In the **Edit** pane of your IDE:
|
||||
1. Open `home.component.ts` in the editor.
|
||||
1. In `home.component.ts`, import `HousingLocationComponent` by adding this line to the file level imports.
|
||||
|
||||
<docs-code header="Import HousingLocationComponent in src/app/home/home.component.ts" path="first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[3]"/>
|
||||
<docs-code header="Import HousingLocationComponent in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[3]"/>
|
||||
|
||||
1. Next update the `imports` property of the `@Component` metadata by adding `HousingLocationComponent` to the array.
|
||||
|
||||
<docs-code header="Add HousingLocationComponent to imports array in src/app/home/home.component.ts" path="first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[7,10]"/>
|
||||
<docs-code header="Add HousingLocationComponent to imports array in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[7,10]"/>
|
||||
|
||||
1. Now the component is ready for use in the template for the `HomeComponent`. Update the `template` property of the `@Component` metadata to include a reference to the `<app-housing-location>` tag.
|
||||
|
||||
<docs-code header="Add housing location to the component template in src/app/home/home.component.ts" path="first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[11,21]"/>
|
||||
<docs-code header="Add housing location to the component template in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/04-interfaces/src/app/home/home.component.ts" visibleLines="[11,21]"/>
|
||||
|
||||
</docs-step>
|
||||
|
||||
@@ -66,7 +66,7 @@ In this step, you will copy over the pre-written styles for the `HousingLocation
|
||||
|
||||
Note: In the browser, these can go in `src/app/housing-location/housing-location.component.ts` in the `styles` array.
|
||||
|
||||
<docs-code header="Add CSS styles to housing location to the component in src/app/housing-location/housing-location.component.css" path="first-app/steps/04-interfaces/src/app/housing-location/housing-location.component.css"/>
|
||||
<docs-code header="Add CSS styles to housing location to the component in src/app/housing-location/housing-location.component.css" path="adev/src/content/tutorials/first-app/steps/04-interfaces/src/app/housing-location/housing-location.component.css"/>
|
||||
|
||||
1. Save your code, return to the browser and confirm that the app builds without error. You should find the message "housing-location works!" rendered to the screen.Correct any errors before you continue to the next step.
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ This step adds the properties to the interface that your app needs to represent
|
||||
1. In the **Edit** pane of your IDE, open the `src/app/housinglocation.ts` file.
|
||||
1. In `housinglocation.ts`, replace the default content with the following code to make your new interface to match this example.
|
||||
|
||||
<docs-code header="Update src/app/housinglocation.ts to match this code" path="first-app/steps/05-inputs/src/app/housinglocation.ts"/>
|
||||
<docs-code header="Update src/app/housinglocation.ts to match this code" path="adev/src/content/tutorials/first-app/steps/05-inputs/src/app/housinglocation.ts"/>
|
||||
|
||||
1. Save your changes and confirm the app does not display any errors. Correct any errors before you continue to the next step.
|
||||
|
||||
@@ -66,15 +66,15 @@ There are a few more lessons to complete before that happens.
|
||||
1. In the **Edit** pane of your IDE, open `src/app/home/home.component.ts`.
|
||||
1. In `src/app/home/home.component.ts`, add this import statement after the existing `import` statements so that `HomeComponent` can use the new interface.
|
||||
|
||||
<docs-code header="Import HomeComponent in src/app/home/home.component.ts" path="first-app/steps/05-inputs/src/app/home/home.component.ts" visibleLines="[4]"/>
|
||||
<docs-code header="Import HomeComponent in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/05-inputs/src/app/home/home.component.ts" visibleLines="[4]"/>
|
||||
|
||||
1. In `src/app/home/home.component.ts`, replace the empty `export class HomeComponent {}` definition with this code to create a single instance of the new interface in the component.
|
||||
|
||||
<docs-code header="Add sample data to src/app/home/home.component.ts" path="first-app/steps/05-inputs/src/app/home/home.component.ts" visibleLines="[27,38]"/>
|
||||
<docs-code header="Add sample data to src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/05-inputs/src/app/home/home.component.ts" visibleLines="[27,38]"/>
|
||||
|
||||
1. Confirm that your `home.component.ts` file matches like this example.
|
||||
|
||||
<docs-code header="src/app/home/home.component.ts" path="first-app/steps/05-inputs/src/app/home/home.component.ts"/>
|
||||
<docs-code header="src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/05-inputs/src/app/home/home.component.ts"/>
|
||||
|
||||
By adding the `housingLocation` property of type `HousingLocation` to the `HomeComponent` class, we're able to confirm that the data matches the description of the interface. If the data didn't satisfy the description of the interface, the IDE has enough information to give us helpful errors.
|
||||
|
||||
|
||||
@@ -26,14 +26,14 @@ In the code editor:
|
||||
1. Navigate to `src/app/housing-location/housing-location.component.ts`
|
||||
1. Update the file imports to include `Input` and `HousingLocation`:
|
||||
|
||||
<docs-code header="Import HousingLocationComponent and Input in src/app/housing-location/housing-location.component.ts" path="first-app/steps/06-property-binding/src/app/housing-location/housing-location.component.ts" visibleLines="[1,3]"/>
|
||||
<docs-code header="Import HousingLocationComponent and Input in src/app/housing-location/housing-location.component.ts" path="adev/src/content/tutorials/first-app/steps/06-property-binding/src/app/housing-location/housing-location.component.ts" visibleLines="[1,3]"/>
|
||||
|
||||
</docs-step>
|
||||
|
||||
<docs-step title="Add the Input property">
|
||||
1. In the same file, add a property called `housingLocation` of type `HousingLocation` to the `HousingLocationComponent` class. Add an `!` after the property name and prefix it with the `@Input()` decorator:
|
||||
|
||||
<docs-code header="Import HousingLocationComponent and Input in src/app/housing-location/housing-location.component.ts" path="first-app/steps/06-property-binding/src/app/housing-location/housing-location.component.ts" visibleLines="[16,18]"/>
|
||||
<docs-code header="Import HousingLocationComponent and Input in src/app/housing-location/housing-location.component.ts" path="adev/src/content/tutorials/first-app/steps/06-property-binding/src/app/housing-location/housing-location.component.ts" visibleLines="[16,18]"/>
|
||||
|
||||
You have to add the `!` because the input is expecting the value to be passed. In this case, there is no default value. In our example application case we know that the value will be passed in - this is by design. The exclamation point is called the non-null assertion operator and it tells the TypeScript compiler that the value of this property won't be null or undefined.
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ In the code editor:
|
||||
|
||||
1. Navigate to `src/app/home/home.component.ts`
|
||||
1. In the template property of the `@Component` decorator, update the code to match the code below:
|
||||
<docs-code header="Add housingLocation property binding" path="first-app/steps/07-dynamic-template-values/src/app/home/home.component.ts" visibleLines="[21]"/>
|
||||
<docs-code header="Add housingLocation property binding" path="adev/src/content/tutorials/first-app/steps/07-dynamic-template-values/src/app/home/home.component.ts" visibleLines="[21]"/>
|
||||
|
||||
When adding a property binding to a component tag, we use the `[attribute] = "value"` syntax to notify Angular that the assigned value should be treated as a property from the component class and not a string value.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ In the code editor:
|
||||
1. Navigate to `src/app/housing-location/housing-location.component.ts`
|
||||
1. In the template property of the `@Component` decorator, replace the existing HTML markup with the following code:
|
||||
|
||||
<docs-code header="Update HousingLocationComponent template" path="first-app/steps/08-ngFor/src/app/housing-location/housing-location.component.ts" visibleLines="[9,15]"/>
|
||||
<docs-code header="Update HousingLocationComponent template" path="adev/src/content/tutorials/first-app/steps/08-ngFor/src/app/housing-location/housing-location.component.ts" visibleLines="[9,15]"/>
|
||||
|
||||
In this updated template code you have used property binding to bind the `housingLocation.photo` to the `src` attribute. The `alt` attribute uses interpolation to give more context to the alt text of the image.
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ In the `HomeComponent` there is only a single housing location. In this step, yo
|
||||
|
||||
1. In `src/app/home/home.component.ts`, remove the `housingLocation` property from the `HomeComponent` class.
|
||||
1. Update the `HomeComponent` class to have a property called `housingLocationList`. Update your code to match the following code:
|
||||
<docs-code header="Add housingLocationList property" path="first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="31-136"/>
|
||||
<docs-code header="Add housingLocationList property" path="adev/src/content/tutorials/first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="31-136"/>
|
||||
|
||||
IMPORANT: Do not remove the `@Component` decorator, you will update that code in an upcoming step.
|
||||
|
||||
@@ -35,7 +35,7 @@ In the `HomeComponent` there is only a single housing location. In this step, yo
|
||||
Now the app has a dataset that you can use to display the entries in the browser using the `ngFor` directive.
|
||||
|
||||
1. Update the `<app-housing-location>` tag in the template code to this:
|
||||
<docs-code header="Add ngFor to HomeComponent template" path="first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="[21,24]"/>
|
||||
<docs-code header="Add ngFor to HomeComponent template" path="adev/src/content/tutorials/first-app/steps/09-services/src/app/home/home.component.ts" visibleLines="[21,24]"/>
|
||||
|
||||
Note, in the code `[housingLocation] = "housingLocation"` the `housingLocation` value now refers to the variable used in the `ngFor` directive. Before this change, it refered to the property on the `HomeComponent` class.
|
||||
|
||||
|
||||
@@ -57,13 +57,13 @@ In the **Edit** pane of your IDE:
|
||||
1. Inside the `HousingService` class, paste these functions after the data you just copied.
|
||||
These functions allow dependencies to access the service's data.
|
||||
|
||||
<docs-code header="Service functions in src/app/housing.service.ts" path="first-app/steps/10-routing/src/app/housing.service.ts" visibleLines="[112,118]"/>
|
||||
<docs-code header="Service functions in src/app/housing.service.ts" path="adev/src/content/tutorials/first-app/steps/10-routing/src/app/housing.service.ts" visibleLines="[112,118]"/>
|
||||
|
||||
You will need these functions in a future lesson. For now, it is enough to understand that these functions return either a specific `HousingLocation` by id or the entire list.
|
||||
|
||||
1. Add a file level import for the `HousingLocation`.
|
||||
|
||||
<docs-code header="Import HousingLocation type in src/app/housing.service.ts" path="first-app/steps/10-routing/src/app/housing.service.ts" visibleLines="[2]"/>
|
||||
<docs-code header="Import HousingLocation type in src/app/housing.service.ts" path="adev/src/content/tutorials/first-app/steps/10-routing/src/app/housing.service.ts" visibleLines="[2]"/>
|
||||
|
||||
1. Confirm that the app builds without error.
|
||||
Correct any errors before you continue to the next step.
|
||||
@@ -77,17 +77,17 @@ In the **Edit** pane of your IDE, in `src/app/home/home.component.ts`:
|
||||
|
||||
1. At the top of `src/app/home/home.component.ts`, add the `inject` to the items imported from `@angular/core`. This will import the `inject` function into the `HomeComponent` class.
|
||||
|
||||
<docs-code header="Update to src/app/home/home.component.ts" path="first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[1]"/>
|
||||
<docs-code header="Update to src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[1]"/>
|
||||
|
||||
1. Add a new file level import for the `HousingService`:
|
||||
|
||||
<docs-code header="Add import to src/app/home/home.component.ts" path="first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[5]"/>
|
||||
<docs-code header="Add import to src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[5]"/>
|
||||
|
||||
1. From `HomeComponent`, delete the `housingLocationList` array entries and assign `housingLocationList` the value of empty array (`[]`). In a few steps you will update the code to pull the data from the `HousingService`.
|
||||
|
||||
1. In `HomeComponent`, add the following code to inject the new service and initialize the data for the app. The `constructor` is the first function that runs when this component is created. The code in the `constructor` will assign the `housingLocationList` the value returned from the call to `getAllHousingLocations`.
|
||||
|
||||
<docs-code header="Initialize data from service in src/app/home/home.component.ts" path="first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[31,36]"/>
|
||||
<docs-code header="Initialize data from service in src/app/home/home.component.ts" path="adev/src/content/tutorials/first-app/steps/10-routing/src/app/home/home.component.ts" visibleLines="[31,36]"/>
|
||||
|
||||
1. Save the changes to `src/app/home/home.component.ts` and confirm your app builds without error.
|
||||
Correct any errors before you continue to the next step.
|
||||
|
||||