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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 37 additions & 23 deletions cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5839,7 +5839,9 @@ export async function buildCoreDeclarationFiles(parsed: commandParser.ParsedComm
nodeutil.mkdirP(builtFolder);
process.chdir(cwd);

const host = shareId ? new Host() : new SnippetHost("decl-build", { "main.ts" : "" }, { "blocksprj": "*" });
const blocksprjConfig = readBlocksprjConfig();

const host = shareId ? new Host() : new SnippetHost("decl-build", { "main.ts" : "" }, { ...blocksprjConfig.dependencies });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need some checking around this because if readBlocksprjConfig returns undefined, trying to expand blocksprjConfig will throw an error.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throwing in cli on an invalid project build seems fine, though maybe an explicit exception would narrow in a bit quicker

const mainPkg = new pxt.MainPackage(host);

if (shareId) {
Expand Down Expand Up @@ -6223,6 +6225,16 @@ function checkDocsAsync(parsed?: commandParser.ParsedCommand): Promise<void> {
)
}

function readBlocksprjConfig(): pxt.PackageConfig {
const configPath = path.join("libs", pxt.BLOCKS_PROJECT_NAME, "pxt.json");

if (!nodeutil.fileExistsSync(configPath)) {
return undefined;
}
const config = nodeutil.readJson(configPath) as pxt.PackageConfig;
return config;
}

function checkFileSize(files: string[]): number {
if (!pxt.appTarget.cloud)
return 0;
Expand Down Expand Up @@ -6262,6 +6274,8 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo
const existingSnippets: pxt.Map<boolean> = {};
let snippets: CodeSnippet[] = [];

const blocksprjConfig = readBlocksprjConfig();

const maxFileSize = checkFileSize(nodeutil.allFiles("docs", { maxDepth: 10, allowMissing: true, includeDirs: true, ignoredFileMarker: ".ignorelargefiles" }));
if (!pxt.appTarget.ignoreDocsErrors
&& maxFileSize > (pxt.appTarget.cloud.maxFileSize || (30000000)))
Expand Down Expand Up @@ -6434,7 +6448,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo
}

// look for snippets
getCodeSnippets(entrypath, md).forEach((snippet, snipIndex) => addSnippet(snippet, entrypath, snipIndex, entrypath));
getCodeSnippets(entrypath, md, blocksprjConfig).forEach((snippet, snipIndex) => addSnippet(snippet, entrypath, snipIndex, entrypath));
}

nodeutil.mkdirP("temp");
Expand Down Expand Up @@ -6479,8 +6493,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo
continue;
}
const tutorial = pxt.tutorial.parseTutorial(tutorialMd);
const pkgs: pxt.Map<string> = { "blocksprj": "*" };
pxt.Util.jsonMergeFrom(pkgs, pxt.gallery.parsePackagesFromMarkdown(tutorialMd) || {});
const pkgs = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, pxt.gallery.parsePackagesFromMarkdown(tutorialMd) || {});

let extraFiles: Map<string> = null;

Expand All @@ -6497,7 +6510,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo
|| (tutorial.language == "python")) {
tutorial.steps
.filter(step => !!step.contentMd)
.forEach((step, stepIndex) => getCodeSnippets(`${card.name}-step${stepIndex}`, step.contentMd)
.forEach((step, stepIndex) => getCodeSnippets(`${card.name}-step${stepIndex}`, step.contentMd, blocksprjConfig)
.forEach((snippet, snippetIndex) => {
snippet.packages = pkgs;
snippet.extraFiles = extraFiles;
Expand Down Expand Up @@ -6539,8 +6552,7 @@ function internalCheckDocsAsync(compileSnippets?: boolean, re?: string, fix?: bo
continue;
}
const prj = pxt.gallery.parseExampleMarkdown(card.name, exMd);
const pkgs: pxt.Map<string> = { "blocksprj": "*" };
pxt.U.jsonMergeFrom(pkgs, prj.dependencies);
const pkgs = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, prj.dependencies);

let extraFiles: Map<string> = undefined;

Expand Down Expand Up @@ -6616,6 +6628,8 @@ function internalCacheUsedBlocksAsync(): Promise<Map<pxt.BuiltTutorialInfo>> {
const mdRegex = /\.md$/;
const targetDirs = pxt.appTarget.cacheusedblocksdirs;
const builtTututorialInfo: Map<pxt.BuiltTutorialInfo> = {};
const blocksprjConfig = readBlocksprjConfig();

if (targetDirs) {
targetDirs.forEach(dir => {
pxt.log(`looking for tutorial markdown in ${dir}`);
Expand All @@ -6633,8 +6647,9 @@ function internalCacheUsedBlocksAsync(): Promise<Map<pxt.BuiltTutorialInfo>> {
pxt.log(`error resolving tutorial markdown at ${path}`);
}
const tutorial = pxt.tutorial.parseTutorial(md) as TutorialInfo;
const pkgs: pxt.Map<string> = { "blocksprj": "*" };
pxt.Util.jsonMergeFrom(pkgs, pxt.gallery.parsePackagesFromMarkdown(md) || {});
const tutorialDeps = pxt.gallery.parsePackagesFromMarkdown(md) || {};
const pkgs: pxt.Map<string> = pxt.tutorial.mergeTutorialDependencies(blocksprjConfig.dependencies, tutorialDeps);

tutorial.pkgs = pkgs;
tutorial.path = path;

Expand Down Expand Up @@ -6752,7 +6767,7 @@ export interface CodeSnippet {
src?: string;
}

export function getCodeSnippets(fileName: string, md: string): CodeSnippet[] {
export function getCodeSnippets(fileName: string, md: string, baseConfig: pxt.PackageConfig): CodeSnippet[] {
const supported: pxt.Map<string> = {
"blocks": "ts",
"block": "ts",
Expand All @@ -6778,21 +6793,20 @@ export function getCodeSnippets(fileName: string, md: string): CodeSnippet[] {
};
}


const pkgs: pxt.Map<string> = {
"blocksprj": "*"
}
snippets.filter(snip => snip.type == "package")
const tutorialDeps = snippets.filter(snip => snip.type == "package")
.map(snip => snip.code.split('\n'))
.forEach(lines => lines
.map(l => l.replace(/\s*$/, ''))
.filter(line => !!line)
.forEach(line => {
.reduce((acc, lines) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why this was changed?

for (let line of lines) {
line = line.replace(/\s*$/, '');
if (!line) continue;
const i = line.indexOf('=');
if (i < 0) pkgs[line] = "*";
else pkgs[line.substring(0, i)] = line.substring(i + 1);
})
);
if (i < 0) acc[line] = "*";
else acc[line.substring(0, i).trim()] = line.substring(i + 1);
}
return acc;
}, {} as pxt.Map<string>);

const pkgs = pxt.tutorial.mergeTutorialDependencies(baseConfig.dependencies, tutorialDeps);

const pkgName = fileName.replace(/\\/g, '-').replace(/.md$/i, '');
return codeSnippets.map((snip, i) => {
Expand Down
56 changes: 54 additions & 2 deletions docs/writing-docs/tutorials/basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ The `BlocksExistValidator` also has an `Enabled` property that determines whethe
```
````

**Enable the `BlocksExistValidator` globally and ignore highlighted blocks**
**Enable the `BlocksExistValidator` globally and ignore highlighted blocks**
_Note: highlight is not specified in the markers property._
````
```validation.global
Expand All @@ -306,7 +306,7 @@ _Note: highlight is not specified in the markers property._
```
````
## Accordion/hidden hints
If you want to provide extra information without having to divert the coder's attention, you can include content in an "accordion" style hint control.
If you want to provide extra information without having to divert the coder's attention, you can include content in an "accordion" style hint control.

### ~ hint
If you want your hint to display by default when a step is encountered see [Explicit Hints](/writing-docs/tutorials/control-options#explicit-hints).
Expand Down Expand Up @@ -383,3 +383,55 @@ If your tutorial requires the use of an extension, you can add it using the [pac
microturtle=github:microsoft/pxt-microturtle
```
````

These dependencies will be merged into the dependencies of the default project (i.e. the project you get when you click "New Project" on the home page).

For example, in pxt-microbit the default project's dependencies look like this:

```json
{
"dependencies": {
"core": "*",
"radio": "*",
"microphone": "*"
}
}
```

With the above package annotation, the dependencies would be updated to include and entry for `microturtle` like so:

```json
{
"dependencies": {
"core": "*",
"radio": "*",
"microphone": "*",
"microturtle": "microsoft/pxt-microturtle"
}
}
```

If your tutorial needs to remove one of the default dependencies from the project, you can do so by placing the word "remove" after the equals sign.

For example, the `bluetooth` extension in pxt-microbit is not compatible with the default `radio` extension. If we wanted to author a `bluetooth` tutorial, we would need to remove the `radio` extension like so:

````
```package
bluetooth
radio=remove
```
````

which would result in:

```json
{
"dependencies": {
"core": "*",
"microphone": "*",
"bluetooth": "*"
}
}
```

In general, it's better not to remove any dependencies from the project unless absolutely required!
13 changes: 13 additions & 0 deletions pxtlib/tutorial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,4 +619,17 @@ ${code}
if (!metadata) return false;
return !!(metadata.hideFromProjects || metadata.hideIteration);
}

export function mergeTutorialDependencies(projectDependencies: pxt.Map<string>, tutorialDependencies: pxt.Map<string>): pxt.Map<string> {
const merged: pxt.Map<string> = { ...projectDependencies };
for (const dep of Object.keys(tutorialDependencies)) {
const ver = tutorialDependencies[dep];
if (ver.toLowerCase() === "remove") {
delete merged[dep];
} else {
merged[dep] = ver;
}
}
return merged;
}
}
2 changes: 1 addition & 1 deletion webapp/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3079,7 +3079,7 @@ export class ProjectView
}

if (options.dependencies) {
Util.jsonMergeFrom(cfg.dependencies, options.dependencies)
cfg.dependencies = pxt.tutorial.mergeTutorialDependencies(cfg.dependencies, options.dependencies);
}
if (options.extensionUnderTest) {
const ext = workspace.getHeader(options.extensionUnderTest);
Expand Down
Loading