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
11 changes: 10 additions & 1 deletion desktop-app/neutralino.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@
"enabled": true,
"writeToLogFile": true
},
"nativeAllowList": ["app.*", "os.*", "filesystem.*", "debug.log"],
"nativeAllowList": [
"app.exit",
"os.showOpenDialog",
"os.showSaveDialog",
"os.showMessageBox",
"os.open",
"os.setTray",
"filesystem.readFile",
"filesystem.writeFile"
],
"globalVariables": {},
"modes": {
"window": {
Expand Down
231 changes: 145 additions & 86 deletions desktop-app/prepare.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,145 @@
#!/usr/bin/env node

/**
* prepare.js — Build script for the Neutralinojs desktop app.
*
* Copies shared browser-version files (script.js, styles.css, assets/)
* from the repo root into desktop-app/resources/, and generates a
* Neutralinojs-compatible index.html from the root index.html by
* injecting the required Neutralinojs script tags and wrapper elements.
*
* Run from the desktop-app/ directory:
* node prepare.js
*/

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

const ROOT_DIR = path.resolve(__dirname, "..");
const RESOURCES_DIR = path.resolve(__dirname, "resources");

/** @section Copy shared files */

/**
* Recursively copy a directory, creating target dirs as needed.
*/
function copyDirSync(src, dest) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}

/** script.js → resources/js/script.js */
const jsDest = path.join(RESOURCES_DIR, "js");
fs.mkdirSync(jsDest, { recursive: true });
fs.copyFileSync(
path.join(ROOT_DIR, "script.js"),
path.join(jsDest, "script.js"),
);
console.log("✓ Copied script.js → resources/js/script.js");

/** styles.css → resources/styles.css */
fs.copyFileSync(
path.join(ROOT_DIR, "styles.css"),
path.join(RESOURCES_DIR, "styles.css"),
);
console.log("✓ Copied styles.css → resources/styles.css");

/** assets/ → resources/assets/ */
copyDirSync(path.join(ROOT_DIR, "assets"), path.join(RESOURCES_DIR, "assets"));
console.log("✓ Copied assets/ → resources/assets/");

/** @section Generate index.html with Neutralinojs injections */

let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8");

/** Fix relative asset paths → absolute (Neutralinojs documentRoot is /resources/) */
html = html.replace(/href="assets\//g, 'href="/assets/');
html = html.replace(/href="styles\.css"/g, 'href="/styles.css"');
/** Replace root script.js tag with neutralino.js + main.js + script.js under /js/ */
html = html.replace(
/<script\s+src="script\.js"\s*><\/script>/i,
'<script src="/js/neutralino.js"></script>\n <script src="/js/main.js"></script>\n <script src="/js/script.js"></script>',
);

/** Inject Neutralinojs app-info element after .app-container */
html = html.replace(
'<div class="app-container">',
`<div class="app-container">
<div id="neutralino-app">
<div id="neutralino-info"></div>
</div>`,
);

fs.writeFileSync(path.join(RESOURCES_DIR, "index.html"), html, "utf-8");
console.log(
"✓ Generated resources/index.html (Neutralinojs injections applied)",
);

console.log("\nDone! Run `npm run dev` to start the desktop app.");
#!/usr/bin/env node

/**
* prepare.js — Build script for the Neutralinojs desktop app.
*
* Copies shared browser-version files (script.js, styles.css, assets/)
* from the repo root into desktop-app/resources/, downloads all remote CDN
* libraries locally for 100% offline capabilities, and generates a
* Neutralinojs-compatible index.html.
*/

const fs = require("fs");
const path = require("path");
const https = require("https");

const ROOT_DIR = path.resolve(__dirname, "..");
const RESOURCES_DIR = path.resolve(__dirname, "resources");
const jsDest = path.join(RESOURCES_DIR, "js");
const LIBS_DIR = path.join(RESOURCES_DIR, "libs");

// Create directories
fs.mkdirSync(jsDest, { recursive: true });
fs.mkdirSync(LIBS_DIR, { recursive: true });

function copyDirSync(src, dest) {
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirSync(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}

// Copy shared assets
fs.copyFileSync(path.join(ROOT_DIR, "script.js"), path.join(jsDest, "script.js"));
console.log("✓ Copied script.js → resources/js/script.js");

fs.copyFileSync(path.join(ROOT_DIR, "styles.css"), path.join(RESOURCES_DIR, "styles.css"));
console.log("✓ Copied styles.css → resources/styles.css");

copyDirSync(path.join(ROOT_DIR, "assets"), path.join(RESOURCES_DIR, "assets"));
console.log("✓ Copied assets/ → resources/assets/");

// Download helper
function downloadFile(url, destPath) {
return new Promise((resolve, reject) => {
if (fs.existsSync(destPath) && fs.statSync(destPath).size > 0) {
resolve();
return;
}
console.log(`Downloading offline dependency: ${path.basename(destPath)}...`);
https.get(url, (res) => {
if (res.statusCode !== 200) {
reject(new Error(`Failed to load ${url} (${res.statusCode})`));
return;
}
const stream = fs.createWriteStream(destPath);
res.pipe(stream);
stream.on("finish", () => {
stream.close();
resolve();
});
}).on("error", reject);
});
}

async function prepareOfflineDependencies() {
console.log("\nStarting Offline Assets Preparation...");
let html = fs.readFileSync(path.join(ROOT_DIR, "index.html"), "utf-8");

// Find all CDN script and link tags
const cdnRegex = /(href|src)="(https:\/\/(?:cdnjs\.cloudflare\.com|cdn\.jsdelivr\.net)\/[^"]+)"/g;
let match;
const downloads = [];
const replacements = [];

while ((match = cdnRegex.exec(html)) !== null) {
const attr = match[1];
const url = match[2];

// Determine local filename - sanitize package version tags or query strings
const urlPath = new URL(url).pathname;
let filename = path.basename(urlPath);
if (url.includes("bootstrap-icons")) {
filename = "bootstrap-icons.min.css";
}

const localDest = path.join(LIBS_DIR, filename);
downloads.push(downloadFile(url, localDest));

// Queue replacement in HTML to point to local libs folder
replacements.push({
original: `${attr}="${url}"`,
replaced: `${attr}="/libs/${filename}"`
});
}

// Also download the relative fonts loaded by bootstrap-icons
const fontDir = path.join(LIBS_DIR, "fonts");
fs.mkdirSync(fontDir, { recursive: true });
downloads.push(downloadFile("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff2", path.join(fontDir, "bootstrap-icons.woff2")));
downloads.push(downloadFile("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/fonts/bootstrap-icons.woff", path.join(fontDir, "bootstrap-icons.woff")));

// Wait for all downloads to finish
try {
await Promise.all(downloads);
console.log("✓ All offline libraries successfully prepared.");
} catch (err) {
console.warn("⚠ Failed to bundle some dependencies offline. Fallback to CDNs will occur.", err.message);
}

// Apply replacements in HTML
replacements.forEach(rep => {
html = html.replace(rep.original, rep.replaced);
});

// Fix relative assets
html = html.replace(/href="assets\//g, 'href="/assets/');
html = html.replace(/href="styles\.css"/g, 'href="/styles.css"');

// Inject Neutralino script tags
html = html.replace(
/<script\s+src="script\.js"\s*><\/script>/i,
'<script src="/js/neutralino.js"></script>\n <script src="/js/main.js"></script>\n <script src="/js/script.js"></script>',
);

// Inject app-info element
html = html.replace(
'<div class="app-container">',
`<div class="app-container">
<div id="neutralino-app">
<div id="neutralino-info"></div>
</div>`,
);

fs.writeFileSync(path.join(RESOURCES_DIR, "index.html"), html, "utf-8");
console.log("✓ Generated resources/index.html (Offline replacements & injections applied)");
console.log("\nDone! Run `npm run dev` to start the desktop app.");
}

prepareOfflineDependencies().catch(console.error);
Loading
Loading