Skip to content
Merged
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,16 @@ git merge v<major>.<minor>.<patch>

### Adding Publications
To add a new publication, add a new entry to `src/components/Publications/copypasta.tsx` file and to the `src/components/Publications/citations.bib` file.

## Building Static Pages
To build pages that are ordinary HTML pages that can be read directly by a browser rather than served by a server, you can use any of the following commands:

```
npm run build:static:c
npm run build:static:p
npm run build:static:rs
npm run build:static:cpp
npm run build:static:ts
```
The static pages end up in a `build` directory with subdirectories `docs/next` for the nightly build version and `docs/0.*.*` for archived version docs.

6 changes: 2 additions & 4 deletions docs/developer/on-target-development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ Microsoft Visual Studio Code can be used to connect to a remote target or a virt

## Configure your Target

You may [manually install Lingua Franca](./../installation), or, if you want to use a provisioning tool, see [xronos-inc/xronos_lfc_ansible](https://github.com/xronos-inc/xronos_lfc_ansible) for an Ansible script to install Lingua Franca tools on a remote target.

If you would like to configure a virtual machine, see [xronos-inc/lfc-multipass](https://github.com/xronos-inc/lfc-multipass) for a cloud-init script to install Lingua Franca tools using multipass.
You may [manually install Lingua Franca](/docs/installation), or, if you want to use a provisioning tool, see [xronos-inc/xronos_lfc_ansible](https://github.com/xronos-inc/xronos_lfc_ansible) for an Ansible script to install Lingua Franca tools on a remote target.

Once you have your remote target, be it a virtual machine or a machine accessible over a network, you can connect to it using Visual Studio Code and the Remote SSH extension.

Expand Down Expand Up @@ -70,6 +68,6 @@ Build by pressing Ctrl + Shift + P followed by `Lingua Franca: Build and Run`. T

## References

- [How to set up Lingua Franca](./../installation)
- [How to set up Lingua Franca](/docs/installation)
- [How to create a VSCode Linux remote environment](https://ubuntu.com/blog/how-to-create-a-vscode-linux-remote-environment)
- [Remote development over SSH](https://code.visualstudio.com/docs/remote/ssh-tutorial)
2 changes: 1 addition & 1 deletion docs/reference/target-declaration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ A target specification may have optional parameters, the names and values of whi
- [**runtime-version**](#runtime-version): Specify which version of the runtime system to use.
- [**rust-include**](#rust-include): (Rust only) A set of Rust modules in the generated project.
- [**scheduler**](#scheduler): (C only) Specification of the scheduler to use.
- [**single-file-project**](#single-file-project): (Rust only) If true, enables [single-file project layout](target-language-details/?target-languages=rs#file-layout).
- [**single-file-project**](#single-file-project): (Rust only) If true, enables [single-file project layout](../target-language-details/?target-languages=rs#file-layout).
- [**single-threaded**](#single-threaded): Specify to not use multithreading.
- [**timeout**](#timeout): A time value (with units) specifying the logical stop time of execution. See [Termination](../writing-reactors/termination.mdx).
- [**workers**](#workers): If using multiple threads, how many worker threads to create.
Expand Down
3 changes: 3 additions & 0 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ const config: Config = {
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'throw',

// Generate URLs with trailing slashes (directories) for better file:// protocol support
trailingSlash: true,

// Even if you don't use internationalization, you can use this field to set
// useful metadata like html lang. For example, if your site is Chinese, you
// may want to replace "en" with "zh-Hans".
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"build:static:c": "docusaurus build && node util/fix-links-for-static.js && node util/set-default-language.js c",
"build:static:cpp": "docusaurus build && node util/fix-links-for-static.js && node util/set-default-language.js cpp",
"build:static:py": "docusaurus build && node util/fix-links-for-static.js && node util/set-default-language.js py",
"build:static:rs": "docusaurus build && node util/fix-links-for-static.js && node util/set-default-language.js rs",
"build:static:ts": "docusaurus build && node util/fix-links-for-static.js && node util/set-default-language.js ts",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
Expand Down
7 changes: 6 additions & 1 deletion src/components/ShikiLFHighlighter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ export const ShikiLFHighlighter = ({
if (isBrowser) {
return <div dangerouslySetInnerHTML={{ __html: code }} />;
} else {
return <div>{children}</div>;
// For static builds, render as a proper code block even without syntax highlighting
return (
<pre className="shiki">
<code>{children}</code>
</pre>
);
}
};
190 changes: 190 additions & 0 deletions util/fix-links-for-static.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/env node

/**
* Post-build script to fix links in Docusaurus-generated HTML files
* to work without a web server (file:// protocol).
*
* This script converts directory links (ending with /) to explicit
* index.html links so they work when opening HTML files directly.
*/

const fs = require('fs');
const path = require('path');

const buildDir = path.join(__dirname, '..', 'build');

function getRelativePath(fromFile, toPath) {
// Convert absolute path to relative path
// fromFile: absolute path to the current HTML file
// toPath: absolute path or URL starting with /

if (!toPath.startsWith('/')) {
// Already relative, return as is
return toPath;
}

// Remove leading slash and build directory path
const toPathRelative = toPath.substring(1);
const fromDir = path.dirname(path.relative(buildDir, fromFile));

// Calculate relative path
if (fromDir === '.') {
return toPathRelative;
}

const fromParts = fromDir.split(path.sep).filter(p => p !== '.');
const toParts = toPathRelative.split('/').filter(p => p !== '');

// Find common prefix
let commonLength = 0;
while (commonLength < fromParts.length &&
commonLength < toParts.length &&
fromParts[commonLength] === toParts[commonLength]) {
commonLength++;
}

// Build relative path
const upLevels = fromParts.length - commonLength;
const downPath = toParts.slice(commonLength).join('/');
const relativePath = '../'.repeat(upLevels) + (downPath || 'index.html');

return relativePath || 'index.html';
}

function fixLinksInFile(filePath) {
let content = fs.readFileSync(filePath, 'utf8');
let modified = false;

// Pattern 1: href="/path/to/dir/" -> href="relative/path/to/dir/index.html"
// Pattern 2: href="/path/to/dir" (no trailing slash) -> href="relative/path/to/dir/index.html" (if it's a directory)
// Pattern 3: href="relative/path/to/dir/" -> href="relative/path/to/dir/index.html"
// Pattern 4: href="relative/path/to/dir" -> href="relative/path/to/dir/index.html"

// We need to be careful not to break:
// - External links (http://, https://, mailto:, etc.)
// - Anchor links (#)
// - Links to files with extensions (.css, .js, .png, etc.)
// - Data URLs and other special protocols

// Replace href attributes that point to directories
// Match: href="/path/" or href="path/" or href="/path" or href="path"
// But exclude: external URLs, anchors, files with extensions, data URLs

const hrefPattern = /href=(["'])((?:(?!\1)[^#?])+?)(\/?)(\1)/g;

content = content.replace(hrefPattern, (match, quote, url, trailingSlash, endQuote) => {
// Skip external URLs
if (url.match(/^(https?:|mailto:|tel:|data:|#)/)) {
return match;
}

// Convert absolute asset paths to relative (but keep .html handling separate)
if (url.match(/\.[a-zA-Z0-9]+$/) && !url.endsWith('.html')) {
// Asset file (CSS, JS, images, etc.) - convert absolute to relative
if (url.startsWith('/')) {
const relativeUrl = getRelativePath(filePath, url);
modified = true;
return `href=${quote}${relativeUrl}${endQuote}`;
}
return match;
}

// Skip URLs that have query parameters or fragments (we'll handle those separately)
if (url.includes('?') || url.includes('#')) {
return match;
}

// If URL ends with /, replace with /index.html
// If URL doesn't end with / and doesn't have an extension, add /index.html
let newUrl = url;
if (url.endsWith('/')) {
newUrl = url + 'index.html';
} else if (!url.match(/\.[a-zA-Z0-9]+$/)) {
// No extension, so it's likely a directory
newUrl = url + '/index.html';
} else {
return match; // Has extension, leave as is
}

// Convert absolute paths to relative paths for file:// protocol support
if (newUrl.startsWith('/')) {
newUrl = getRelativePath(filePath, newUrl);
}

modified = true;
return `href=${quote}${newUrl}${endQuote}`;
});

// Also fix src attributes for similar cases (though less common)
const srcPattern = /src=(["'])((?:(?!\1)[^#?])+?)(\/?)(\1)/g;

content = content.replace(srcPattern, (match, quote, url, trailingSlash, endQuote) => {
// Skip external URLs
if (url.match(/^(https?:|data:)/)) {
return match;
}

// Skip URLs that already have file extensions
if (url.match(/\.[a-zA-Z0-9]+$/)) {
// Convert absolute paths to relative paths
if (url.startsWith('/')) {
const relativeUrl = getRelativePath(filePath, url);
modified = true;
return `src=${quote}${relativeUrl}${endQuote}`;
}
return match;
}

// Skip URLs that have query parameters
if (url.includes('?')) {
return match;
}

let newUrl = url;
if (url.endsWith('/')) {
newUrl = url + 'index.html';
} else if (!url.match(/\.[a-zA-Z0-9]+$/)) {
newUrl = url + '/index.html';
} else {
return match;
}

// Convert absolute paths to relative paths for file:// protocol support
if (newUrl.startsWith('/')) {
newUrl = getRelativePath(filePath, newUrl);
}

modified = true;
return `src=${quote}${newUrl}${endQuote}`;
});

if (modified) {
fs.writeFileSync(filePath, content, 'utf8');
return true;
}
return false;
}

function processDirectory(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
let fixedCount = 0;

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
fixedCount += processDirectory(fullPath);
} else if (entry.isFile() && entry.name.endsWith('.html')) {
if (fixLinksInFile(fullPath)) {
fixedCount++;
}
}
}

return fixedCount;
}

console.log('Fixing links in HTML files for static file serving...');
const fixedCount = processDirectory(buildDir);
console.log(`Fixed links in ${fixedCount} HTML file(s).`);

Loading