@qooxdoo/framework
Version:
The JS Framework for Coders
1,577 lines (1,393 loc) • 60.4 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2017 Zenesis Ltd
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* John Spackman (john.spackman@zenesis.com, @johnspackman)
************************************************************************ */
const process = require("process");
const Gauge = require("gauge");
const semver = require("semver");
const path = require("upath");
const consoleControl = require("console-control-strings");
const fs = qx.tool.utils.Promisify.fs;
require("app-module-path").addPath(process.cwd() + "/node_modules");
/**
* Handles compilation of the project
* @ignore(setImmediate)
*/
qx.Class.define("qx.tool.cli.commands.Compile", {
extend: qx.tool.cli.commands.Command,
statics: {
YARGS_BUILDER: {
target: {
alias: "t",
describe:
"Set the target type: source or build or class name. Default is first target in config file",
requiresArg: true,
type: "string"
},
"output-path-prefix": {
describe:
"Sets a prefix for the output path of the target - used to compile a version into a non-standard directory",
type: "string"
},
download: {
alias: "d",
describe: "Whether to automatically download missing libraries",
type: "boolean",
default: true
},
locale: {
alias: "l",
describe: "Compile for a given locale",
nargs: 1,
requiresArg: true,
type: "string",
array: true
},
"update-po-files": {
alias: "u",
describe:
"enables detection of translations and writing them out into .po files",
type: "boolean",
default: false
},
"library-po": {
describe: "The policy for updating translations in libraries",
type: ["ignore", "untranslated", "all"],
default: "ignore"
},
"write-all-translations": {
describe:
"enables output of all translations, not just those that are explicitly referenced",
type: "boolean"
},
"app-class": {
describe: "sets the application class",
nargs: 1,
requiresArg: true,
type: "string"
},
"app-theme": {
describe: "sets the theme class for the current application",
nargs: 1,
requiresArg: true,
type: "string"
},
"app-name": {
describe: "sets the name of the current application",
nargs: 1,
requiresArg: true,
type: "string"
},
"app-group": {
describe: "which application groups to compile (defaults to all)",
nargs: 1,
requiresArg: true,
type: "string"
},
"local-fonts": {
describe: "whether to prefer local font files over CDN",
type: "boolean"
},
watch: {
describe: "enables watching for changes and continuous compilation",
type: "boolean",
alias: "w"
},
"watch-debug": {
describe: "enables debug messages for watching",
type: "boolean"
},
"machine-readable": {
alias: "M",
describe: "output compiler messages in machine-readable format",
type: "boolean"
},
minify: {
alias: "m",
describe: "disables minification (build targets only)",
choices: ["off", "minify", "mangle", "beautify"],
default: "mangle"
},
"mangle-privates": {
describe: "Whether to mangle private variables",
default: true,
type: "boolean"
},
"save-source-in-map": {
describe: "Saves the source code in the map file (build target only)",
type: "boolean",
default: false
},
"source-map-relative-paths": {
describe:
"If true, the source file will be saved in the map file if the target supports it. Can be overridden on a per application basis.",
type: "boolean",
default: false
},
"save-unminified": {
alias: "u",
describe:
"Saves a copy of the unminified version of output files (build target only)",
type: "boolean",
default: false
},
"inline-external-scripts": {
describe: "Inlines external Javascript",
type: "boolean"
},
erase: {
alias: "e",
describe:
"Enabled automatic deletion of the output directory when compiler version or environment variables change",
type: "boolean",
default: true
},
feedback: {
describe: "Shows gas-gauge feedback",
type: "boolean",
alias: "f"
},
typescript: {
alias: "T",
describe: "Outputs typescript definitions in qooxdoo.d.ts",
type: "boolean",
default: null
},
"add-created-at": {
describe: "Adds code to populate object's $$createdAt",
type: "boolean"
},
"verbose-created-at": {
describe: "Adds additional detail to $$createdAt",
type: "boolean"
},
clean: {
alias: "D",
describe: "Deletes the target dir before compile",
type: "boolean"
},
"warn-as-error": {
alias: "E",
describe: "Handle compiler warnings as error",
type: "boolean",
default: false
},
"write-library-info": {
alias: "I",
describe: "Write library information to the script, for reflection",
type: "boolean",
default: true
},
"write-compile-info": {
describe:
"Write application summary information to the script, used mostly for unit tests",
type: "boolean",
default: false
},
bundling: {
alias: "b",
describe: "Whether bundling is enabled",
type: "boolean",
default: true
}
},
getYargsCommand() {
return {
command: "compile",
describe: "compiles the current application, using compile.json",
builder: qx.tool.cli.commands.Compile.YARGS_BUILDER
};
}
},
events: {
/**
* Fired when application writing starts
*/
writingApplications: "qx.event.type.Event",
/**
* Fired when writing of single application starts; data is an object containing:
* maker {qx.tool.compiler.makers.Maker}
* target {qx.tool.compiler.targets.Target}
* appMeta {qx.tool.compiler.targets.meta.ApplicationMeta}
*/
writingApplication: "qx.event.type.Data",
/**
* Fired when writing of single application is complete; data is an object containing:
* maker {qx.tool.compiler.makers.Maker}
* target {qx.tool.compiler.targets.Target}
* appMeta {qx.tool.compiler.targets.meta.ApplicationMeta}
*
* Note that target.getAppMeta() will return null after this event has been fired
*/
writtenApplication: "qx.event.type.Data",
/**
* Fired after writing of all applications; data is an object containing an array,
* each of which has previously been passed with `writeApplication`:
* maker {qx.tool.compiler.makers.Maker}
* target {qx.tool.compiler.targets.Target}
* appMeta {qx.tool.compiler.targets.meta.ApplicationMeta}
*
* Note that target.getAppMeta() will return null after this event has been fired
*/
writtenApplications: "qx.event.type.Data",
/**
* Fired after writing of all meta data; data is an object containing:
* maker {qx.tool.compiler.makers.Maker}
*/
writtenMetaData: "qx.event.type.Data",
/**
* Fired when a class is about to be compiled.
*
* The event data is an object with the following properties:
*
* dbClassInfo: {Object} the newly populated class info
* oldDbClassInfo: {Object} the previous populated class info
* classFile - {ClassFile} the qx.tool.compiler.ClassFile instance
*/
compilingClass: "qx.event.type.Data",
/**
* Fired when a class is compiled.
*
* The event data is an object with the following properties:
* dbClassInfo: {Object} the newly populated class info
* oldDbClassInfo: {Object} the previous populated class info
* classFile - {ClassFile} the qx.tool.compiler.ClassFile instance
*/
compiledClass: "qx.event.type.Data",
/**
* Fired when the database is been saved
*
* data:
* database: {Object} the database to save
*/
saveDatabase: "qx.event.type.Data",
/**
* Fired after all enviroment data is collected
*
* The event data is an object with the following properties:
* application {qx.tool.compiler.app.Application} the app
* enviroment: {Object} enviroment data
*/
checkEnvironment: "qx.event.type.Data",
/**
* Fired when making of apps begins
*/
making: "qx.event.type.Event",
/**
* Fired when making of apps is done.
*/
made: "qx.event.type.Event",
/**
* Fired when minification begins.
*
* The event data is an object with the following properties:
* application {qx.tool.compiler.app.Application} the app being minified
* part: {String} the part being minified
* filename: {String} the part filename
*/
minifyingApplication: "qx.event.type.Data",
/**
* Fired when minification is done.
*
* The event data is an object with the following properties:
* application {qx.tool.compiler.app.Application} the app being minified
* part: {String} the part being minified
* filename: {String} the part filename
*/
minifiedApplication: "qx.event.type.Data"
},
members: {
__gauge: null,
__makers: null,
__libraries: null,
__outputDirWasCreated: false,
/** @type{String} the path to the root of the meta files by classname */
__metaDir: null,
/** @type{Boolean} whether the typescript output is enabled */
__typescriptEnabled: false,
/** @type{String} the name of the typescript file to generate */
__typescriptFile: null,
/*
* @Override
*/
async process() {
await super.process();
let configDb = await qx.tool.cli.ConfigDb.getInstance();
if (this.argv["feedback"] === null) {
this.argv["feedback"] = configDb.db("qx.default.feedback", true);
}
if (this.argv.verbose) {
console.log(`
Compiler: v${this.getCompilerVersion()} in ${require.main.filename}
Framework: v${await this.getQxVersion()} in ${await this.getQxPath()}`);
}
if (this.argv["machine-readable"]) {
qx.tool.compiler.Console.getInstance().setMachineReadable(true);
} else {
let configDb = await qx.tool.cli.ConfigDb.getInstance();
let color = configDb.db("qx.default.color", null);
if (color) {
let colorOn = consoleControl.color(color.split(" "));
process.stdout.write(colorOn + consoleControl.eraseLine());
let colorReset = consoleControl.color("reset");
process.on("exit", () =>
process.stdout.write(colorReset + consoleControl.eraseLine())
);
let Console = qx.tool.compiler.Console.getInstance();
Console.setColorOn(colorOn);
}
if (this.argv["feedback"]) {
var themes = require("gauge/themes");
var ourTheme = themes.newTheme(
themes({ hasUnicode: true, hasColor: true })
);
let colorOn = qx.tool.compiler.Console.getInstance().getColorOn();
ourTheme.preProgressbar = colorOn + ourTheme.preProgressbar;
ourTheme.preSubsection = colorOn + ourTheme.preSubsection;
ourTheme.progressbarTheme.postComplete += colorOn;
ourTheme.progressbarTheme.postRemaining += colorOn;
this.__gauge = new Gauge();
this.__gauge.setTheme(ourTheme);
this.__gauge.show("Compiling", 0);
const TYPES = {
error: "ERROR",
warning: "Warning"
};
qx.tool.compiler.Console.getInstance().setWriter((str, msgId) => {
msgId = qx.tool.compiler.Console.MESSAGE_IDS[msgId];
if (!msgId || msgId.type !== "message") {
this.__gauge.hide();
qx.tool.compiler.Console.log(
colorOn + TYPES[(msgId || {}).type || "error"] + ": " + str
);
this.__gauge.show();
} else {
this.__gauge.show(colorOn + str);
}
});
}
}
if (this.__gauge) {
this.addListener("writingApplications", () =>
this.__gauge.show("Writing Applications", 0)
);
this.addListener("writtenApplications", () =>
this.__gauge.show("Writing Applications", 1)
);
this.addListener("writingApplication", evt =>
this.__gauge.pulse(
"Writing Application " +
evt.getData().appMeta.getApplication().getName()
)
);
this.addListener("compilingClass", evt =>
this.__gauge.pulse(
"Compiling " + evt.getData().classFile.getClassName()
)
);
this.addListener("minifyingApplication", evt =>
this.__gauge.pulse(
"Minifying " +
evt.getData().application.getName() +
" " +
evt.getData().filename
)
);
} else {
this.addListener("writingApplication", evt => {
let appInfo = evt.getData();
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.writingApplication",
appInfo.appMeta.getApplication().getName()
);
});
this.addListener("minifyingApplication", evt =>
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.minifyingApplication",
evt.getData().application.getName(),
evt.getData().filename
)
);
}
this.addListener("making", evt => {
if (this.__gauge) {
this.__gauge.show("Compiling", 1);
} else {
qx.tool.compiler.Console.print("qx.tool.cli.compile.makeBegins");
}
});
this.addListener("made", evt => {
if (this.__gauge) {
this.__gauge.show("Compiling", 1);
} else {
qx.tool.compiler.Console.print("qx.tool.cli.compile.makeEnds");
}
});
this.addListener("writtenApplications", e => {
if (this.argv.verbose) {
qx.tool.compiler.Console.log(
"\nCompleted all applications, libraries used are:"
);
Object.values(this.__libraries).forEach(lib =>
qx.tool.compiler.Console.log(
` ${lib.getNamespace()} (${lib.getRootDir()})`
)
);
}
});
await this._loadConfigAndStartMaking();
if (!this.argv.watch) {
let success = this.__makers.every(maker => maker.getSuccess());
let hasWarnings = this.__makers.every(maker => maker.getHasWarnings());
if (success && hasWarnings && this.argv.warnAsError) {
success = false;
}
if (
!this.argv.deploying &&
!this.argv["machine-readable"] &&
this.argv["feedback"] &&
this.__outputDirWasCreated &&
this.argv.target === "build"
) {
qx.tool.compiler.Console.warn(
" *******************************************************************************************\n" +
" ** **\n" +
" ** Your compilation will include temporary files that are only necessary during **\n" +
" ** development; these files speed up the compilation, but take up space that you would **\n" +
" ** probably not want to put on a production server. **\n" +
" ** **\n" +
" ** When you are ready to deploy, try running `qx deploy` to get a minimised version **\n" +
" ** **\n" +
" *******************************************************************************************"
);
}
process.exitCode = success ? 0 : 1;
}
},
/**
* Loads the configuration and starts the make
*
* @return {Boolean} true if all makers succeeded
*/
async _loadConfigAndStartMaking() {
if (
!this.getCompilerApi().compileJsonExists() &&
!qx.tool.cli.Cli.getInstance().compileJsExists()
) {
qx.tool.compiler.Console.error(
"Cannot find either compile.json nor compile.js"
);
process.exit(1);
}
var config = this.getCompilerApi().getConfiguration();
var makers = (this.__makers = await this.createMakersFromConfig(config));
if (!makers || !makers.length) {
throw new qx.tool.utils.Utils.UserError(
"Error: Cannot find anything to make"
);
}
let countMaking = 0;
const collateDispatchEvent = evt => {
if (countMaking == 1) {
this.dispatchEvent(evt.clone());
}
};
let isFirstWatcher = true;
await qx.Promise.all(
makers.map(async maker => {
var analyser = maker.getAnalyser();
let cfg = await qx.tool.cli.ConfigDb.getInstance();
analyser.setWritePoLineNumbers(
cfg.db("qx.translation.strictPoCompatibility", false)
);
if (!(await fs.existsAsync(maker.getOutputDir()))) {
this.__outputDirWasCreated = true;
}
if (this.argv["clean"]) {
await maker.eraseOutputDir();
await qx.tool.utils.files.Utils.safeUnlink(
analyser.getDbFilename()
);
await qx.tool.utils.files.Utils.safeUnlink(
analyser.getResDbFilename()
);
}
if (config.ignores) {
analyser.setIgnores(config.ignores);
}
var target = maker.getTarget();
analyser.addListener("compilingClass", e =>
this.dispatchEvent(e.clone())
);
analyser.addListener("compiledClass", e =>
this.dispatchEvent(e.clone())
);
analyser.addListener("saveDatabase", e =>
this.dispatchEvent(e.clone())
);
target.addListener("checkEnvironment", e =>
this.dispatchEvent(e.clone())
);
let appInfos = [];
target.addListener("writingApplication", async () => {
let appInfo = {
maker,
target,
appMeta: target.getAppMeta()
};
appInfos.push(appInfo);
await this.fireDataEventAsync("writingApplication", appInfo);
});
target.addListener("writtenApplication", async () => {
await this.fireDataEventAsync("writtenApplication", {
maker,
target,
appMeta: target.getAppMeta()
});
});
maker.addListener("writingApplications", collateDispatchEvent);
maker.addListener("writtenApplications", async () => {
await this.fireDataEventAsync("writtenApplications", appInfos);
});
if (target instanceof qx.tool.compiler.targets.BuildTarget) {
target.addListener("minifyingApplication", e =>
this.dispatchEvent(e.clone())
);
target.addListener("minifiedApplication", e =>
this.dispatchEvent(e.clone())
);
}
let stat =
await qx.tool.utils.files.Utils.safeStat("source/index.html");
if (stat) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.legacyFiles",
"source/index.html"
);
}
// Simple one of make
if (!this.argv.watch) {
maker.addListener("making", () => {
countMaking++;
if (countMaking == 1) {
this.fireEvent("making");
}
});
maker.addListener("made", () => {
countMaking--;
if (countMaking == 0) {
this.fireEvent("made");
}
});
return await maker.make();
}
// Continuous make
let watch = new qx.tool.cli.Watch(maker);
config.applications.forEach(appConfig => {
if (appConfig.runWhenWatching) {
watch.setRunWhenWatching(
appConfig.name,
appConfig.runWhenWatching
);
}
});
if (this.argv["watch-debug"]) {
watch.setDebug(true);
}
watch.addListener("making", () => {
countMaking++;
if (countMaking == 1) {
this.fireEvent("making");
}
});
watch.addListener("made", () => {
countMaking--;
if (countMaking == 0) {
this.fireEvent("made");
}
});
watch.addListener("configChanged", async () => {
await watch.stop();
setImmediate(() => this._loadConfigAndStartMaking());
});
let arr = [this._compileJsFilename, this._compileJsonFilename].filter(
str => Boolean(str)
);
watch.setConfigFilenames(arr);
if (
target instanceof qx.tool.compiler.targets.SourceTarget &&
isFirstWatcher
) {
isFirstWatcher = false;
try {
await this.__attachTypescriptWatcher(watch);
} catch (ex) {
qx.tool.compiler.Console.error(ex);
}
}
return watch.start();
})
);
if (!this.argv.watch) {
try {
await this.__attachTypescriptWatcher(null);
} catch (ex) {
qx.tool.compiler.Console.error(ex);
}
}
},
async __attachTypescriptWatcher(watch) {
let classFiles = [];
// Scans a directory recursively to find all .js files
const scanImpl = async filename => {
let basename = path.basename(filename);
let stat = await fs.promises.stat(filename);
if (stat.isFile() && basename.match(/\.js$/)) {
classFiles.push(filename);
} else if (
stat.isDirectory() &&
(basename == "." || basename[0] != ".")
) {
let files = await fs.promises.readdir(filename);
for (let i = 0; i < files.length; i++) {
let subname = path.join(filename, files[i]);
await scanImpl(subname);
}
}
};
// Do the initial scan
qx.tool.compiler.Console.info(`Loading meta data ...`);
let metaDb = new qx.tool.compiler.MetaDatabase().set({
rootDir: this.__metaDir
});
await metaDb.load();
// Scan all library directories
metaDb.getDatabase().libraries = {};
for (let lib of Object.values(this.__libraries)) {
let dir = path.join(lib.getRootDir(), lib.getSourcePath());
metaDb.getDatabase().libraries[lib.getNamespace()] = {
sourceDir: dir
};
await scanImpl(dir);
}
for (let filename of classFiles) {
await metaDb.addFile(filename, !!this.argv.clean);
}
await metaDb.reparseAll();
await metaDb.save();
await this.fireDataEventAsync("writtenMetaData", metaDb);
// Do the inital write
let tsWriter = null;
if (this.__typescriptEnabled) {
qx.tool.compiler.Console.info(`Generating typescript output ...`);
tsWriter = new qx.tool.compiler.targets.TypeScriptWriter(metaDb);
if (this.__typescriptFile) {
tsWriter.setOutputTo(this.__typescriptFile);
} else {
tsWriter.setOutputTo(path.join(this.__metaDir, "..", "qooxdoo.d.ts"));
}
await tsWriter.process();
}
if (!watch) {
return;
}
// Redo the files that change, as they change
classFiles = {};
let debounce = new qx.tool.utils.Debounce(async () => {
let filesParsed = false;
qx.tool.compiler.Console.info(`Loading meta data ...`);
let addFilePromises = [];
while (true) {
let arr = Object.keys(classFiles);
if (arr.length == 0) {
break;
}
filesParsed = true;
classFiles = {};
arr.forEach(filename => {
if (this.argv.verbose) {
qx.tool.compiler.Console.info(
`Processing meta for ${filename} ...`
);
}
addFilePromises.push(metaDb.addFile(filename));
});
}
if (filesParsed) {
qx.tool.compiler.Console.info(`Generating typescript output ...`);
await Promise.all(addFilePromises);
await metaDb.reparseAll();
await metaDb.save();
if (this.__typescriptEnabled) {
await tsWriter.process();
}
}
});
// Watch for changes
watch.addListener("fileChanged", evt => {
let data = evt.getData();
if (data.fileType == "source") {
let filename = data.library.getFilename(data.filename);
classFiles[filename] = true;
debounce.run();
}
});
},
/**
* Processes the configuration from a JSON data structure and creates a Maker
*
* @param data {Map}
* @return {qx.tool.compiler.makers.Maker}
*/
async createMakersFromConfig(data) {
const Console = qx.tool.compiler.Console.getInstance();
var t = this;
if (data.babelOptions) {
if (!data?.babel?.options) {
data.babel = data.babel || {};
data.babel.options = data.babelOptions;
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.deprecatedBabelOptions"
);
} else {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.deprecatedBabelOptionsConflicting"
);
}
delete data.babelOptions;
}
if (qx.lang.Type.isBoolean(data?.meta?.typescript)) {
this.__typescriptEnabled = data.meta.typescript;
} else if (qx.lang.Type.isString(data?.meta?.typescript)) {
this.__typescriptEnabled = true;
this.__typescriptFile = path.relative(
process.cwd(),
path.resolve(data?.meta?.typescript)
);
}
if (qx.lang.Type.isBoolean(this.argv.typescript)) {
this.__typescriptEnabled = this.argv.typescript;
}
var argvAppNames = null;
if (t.argv["app-name"]) {
argvAppNames = {};
t.argv["app-name"]
.split(",")
.forEach(name => (argvAppNames[name] = true));
}
var argvAppGroups = null;
if (t.argv["app-group"]) {
argvAppGroups = {};
t.argv["app-group"]
.split(",")
.forEach(name => (argvAppGroups[name] = true));
}
/*
* Calculate the the list of targets and applications; this is a many to many list, where an
* application can be compiled for many targets, and each target has many applications.
*
* Each target configuration is updated to have `appConfigs[]` and each application configuration
* is updated to have `targetConfigs[]`.
*/
data.targets.forEach(
(targetConfig, index) => (targetConfig.index = index)
);
let targetConfigs = [];
let defaultTargetConfig = null;
data.targets.forEach(targetConfig => {
if (targetConfig.type === this.getTargetType()) {
if (
!targetConfig["application-names"] &&
!targetConfig["application-types"]
) {
if (defaultTargetConfig) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.multipleDefaultTargets"
);
} else {
defaultTargetConfig = targetConfig;
}
} else {
targetConfigs.push(targetConfig);
}
}
});
let allAppNames = {};
data.applications.forEach((appConfig, index) => {
if (appConfig.name) {
if (allAppNames[appConfig.name]) {
throw new qx.tool.utils.Utils.UserError(
`Multiple applications with the same name '${appConfig.name}'`
);
}
allAppNames[appConfig.name] = appConfig;
}
if (appConfig.group) {
if (typeof appConfig.group == "string") {
appConfig.group = [appConfig.group];
}
}
appConfig.index = index;
let appType = appConfig.type || "browser";
let appTargetConfigs = targetConfigs.filter(targetConfig => {
let appTypes = targetConfig["application-types"];
if (appTypes && !qx.lang.Array.contains(appTypes, appType)) {
return false;
}
let appNames = targetConfig["application-names"];
if (
appConfig.name &&
appNames &&
!qx.lang.Array.contains(appNames, appConfig.name)
) {
return false;
}
return true;
});
if (appTargetConfigs.length == 0) {
if (defaultTargetConfig) {
appTargetConfigs = [defaultTargetConfig];
} else {
throw new qx.tool.utils.Utils.UserError(
`Cannot find any suitable targets for application #${index} (named ${
appConfig.name || "unnamed"
})`
);
}
}
appTargetConfigs.forEach(targetConfig => {
if (!targetConfig.appConfigs) {
targetConfig.appConfigs = [];
}
targetConfig.appConfigs.push(appConfig);
if (!appConfig.targetConfigs) {
appConfig.targetConfigs = [];
}
appConfig.targetConfigs.push(targetConfig);
});
});
if (defaultTargetConfig && defaultTargetConfig.appConfigs) {
targetConfigs.push(defaultTargetConfig);
}
let libraries = (this.__libraries = {});
let librariesArray = [];
for (let libPath of data.libraries) {
let library = await qx.tool.compiler.app.Library.createLibrary(libPath);
libraries[library.getNamespace()] = library;
librariesArray.push(library);
}
// Search for Qooxdoo library if not already provided
var qxLib = libraries["qx"];
if (!qxLib) {
let qxPath = await qx.tool.config.Utils.getQxPath();
var library = await qx.tool.compiler.app.Library.createLibrary(qxPath);
libraries[library.getNamespace()] = library;
librariesArray.push(library);
qxLib = libraries["qx"];
}
if (this.argv.verbose) {
Console.log("Qooxdoo found in " + qxLib.getRootDir());
}
let errors = await this.__checkDependencies(
Object.values(libraries),
data.packages
);
if (errors.length > 0) {
if (this.argv.warnAsError) {
throw new qx.tool.utils.Utils.UserError(errors.join("\n"));
} else {
qx.tool.compiler.Console.log(errors.join("\n"));
}
}
/*
* Figure out which will be the default application; this will need some work for situations
* where there are multiple browser based targets
*/
targetConfigs.forEach(targetConfig => {
let hasExplicitDefaultApp = false;
targetConfig.defaultAppConfig = null;
if (targetConfig.appConfigs) {
targetConfig.appConfigs.forEach(appConfig => {
if (appConfig.type && appConfig.type != "browser") {
return;
}
let setDefault;
if (appConfig.writeIndexHtmlToRoot !== undefined) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.deprecatedCompileSeeOther",
"application.writeIndexHtmlToRoot",
"application.default"
);
setDefault = appConfig.writeIndexHtmlToRoot;
} else if (appConfig["default"] !== undefined) {
setDefault = appConfig["default"];
}
if (setDefault !== undefined) {
if (setDefault) {
if (hasExplicitDefaultApp) {
throw new qx.tool.utils.Utils.UserError(
"Error: Can only set one application to be the default application!"
);
}
hasExplicitDefaultApp = true;
targetConfig.defaultAppConfig = appConfig;
}
} else if (!targetConfig.defaultAppConfig) {
targetConfig.defaultAppConfig = appConfig;
}
});
if (!hasExplicitDefaultApp && targetConfig.appConfigs.length > 1) {
targetConfig.defaultAppConfig = targetConfig.appConfigs[0];
}
}
});
/*
* There is still only one target per maker, so convert our list of targetConfigs into an array of makers
*/
let targetOutputPaths = {};
let makers = [];
this.__metaDir = data.meta?.output;
if (!this.__metaDir) {
this.__metaDir = path.relative(
process.cwd(),
path.resolve(targetConfigs[0].outputPath, "../meta")
);
}
targetConfigs.forEach(targetConfig => {
if (!targetConfig.appConfigs) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.unusedTarget",
targetConfig.type,
targetConfig.index
);
return;
}
let appConfigs = targetConfig.appConfigs.filter(appConfig => {
if (argvAppGroups) {
let groups = appConfig.group || [];
if (!groups.find(groupName => !!argvAppGroups[groupName])) {
return false;
}
}
if (argvAppNames && appConfig.name) {
if (!argvAppNames[appConfig.name]) {
return false;
}
}
return true;
});
if (!appConfigs.length) {
return;
}
var outputPath = targetConfig.outputPath;
if (this.argv.outputPathPrefix) {
outputPath = path.join(this.argv.outputPathPrefix, outputPath);
}
if (!outputPath) {
throw new qx.tool.utils.Utils.UserError(
"Missing output-path for target " + targetConfig.type
);
}
let absOutputPath = path.resolve(outputPath);
if (targetOutputPaths[absOutputPath]) {
throw new qx.tool.utils.Utils.UserError(
`Multiple output targets share the same target directory ${outputPath} - each target output must be unique`
);
}
targetOutputPaths[absOutputPath] = true;
var maker = new qx.tool.compiler.makers.AppMaker();
if (!this.argv["erase"]) {
maker.setNoErase(true);
}
var targetClass = targetConfig.targetClass
? this.resolveTargetClass(targetConfig.targetClass)
: null;
if (!targetClass && targetConfig.type) {
targetClass = this.resolveTargetClass(targetConfig.type);
}
if (!targetClass) {
throw new qx.tool.utils.Utils.UserError(
"Cannot find target class: " +
(targetConfig.targetClass || targetConfig.type)
);
}
/* eslint-disable new-cap */
var target = new targetClass(outputPath);
/* eslint-enable new-cap */
if (targetConfig.uri) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.deprecatedUri",
"target.uri",
targetConfig.uri
);
}
if (targetConfig.addTimestampsToUrls !== undefined) {
target.setAddTimestampsToUrls(targetConfig.addTimestampsToUrls);
} else {
target.setAddTimestampsToUrls(
target instanceof qx.tool.compiler.targets.BuildTarget
);
}
if (targetConfig.writeCompileInfo || this.argv.writeCompileInfo) {
target.setWriteCompileInfo(true);
}
if (data.i18nAsParts) {
target.setI18nAsParts(true);
}
target.setWriteLibraryInfo(this.argv.writeLibraryInfo);
target.setUpdatePoFiles(this.argv.updatePoFiles);
target.setLibraryPoPolicy(this.argv.libraryPo);
let fontsConfig = targetConfig.fonts || {};
let preferLocalFonts = true;
if (this.argv.localFonts !== undefined) {
preferLocalFonts = this.argv.localFonts;
} else if (fontsConfig.local !== undefined) {
preferLocalFonts = fontsConfig.local;
}
target.setPreferLocalFonts(preferLocalFonts);
if (fontsConfig.fontTypes !== undefined) {
target.setFontTypes(fontsConfig.fontTypes);
}
// Take the command line for `minify` as most precedent only if provided
var minify;
if (process.argv.indexOf("--minify") > -1) {
minify = t.argv["minify"];
}
minify = minify || targetConfig["minify"] || t.argv["minify"];
if (typeof minify == "boolean") {
minify = minify ? "minify" : "off";
}
if (!minify) {
minify = "mangle";
}
if (typeof target.setMinify == "function") {
target.setMinify(minify);
}
function chooseValue(...args) {
for (let i = 0; i < args.length; i++) {
if (args[i] !== undefined) {
return args[i];
}
}
return undefined;
}
// Take the command line for `saveSourceInMap` as most precedent only if provided
var saveSourceInMap = chooseValue(
targetConfig["save-source-in-map"],
t.argv["saveSourceInMap"]
);
if (
typeof saveSourceInMap == "boolean" &&
typeof target.setSaveSourceInMap == "function"
) {
target.setSaveSourceInMap(saveSourceInMap);
}
var sourceMapRelativePaths = chooseValue(
targetConfig["source-map-relative-paths"],
t.argv["sourceMapRelativePaths"]
);
if (
typeof sourceMapRelativePaths == "boolean" &&
typeof target.setSourceMapRelativePaths == "function"
) {
target.setSourceMapRelativePaths(sourceMapRelativePaths);
}
var saveUnminified = chooseValue(
targetConfig["save-unminified"],
t.argv["save-unminified"]
);
if (
typeof saveUnminified == "boolean" &&
typeof target.setSaveUnminified == "function"
) {
target.setSaveUnminified(saveUnminified);
}
var inlineExternal = chooseValue(
targetConfig["inline-external-scripts"],
t.argv["inline-external-scripts"]
);
if (typeof inlineExternal == "boolean") {
target.setInlineExternalScripts(inlineExternal);
} else if (target instanceof qx.tool.compiler.targets.BuildTarget) {
target.setInlineExternalScripts(true);
}
var deployDir = targetConfig["deployPath"];
if (deployDir && typeof target.setDeployDir == "function") {
target.setDeployDir(deployDir);
}
var deployMap = targetConfig["deploy-source-maps"];
if (
typeof deployMap == "boolean" &&
typeof target.setDeployDir == "function"
) {
target.setDeployMap(deployMap);
}
maker.setTarget(target);
var manglePrivates = chooseValue(
targetConfig["mangle-privates"],
t.argv["mangle-privates"]
);
if (typeof manglePrivates == "string") {
maker.getAnalyser().setManglePrivates(manglePrivates);
} else if (typeof manglePrivates == "boolean") {
if (manglePrivates) {
maker
.getAnalyser()
.setManglePrivates(
target instanceof qx.tool.compiler.targets.BuildTarget
? "unreadable"
: "readable"
);
} else {
maker.getAnalyser().setManglePrivates("off");
}
}
if (targetConfig["application-types"]) {
maker
.getAnalyser()
.setApplicationTypes(targetConfig["application-types"]);
}
if (targetConfig["proxySourcePath"]) {
maker
.getAnalyser()
.setProxySourcePath(targetConfig["proxySourcePath"]);
}
maker.setLocales(data.locales || ["en"]);
if (data.writeAllTranslations) {
maker.setWriteAllTranslations(data.writeAllTranslations);
}
if (typeof targetConfig.typescript == "string") {
Console.warn(
"The 'typescript' property inside a target definition is deprecated - please see top level 'meta.typescript' property"
);
if (this.__typescriptFile) {
Console.warn(
"Multiple conflicting locations for the Typescript output - choosing to write to " +
this.__typescriptFile +
" and NOT " +
targetConfig.typescript
);
} else {
this.__typescriptEnabled = true;
this.__typescriptFile = path.relative(
process.cwd(),
path.resolve(targetConfig.typescript)
);
}
}
if (data.environment) {
maker.setEnvironment(data.environment);
}
/*
Libraries have to be added first because there is qx library
which includes a framework version
*/
for (let library of librariesArray) {
maker.getAnalyser().addLibrary(library);
}
let targetEnvironment = {
"qx.version": maker.getAnalyser().getQooxdooVersion(),
"qx.compiler.targetType": target.getType(),
"qx.compiler.outputDir": target.getOutputDir(),
"qx.target.privateArtifacts": !!data["private-artifacts"]
};
if (data["private-artifacts"]) {
target.setPrivateArtifacts(true);
}
qx.lang.Object.mergeWith(
targetEnvironment,
targetConfig.environment,
false
);
target.setEnvironment(targetEnvironment);
if (targetConfig.preserveEnvironment) {
target.setPreserveEnvironment(targetConfig.preserveEnvironment);
}
if (data["path-mappings"]) {
for (var from in data["path-mappings"]) {
var to = data["path-mappings"][from];
target.addPathMapping(from, to);
}
}
function mergeArray(dest, ...srcs) {
srcs.forEach(function (src) {
if (src) {
src.forEach(function (elem) {
if (!qx.lang.Array.contains(dest, src)) {
dest.push(elem);
}
});
}
});
return dest;
}
let babelConfig = qx.lang.Object.clone(data.babel || {}, true);
babelConfig.options = babelConfig.options || {};
qx.lang.Object.mergeWith(
babelConfig.options,
targetConfig.babelOptions || {}
);
maker.getAnalyser().setBabelConfig(babelConfig);
let browserifyConfig = qx.lang.Object.clone(data.browserify || {}, true);
browserifyConfig.options = browserifyConfig.options || {};
qx.lang.Object.mergeWith(
browserifyConfig.options,
targetConfig.browserifyOptions || {}
);
maker.getAnalyser().setBrowserifyConfig(browserifyConfig);
var addCreatedAt =
targetConfig["addCreatedAt"] || t.argv["addCreatedAt"];
if (addCreatedAt) {
maker.getAnalyser().setAddCreatedAt(true);
}
const verboseCreatedAt =
targetConfig["verboseCreatedAt"] || t.argv["verboseCreatedAt"];
if (verboseCreatedAt) {
maker.getAnalyser().setVerboseCreatedAt(true);
}
let allApplicationTypes = {};
appConfigs.forEach(appConfig => {
var app = (appConfig.app = new qx.tool.compiler.app.Application(
appConfig["class"]
));
app.setTemplatePath(qx.tool.utils.Utils.getTemplateDir());
[
"type",
"theme",
"name",
"environment",
"outputPath",
"bootPath",
"loaderTemplate",
"publish",
"deploy",
"standalone",
"localModules"
].forEach(name => {
if (appConfig[name] !== undefined) {
var fname = "set" + qx.lang.String.firstUp(name);
app[fname](appConfig[name]);
}
});
allApplicationTypes[app.getType()] = true;
if (appConfig.uri) {
qx.tool.compiler.Console.print(
"qx.tool.cli.compile.deprecatedUri",
"application.uri",
appConfig.uri
);
}
if (appConfig.title) {
app.setTitle(appConfig.title);
}
if (appConfig.description) {
app.setDescription(appConfig.description);
}
appConfig.localModules = appConfig.localModules || {};
qx.lang.Object.mergeWith(
appConfig.localModules,
data.localModules || {},
false
);
if (!qx.lang.Object.isEmpty(appConfig.localModules)) {
app.setLocalModules(appConfig.localModules);
}
var parts = appConfig.parts || targetConfig.parts || data.parts;
if (parts) {
if (!parts.boot) {
throw new qx.tool.utils.Utils.UserError(
"Cannot determine a boot part for application " +
(appConfig.index + 1) +
" " +
(appConfig.name || "")
);
}
for (var partName in parts) {
var partData = parts[partName];
var include =
typeof partData.include == "string"
? [partData.include]
: partData.include;
var exclude =
typeof partData.exclude == "string"
? [partData.exclude]
: partData.exclude;
var part = new qx.tool.compiler.app.Part(
partName,
include,
exclude
).set({
combine: Boolean(partData.combine),
minify: Boolean(partData.minify)
});
app.addPart(part);
}
}
if (target.getType() == "source" && t.argv.bundling) {
var bundle = appConfig.bundle || targetConfig.bundle || data.bundle;
if (bundle) {
if (bundle.include) {
app.setBundleInclude(bundle.include);
}
if (bundle.exclude) {
app.setBundleExclude(bundle.exclude);
}
}
}
app.set({
exclude: mergeArray(
[],
data.exclude,
targetConfig.exclude,
appConfig.exclude
),
include: mergeArray(
[],
data.include,
targetConfig.include,
appConfig.include
)
});
maker.addApplication(app);
});
const CF = qx.tool.compiler.ClassFile;
let globalSymbols = [];
qx.lang.Array.append(globalSymbols, CF.QX_GLOBALS);
qx.lang.Array.append(globalSymbols, CF.COMMON_GLOBALS);
if (allApplicationTypes["browser"]) {
qx.lang.Array.append(globalSymbols, CF.BROWSER_GLOBALS);
}
if (allApplicationTypes["node"]) {
qx.lang.Array.append(globalSymbols, CF.NODE_GLOBALS);
}
if (allApplicationTypes["rhino"]) {
qx.lang.Array.append(globalSymbols, CF.RHINO_GLOBALS);
}
maker.getAnalyser().setGlobalSymbols(globalSymbols);
if (
targetConfig.defaultAppConfig &&
targetConfig.defaultAppConfig.app &&
(targetConfig.defaultAppConfig.type || "browser") === "browser"
) {
targetConfig.defaultAppConfig.app.setWriteIndexHtmlToRoot(true);
} else {
qx.tool.utils.files.Utils.safeUnlink(
target.getOutputDir() + "index.html"
);
}
const showMarkers = (classname, markers) => {
if (markers) {
markers.forEach(function (marker) {
var str = qx.tool.compiler.Console.decodeMarker(marker);
Console.warn(classname + ": " + str);
});
}
};
// Note - this will cause output multiple times,