Skip to content

Commit

Permalink
Merged main
Browse files Browse the repository at this point in the history
  • Loading branch information
0xCipherCoder committed Oct 13, 2024
2 parents 8176914 + 21cd11a commit cc892c3
Show file tree
Hide file tree
Showing 127 changed files with 9,937 additions and 7,955 deletions.
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,5 +106,12 @@ package-lock.json
# translations are stored in the `i18n` via crowdin
i18n


# code-import
code/node_modules
code/package-lock.json
code/yarn.lock
code/pnpm-lock.yaml

# vscode configuration
.vscode
.vscode
1 change: 1 addition & 0 deletions .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
npx lint-staged
59 changes: 53 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ transparent as possible, whether it's:
- publicly displayed via the UI of [solana.com](https://solana.com) (located in
a different repo)
- content translations are supported via Crowdin
- code blocks must use code-import for file snippets (via filesystem)
- code file should be [tests](https://nodejs.org/api/test.html) and should add
code ranges instead of whole test file

## Style guidelines

Expand All @@ -45,9 +48,7 @@ In particular:
and save the person reviewing your PR some time. We recommend
[Grammarly](https://grammarly.com/). In
[your Grammarly dictionary](https://account.grammarly.com/customize), you may
wish to add Solana-specific words like `lamport`, `blockhash`, etc. For VScode
users, there is a
[VScode extension for Grammarly](https://marketplace.visualstudio.com/items?itemName=znck.grammarly).
wish to add Solana-specific words like `lamport`, `blockhash`, etc.
- Use US English rather than British English. Grammarly will catch this for you.
- Use 'onchain' (not on-chain, definitely not smart contract) when referring to
onchain apps. This comes from the Solana Foundation style guide, and is
Expand Down Expand Up @@ -84,8 +85,8 @@ In particular:
have `instruction handlers` that process `instructions`. Do not refer to
[instruction handlers](https://solana.com/docs/terminology#instruction-handler)
as instructions! The reason is simple: an instruction cannot process an
instruction. The `multiple` template in Anchor also calls the function's
`handler`.
instruction. The `multiple` template in Anchor also calls these functions
`handler`s.

### Code

Expand Down Expand Up @@ -273,6 +274,52 @@ For images, you can use the path starting with `/public` like this:
> links will be automatically adjusted to function on the website. Including
> making the images viewable and removing `.md` file extensions.
### Code Blocks

In addition to standard markdown "fenced" code blocks (i.e. using triple
backticks), the developer content repo requires the use of code-import for file
snippets. This ensures that code examples are always up-to-date with the actual
source files.

#### Using code-import

To use code-import, follow these steps:

Ensure your code file is a test file located in the appropriate directory within
the repo. Use the following syntax to import code snippets:

```javascript file="/path/to/your/file.ts#L1-L10,#L15-L20"

```

This will import lines 1-10 and 15-20 from the specified file.

Always use code ranges instead of importing whole files. This helps keep
examples concise and focused.

#### Code-import Rules

- The file path must start with a forward slash (/).
- You can specify multiple line ranges, separated by commas.
- Line ranges should be in ascending order and not overlap.
- Invalid ranges (e.g., #L4-L3) are not allowed.
- Line numbers start at 1, so #L0 is invalid.
- Trailing commas in the range specification are not allowed.

Example of a valid code-import:

```javascript file="/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L18"

```

Example of an invalid code-import:

```javascript file=/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L19,#L1-L3

```

This is invalid because the ranges are not in ascending order and overlap.

### Table of contents

When a content page is rendered on solana.com, a table of contents will be
Expand Down Expand Up @@ -519,7 +566,7 @@ a list of available components
content
- [images](#images) - details about how to include images in a piece of content
- [code blocks](#code-blocks) - additional functionality on top of standard
markdown code blocks
markdown code blocks, these support code file import from filesystem
- [blockquote](#blockquote) - additional functionality on top of the standard
HTML `blockquote` element
- [Callout](#callout) - custom component used to render message to the reader in
Expand Down
19 changes: 19 additions & 0 deletions code/cookbook/wallets/check-public-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { PublicKey } from "@solana/web3.js";

// Note that Keypair.generate() will always give a public key that is valid for users

// Valid public key
const key = new PublicKey("5oNDL3swdJJF1g9DzJiZ4ynHXgszjAEpUkxVYejchzrY");
// Lies on the ed25519 curve and is suitable for users
console.log(PublicKey.isOnCurve(key.toBytes()));

// Valid public key
const offCurveAddress = new PublicKey(
"4BJXYkfvg37zEmBbsacZjeQDpTNx91KppxFJxRqrz48e",
);

// Not on the ed25519 curve, therefore not suitable for users
console.log(PublicKey.isOnCurve(offCurveAddress.toBytes()));

// Not a valid public key
const errorPubkey = new PublicKey("testPubkey");
15 changes: 15 additions & 0 deletions code/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "code",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@solana/web3.js": "^1.95.2"
}
}
222 changes: 222 additions & 0 deletions coder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { promises as fs } from "node:fs";
import path from "node:path";
import os from "node:os";
import { unified } from "unified";
import remarkParse from "remark-parse";
import remarkStringify from "remark-stringify";
import remarkFrontmatter from "remark-frontmatter";
import { visit } from "unist-util-visit";
import ignore, { type Ignore } from "ignore";
import importCode from "./src/utils/code-import";
import chokidar from "chokidar";

let debugMode = false;

const debug = (...args: string[]) => {
if (debugMode) {
console.log("[DEBUG]", ...args);
}
};

const hasCodeComponentWithFileMeta = async (
filePath: string,
): Promise<boolean> => {
const content = await fs.readFile(filePath, "utf8");
let hasMatch = false;

const tree = unified().use(remarkParse).use(remarkFrontmatter).parse(content);

visit(tree, "code", node => {
if (node.meta?.includes("file=")) {
hasMatch = true;
return false; // Stop visiting
}
});

return hasMatch;
};

const getIgnore = async (directory: string): Promise<Ignore> => {
const ig = ignore();

try {
const gitignoreContent = await fs.readFile(
path.join(directory, ".gitignore"),
"utf8",
);
ig.add(gitignoreContent);
// ignore all dotfiles
ig.add([".*"]);
// ignore CONTRIBUTING.md because it mentions the code component example
ig.add("CONTRIBUTING.md");
} catch (error) {
// If .gitignore doesn't exist, just continue without it
if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
throw error;
}
}

return ig;
};

const getMarkdownAndMDXFiles = async (directory: string): Promise<string[]> => {
const ig = await getIgnore(directory);

const walkDir = async (dir: string): Promise<string[]> => {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = await Promise.all(
entries.map(async entry => {
const res = path.resolve(dir, entry.name);
const relativePath = path.relative(directory, res);

if (ig.ignores(relativePath) || entry.name === ".gitignore") {
debug(`Ignoring file: ${relativePath}`);
return [];
}

if (entry.isDirectory()) {
return walkDir(res);
}

if (
entry.isFile() &&
(entry.name.endsWith(".md") || entry.name.endsWith(".mdx"))
) {
if (await hasCodeComponentWithFileMeta(res)) {
debug(`Found file with code component: ${relativePath}`);
return res;
}
debug(
`Skipping file (no code component with file meta): ${relativePath}`,
);
}

return [];
}),
);
return files.flat();
};

return walkDir(directory);
};

const processContent = async (
content: string,
filePath: string,
): Promise<string> => {
try {
const file = await unified()
.use(remarkParse)
.use(remarkFrontmatter)
.use(importCode, {
preserveTrailingNewline: false,
removeRedundantIndentations: true,
rootDir: process.cwd(),
})
.use(remarkStringify, {
bullet: "-",
emphasis: "*",
fences: true,
listItemIndent: "one",
rule: "-",
ruleSpaces: false,
strong: "*",
tightDefinitions: true,
})
.process(content);
return String(file);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
throw new Error(
`File not found: ${(error as NodeJS.ErrnoException).path}`,
);
}
throw error;
}
};

const processFile = async (filePath: string): Promise<void> => {
try {
if (!(await hasCodeComponentWithFileMeta(filePath))) {
debug(`Skipping ${filePath}: No code component with file meta found.`);
return;
}

const originalContent = await fs.readFile(filePath, "utf8");
const processedContent = await processContent(originalContent, filePath);
if (originalContent !== processedContent) {
await fs.writeFile(filePath, processedContent);
console.log(`Updated: ${filePath}`);
} else {
debug(`No changes needed for: ${filePath}`);
}
} catch (error) {
console.error(`Error processing ${filePath}: ${(error as Error).message}`);
}
};

const processInChunks = async <T>(
items: T[],
processItem: (item: T) => Promise<void>,
chunkSize: number,
): Promise<void> => {
for (let i = 0; i < items.length; i += chunkSize) {
const chunk = items.slice(i, i + chunkSize);
await Promise.all(chunk.map(processItem));
}
};

const watchFiles = async (directory: string): Promise<void> => {
const watcher = chokidar.watch(["**/*.md", "**/*.mdx"], {
ignored: [
"**.**",
/(^|[\/\\])\../,
"**/node_modules/**",
"**/.git/**",
".gitignore",
], // ignore dotfiles, node_modules, .git, and .gitignore
persistent: true,
cwd: directory,
});

console.log("Watch mode started. Waiting for file changes...");

watcher
.on("add", filePath => processFile(path.join(directory, filePath)))
.on("change", filePath => processFile(path.join(directory, filePath)))
.on("unlink", filePath => console.log(`File ${filePath} has been removed`));
};

const main = async (): Promise<void> => {
const filePath = process.argv[2];
const watchMode =
process.argv.includes("--watch") || process.argv.includes("-w");
debugMode = process.argv.includes("--debug") || process.argv.includes("-d");

if (debugMode) {
console.log("Debug mode enabled");
}

if (filePath && !watchMode && !debugMode) {
// Process single file
const absolutePath = path.resolve(process.cwd(), filePath);
console.log(`Processing single file: ${absolutePath}`);
await processFile(absolutePath);
} else if (watchMode) {
// Watch mode
await watchFiles(process.cwd());
} else {
// Process all files
const files = await getMarkdownAndMDXFiles(process.cwd());
const chunkSize = Math.max(1, Math.ceil(files.length / os.cpus().length));

console.log(`Processing ${files.length} files...`);
await processInChunks(files, processFile, chunkSize);
}

if (!watchMode) {
console.log("Sync process completed.");
}
};

main().catch(console.error);
2 changes: 1 addition & 1 deletion content/cookbook/wallets/check-publickey.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ have a private key associated with them. You can check this by looking to see if
the public key lies on the ed25519 curve. Only public keys that lie on the curve
can be controlled by users with wallets.

```javascript file="check-public-key.ts"
```javascript file=/code/cookbook/wallets/check-public-key.ts#L1-L2,#L3-L19
import { PublicKey } from "@solana/web3.js";

// Note that Keypair.generate() will always give a public key that is valid for users
Expand Down
3 changes: 0 additions & 3 deletions content/courses/connecting-to-offchain-data/metadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,3 @@ lessons:
- oracles
- verifiable-randomness-functions
priority: 20
# Uses out of date repos
# TODO: Superteam to update
isHidden: true
2 changes: 1 addition & 1 deletion content/courses/connecting-to-offchain-data/oracles.md
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,7 @@ the data feed account does not exist anymore, withdraw the user's escrowed
funds.
A potential solution to this challenge can be found
[in the Github repository on the `challenge-solution` branch](https://github.com/Unboxed-Software/michael-burry-escrow/tree/challenge-solution).
[in the Github repository on the `challenge-solution` branch](https://github.com/solana-developers/burry-escrow/tree/challenge-solution).
<Callout type="success" title="Completed the lab?">
Expand Down
Loading

0 comments on commit cc892c3

Please sign in to comment.