mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-09-05 10:23:39 +00:00
Scripts to generate and serve index HTML file for all tests
Node.js, requires no additional interpreter or dependencies. Generates a single index.html file at root which displays launch links for all tests.
This commit is contained in:
@@ -27,3 +27,4 @@ node_modules
|
||||
/dist/cappuccino/lib
|
||||
/dist/cappuccino/bin
|
||||
Tests/Manual/.Frameworks
|
||||
/Tests/Manual/index.html
|
||||
|
||||
Executable
+140
@@ -0,0 +1,140 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* generate-index.js
|
||||
* Tests/Manual
|
||||
*
|
||||
* Created by David Richardson, September 7, 2026
|
||||
* Copyright 2026, David Richardson. All rights reserved.
|
||||
*
|
||||
* Writes ./index.html: links every manual test app's source entry
|
||||
* points, plus its Build/Debug and Build/Release product entry
|
||||
* points when they exist.
|
||||
*
|
||||
* No npm dependencies -- Node core modules only.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const ROOT = __dirname;
|
||||
|
||||
// Same detection rule as Tests/Manual/Jakefile: an app directory has
|
||||
// Info.plist or index.html, and is never Frameworks/.Frameworks.
|
||||
function listAppDirs()
|
||||
{
|
||||
return fs.readdirSync(ROOT, { withFileTypes: true })
|
||||
.filter(function(entry)
|
||||
{
|
||||
if (!entry.isDirectory())
|
||||
return false;
|
||||
if (entry.name.charAt(0) === ".")
|
||||
return false;
|
||||
if (entry.name === "Frameworks")
|
||||
return false;
|
||||
|
||||
var dir = path.join(ROOT, entry.name);
|
||||
return fs.existsSync(path.join(dir, "Info.plist")) ||
|
||||
fs.existsSync(path.join(dir, "index.html"));
|
||||
})
|
||||
.map(function(entry) { return entry.name; })
|
||||
.sort();
|
||||
}
|
||||
|
||||
// The built product directory is named after productName, not the app
|
||||
// folder (confirmed: ArrayController1 -> Build/Debug/ArrayController).
|
||||
// Find it by reading what's actually there, not by assuming a name.
|
||||
function findBuiltProduct(appDir, configuration)
|
||||
{
|
||||
var configDir = path.join(ROOT, appDir, "Build", configuration);
|
||||
|
||||
if (!fs.existsSync(configDir))
|
||||
return null;
|
||||
|
||||
var entries = fs.readdirSync(configDir, { withFileTypes: true })
|
||||
.filter(function(entry) { return entry.isDirectory(); });
|
||||
|
||||
for (var i = 0; i < entries.length; i++)
|
||||
{
|
||||
var candidate = path.join(configDir, entries[i].name, "index.html");
|
||||
|
||||
if (fs.existsSync(candidate))
|
||||
return path.join(appDir, "Build", configuration, entries[i].name, "index.html");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function escapeHtml(s)
|
||||
{
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
|
||||
function link(href, label)
|
||||
{
|
||||
return "<a href=\"" + encodeURI(href) + "\">" + escapeHtml(label) + "</a>";
|
||||
}
|
||||
|
||||
function buildIndexHtml()
|
||||
{
|
||||
var appDirs = listAppDirs();
|
||||
|
||||
var rows = appDirs.map(function(appDir)
|
||||
{
|
||||
var links = [];
|
||||
|
||||
if (fs.existsSync(path.join(ROOT, appDir, "index.html")))
|
||||
links.push(link(path.join(appDir, "index.html"), "source"));
|
||||
|
||||
if (fs.existsSync(path.join(ROOT, appDir, "index-debug.html")))
|
||||
links.push(link(path.join(appDir, "index-debug.html"), "source debug"));
|
||||
|
||||
var debugProduct = findBuiltProduct(appDir, "Debug");
|
||||
if (debugProduct)
|
||||
links.push(link(debugProduct, "built debug"));
|
||||
|
||||
var releaseProduct = findBuiltProduct(appDir, "Release");
|
||||
if (releaseProduct)
|
||||
links.push(link(releaseProduct, "built release"));
|
||||
|
||||
return "<tr><td>" + escapeHtml(appDir) + "</td><td>" + links.join(" ") + "</td></tr>";
|
||||
});
|
||||
|
||||
return "<!DOCTYPE html>\n" +
|
||||
"<html><head><meta charset=\"utf-8\"><title>Manual Tests</title>\n" +
|
||||
"<style>body{font-family:sans-serif;font-size:14px}" +
|
||||
"table{border-collapse:collapse}td{padding:2px 8px;vertical-align:top}" +
|
||||
"tr:nth-child(even){background:#f4f4f4}</style></head><body>\n" +
|
||||
"<h1>Manual Integration Tests</h1>\n" +
|
||||
"<p>" + appDirs.length + " applications. Generated by generate-index.js.</p>\n" +
|
||||
"<table>\n" + rows.join("\n") + "\n</table>\n" +
|
||||
"</body></html>\n";
|
||||
}
|
||||
|
||||
function generateIndex()
|
||||
{
|
||||
var html = buildIndexHtml();
|
||||
fs.writeFileSync(path.join(ROOT, "index.html"), html);
|
||||
return html;
|
||||
}
|
||||
|
||||
module.exports = { generateIndex: generateIndex };
|
||||
|
||||
if (require.main === module)
|
||||
{
|
||||
generateIndex();
|
||||
console.log("Wrote " + path.join(ROOT, "index.html"));
|
||||
}
|
||||
Executable
+114
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* serve.js
|
||||
* Tests/Manual
|
||||
*
|
||||
* Regenerates index.html, then serves this directory over plain HTTP,
|
||||
* so tests run in any browser -- not only Safari via file://.
|
||||
*
|
||||
* No npm dependencies -- Node core modules only. Ctrl-C to stop.
|
||||
*
|
||||
* Writes ./index.html: links every manual test app's source entry
|
||||
* points, plus its Build/Debug and Build/Release product entry
|
||||
* points when they exist.
|
||||
*
|
||||
* No npm dependencies -- Node core modules only.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { exec } = require("child_process");
|
||||
const { generateIndex } = require("./generate-index.js");
|
||||
|
||||
const ROOT = __dirname;
|
||||
const PORT = 8347;
|
||||
|
||||
const MIME_TYPES = {
|
||||
".html": "text/html",
|
||||
".htm": "text/html",
|
||||
".js": "application/javascript",
|
||||
".sj": "application/javascript",
|
||||
".css": "text/css",
|
||||
".json": "application/json",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".svg": "image/svg+xml",
|
||||
".txt": "text/plain",
|
||||
".plist": "text/plain"
|
||||
};
|
||||
|
||||
function resolveRequestPath(url)
|
||||
{
|
||||
var decoded = decodeURIComponent(url.split("?")[0]);
|
||||
var resolved = path.normalize(path.join(ROOT, decoded));
|
||||
|
||||
// Reject traversal outside ROOT.
|
||||
if (resolved !== ROOT && !resolved.startsWith(ROOT + path.sep))
|
||||
return null;
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function serveFile(filePath, res)
|
||||
{
|
||||
fs.readFile(filePath, function(err, data)
|
||||
{
|
||||
if (err)
|
||||
{
|
||||
res.writeHead(404, { "Content-Type": "text/plain" });
|
||||
res.end("404 Not Found: " + filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
var type = MIME_TYPES[path.extname(filePath).toLowerCase()] || "application/octet-stream";
|
||||
res.writeHead(200, { "Content-Type": type });
|
||||
res.end(data);
|
||||
});
|
||||
}
|
||||
|
||||
const server = http.createServer(function(req, res)
|
||||
{
|
||||
var filePath = resolveRequestPath(req.url);
|
||||
|
||||
if (!filePath)
|
||||
{
|
||||
res.writeHead(403, { "Content-Type": "text/plain" });
|
||||
res.end("403 Forbidden");
|
||||
return;
|
||||
}
|
||||
|
||||
fs.stat(filePath, function(err, stats)
|
||||
{
|
||||
if (!err && stats.isDirectory())
|
||||
filePath = path.join(filePath, "index.html");
|
||||
|
||||
serveFile(filePath, res);
|
||||
});
|
||||
});
|
||||
|
||||
generateIndex();
|
||||
|
||||
server.listen(PORT, function()
|
||||
{
|
||||
var url = "http://localhost:" + PORT + "/index.html";
|
||||
console.log("Serving " + ROOT + " at " + url);
|
||||
console.log("Ctrl-C to stop.");
|
||||
exec("open " + url);
|
||||
});
|
||||
Reference in New Issue
Block a user