mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
The reveal answer button accidentally loads files into the embedded editor that aren't properly relativized. This ends up switching the currently open file to a different file, unexpectedly. In addition, due to the incorrect paths, files like `favicon.ico` end up being loaded in the embedded editor; resulting in a bad experience as the images are shown as plain text. PR Close #61925
87 lines
2.5 KiB
TypeScript
87 lines
2.5 KiB
TypeScript
/**
|
|
* @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, relative} from 'path';
|
|
import {glob} from 'tinyglobby';
|
|
import {
|
|
FileAndContentRecord,
|
|
PackageJson,
|
|
TutorialConfig,
|
|
TutorialMetadata,
|
|
} from '../../interfaces';
|
|
import {getFileContents} from './utils.mjs';
|
|
import assert from 'assert';
|
|
|
|
/** Generate the metadata.json content for a provided tutorial config. */
|
|
export async function generateMetadata(
|
|
path: string,
|
|
config: TutorialConfig,
|
|
files: FileAndContentRecord,
|
|
): Promise<TutorialMetadata> {
|
|
const tutorialFiles: FileAndContentRecord = {};
|
|
const {dependencies, devDependencies} = JSON.parse(
|
|
files['package.json'] as string,
|
|
) as PackageJson;
|
|
|
|
config.openFiles?.forEach((file) => (tutorialFiles[file] = files[file]));
|
|
|
|
return {
|
|
type: config.type,
|
|
openFiles: config.openFiles || [],
|
|
allFiles: Object.keys(files),
|
|
tutorialFiles,
|
|
answerFiles: await getAnswerFiles(path, config, files),
|
|
hiddenFiles: config.openFiles
|
|
? Object.keys(files).filter((filename) => !config.openFiles!.includes(filename))
|
|
: [],
|
|
dependencies: {
|
|
...dependencies,
|
|
...devDependencies,
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Generate the answer files for the metadata.json. */
|
|
async function getAnswerFiles(
|
|
path: string,
|
|
config: TutorialConfig,
|
|
files: FileAndContentRecord,
|
|
): Promise<FileAndContentRecord> {
|
|
const answerFiles: FileAndContentRecord = {};
|
|
const answerPrefix = /answer[\\/]/;
|
|
|
|
if (config.answerSrc) {
|
|
assert(config.answerRootDir, `No \`answerRootDir\` specified for: ${path}`);
|
|
|
|
const answersDir = join(path, config.answerSrc);
|
|
const answerRootDir = join(path, config.answerRootDir);
|
|
|
|
const answerFilePaths = await glob('**/*', {
|
|
cwd: answersDir,
|
|
onlyFiles: true,
|
|
absolute: false,
|
|
});
|
|
|
|
for (const answerFile of answerFilePaths) {
|
|
const absAnswerFile = join(answersDir, answerFile);
|
|
// Relativize answer file path.
|
|
const filePath = relative(answerRootDir, absAnswerFile);
|
|
// We use the absolute file in order to read the content, but the key
|
|
// needs to be a relative path within the project.
|
|
answerFiles[filePath] = getFileContents(absAnswerFile);
|
|
}
|
|
} else {
|
|
for (const file of Object.keys(files)) {
|
|
if (answerPrefix.test(file)) {
|
|
answerFiles[file.replace(answerPrefix, '')] = files[file];
|
|
}
|
|
}
|
|
}
|
|
|
|
return answerFiles;
|
|
}
|