mascot-app
Version:
Master ActionScript COnfigurator Tool. MASCOT automatically resolves ActionScript project dependencies and generates `asconfig.json` files for easy compilation with `asconfigc`.
188 lines (159 loc) • 6.5 kB
JavaScript
const fs = require("fs");
const path = require("path");
/**
* Builds a project-level dependency map from `classes.json`.
* Outputs a `deps.json` file and appends errors to `problems.log`.
*
* @param {string} workspaceDir - The workspace directory (not currently used but reserved for future use).
* @param {string} outputDir - The directory containing `classes.json` and `projects.json`, and where `deps.json` and `problems.log` will be saved.
* @param {boolean} [replace=false] - Whether to replace `deps.json` if it already exists.
*/
function buildDependencies(workspaceDir, outputDir, replace = false) {
const classesFilePath = path.join(outputDir, "classes.json");
const projectsFilePath = path.join(outputDir, "projects.json");
const depsFilePath = path.join(outputDir, "deps.json");
const problemsFilePath = path.join(outputDir, "problems.log");
// Check required files
if (!fs.existsSync(classesFilePath) || !fs.existsSync(projectsFilePath)) {
const errorMsg =
"Required input files `classes.json` or `projects.json` not found.";
console.error(errorMsg);
fs.appendFileSync(problemsFilePath, errorMsg + "\n");
return;
}
if (fs.existsSync(depsFilePath)) {
if (!replace) {
console.log("deps.json already exists. Skipping dependency analysis.");
return;
} else {
fs.unlinkSync(depsFilePath);
console.log("Existing deps.json deleted. Starting fresh.");
}
}
// Load input files
const classes = JSON.parse(fs.readFileSync(classesFilePath));
const projects = JSON.parse(fs.readFileSync(projectsFilePath));
const projectDependencies = {}; // Map of project path to dependency info
const problems = [];
// Populate dependencies and root classes
classes.forEach((classEntry) => {
const { analyzed_class, class_couplings } = classEntry;
const projectPath = analyzed_class.project_dir;
// Ensure the project is initialized in the dependency map
if (!projectDependencies[projectPath]) {
const root_classes = [];
const project = projects.find(
(proj) => proj.project_home_dir === projectPath
);
if (project && project.has_descriptor) {
root_classes.push(
...project.known_descriptors.map((descObj) => descObj.related_class)
);
}
projectDependencies[projectPath] = {
project_path: projectPath,
project_dependencies: [],
num_dependencies: 0,
root_classes,
};
}
// Add dependencies
const projectDeps = projectDependencies[projectPath];
class_couplings
.filter(
(coupling) =>
coupling.class_exists &&
coupling.matching_project &&
coupling.matching_project !== projectPath
)
.forEach((coupling) => {
if (
!projectDeps.project_dependencies.includes(coupling.matching_project)
) {
projectDeps.project_dependencies.push(coupling.matching_project);
projectDeps.num_dependencies++;
}
});
});
// Sort projects by num_dependencies ascending
const sortedDependencies = Object.values(projectDependencies).sort(
(a, b) => a.num_dependencies - b.num_dependencies
);
// Write results to deps.json
fs.writeFileSync(depsFilePath, JSON.stringify(sortedDependencies, null, 2));
fs.appendFileSync(problemsFilePath, problems.join("\n\n"));
console.log(`Dependency analysis complete. Results saved to ${depsFilePath}`);
console.log(`Problems logged to ${problemsFilePath}`);
}
/**
* Generates a flat list of build tasks for projects in dependency order.
*
* @param {string} workspaceDir - Absolute path to the workspace directory.
* @param {string} cacheDir - Absolute path to the cache directory where `deps.json` is located.
* @param {boolean} [replace=false] - Whether to replace the `tasks.json` file if it exists.
*/
function makeBuildTasks(workspaceDir, cacheDir, replace = false) {
const depsFilePath = path.join(cacheDir, "deps.json");
const tasksFilePath = path.join(cacheDir, "tasks.json");
const problemsFilePath = path.join(cacheDir, "problems.log");
// Ensure `deps.json` exists
if (!fs.existsSync(depsFilePath)) {
const errorMsg = "`deps.json` is missing. Cannot create build tasks.";
console.error(errorMsg);
fs.appendFileSync(problemsFilePath, errorMsg + "\n");
return;
}
// Check for existing `tasks.json`
if (fs.existsSync(tasksFilePath)) {
if (!replace) {
console.log(
"tasks.json already exists. Skipping build tasks generation."
);
return;
} else {
fs.unlinkSync(tasksFilePath);
console.log("Existing tasks.json deleted. Starting fresh.");
}
}
const deps = JSON.parse(fs.readFileSync(depsFilePath));
const problems = [];
const tasks = [];
const visited = new Set(); // Tracks projects already processed
// Helper nested function to recursively collect dependencies via depth-first traversal
function collectDependencies(projectPath, collected = []) {
// TODO FIXME!!! Handle cyclic dependencies...
const project = deps.find((entry) => entry.project_path === projectPath);
if (!project) {
problems.push(
`Missing dependency information for project: ${projectPath}`
);
return collected;
}
(project.project_dependencies || []).forEach((dependency) => {
collectDependencies(dependency, collected); // FIX:
if (!collected.includes(dependency)) {
collected.push(dependency);
}
});
if (!collected.includes(projectPath)) {
collected.push(projectPath);
}
return collected;
}
// Build tasks for each project
deps.forEach((project) => {
const { project_path } = project;
// Collect all dependencies, including transitive ones
const allDependencies = collectDependencies(project_path);
tasks.push({
project_path,
project_build_tasks: allDependencies,
num_tasks: allDependencies.length,
});
});
// Write tasks.json
fs.writeFileSync(tasksFilePath, JSON.stringify(tasks, null, 2));
fs.appendFileSync(problemsFilePath, problems.join("\n") + "\n");
console.log("Build tasks generation complete. Results saved to tasks.json.");
}
module.exports = { buildDependencies, makeBuildTasks };