diff --git a/.gitattributes b/.gitattributes
index afbe3b25f10..7e4c4dcb5ed 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1,3 +1,3 @@
* text=auto
-
+*.map text eol=lf
**/wwwroot/Scripts/* linguist-vendored
diff --git a/.github/workflows/assets_validation.yml b/.github/workflows/assets_validation.yml
index 06812e4026a..89a368def66 100644
--- a/.github/workflows/assets_validation.yml
+++ b/.github/workflows/assets_validation.yml
@@ -24,8 +24,10 @@ jobs:
- uses: actions/checkout@v4
- name: Rebuild packages
run: |
- npm install
- gulp rebuild
+ npm install -g corepack
+ corepack enable
+ yarn
+ yarn build -gr
- name: Check if git has changes
shell: pwsh
run: |
@@ -33,7 +35,7 @@ jobs:
if ($changes)
{
- Write-Output 'Please make sure to build the assets properly before pushing, see https://docs.orchardcore.net/en/latest/guides/gulp-pipeline/.'
+ Write-Output 'Please make sure to build the assets properly before pushing, see https://docs.orchardcore.net/en/latest/guides/assets-manager/.'
Write-Output 'The following files changed:'
Write-Output $changes
Write-Output 'You can also download the attached artifact to see the changes.'
diff --git a/.gitignore b/.gitignore
index 564c6bdfcfa..41db67331e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -193,6 +193,15 @@ nuget.exe
#exclude node modules
node_modules/
+#exclude yarn
+.yarn
+
+#exclude parcel
+.parcel-cache
+
+#exclude .map files
+*.map
+
wwwroot
**/Localization/**/*.po
!test/OrchardCore.Tests/Localization/**/*.po
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 00000000000..06654a0bcdf
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "all",
+ "tabWidth": 4,
+ "semi": true,
+ "printWidth": 180
+}
diff --git a/.scripts/assets-manager/.gitignore b/.scripts/assets-manager/.gitignore
new file mode 100644
index 00000000000..53c37a16608
--- /dev/null
+++ b/.scripts/assets-manager/.gitignore
@@ -0,0 +1 @@
+dist
\ No newline at end of file
diff --git a/.scripts/assets-manager/.parcelrc b/.scripts/assets-manager/.parcelrc
new file mode 100644
index 00000000000..a1c3cb196c2
--- /dev/null
+++ b/.scripts/assets-manager/.parcelrc
@@ -0,0 +1,12 @@
+{
+ "extends": "@parcel/config-default",
+ "resolvers": [
+ "@parcel/resolver-glob",
+ "..."
+ ],
+ "optimizers": {
+ "*.{js,mjs,cjs}": [
+ "@parcel/optimizer-terser"
+ ]
+ }
+}
diff --git a/.scripts/assets-manager/README.md b/.scripts/assets-manager/README.md
new file mode 100644
index 00000000000..b98b006ebea
--- /dev/null
+++ b/.scripts/assets-manager/README.md
@@ -0,0 +1 @@
+[Assets Manager documentation](../../src/docs/guides/assets-manager/README.md)
\ No newline at end of file
diff --git a/.scripts/assets-manager/assetGroups.mjs b/.scripts/assets-manager/assetGroups.mjs
new file mode 100644
index 00000000000..0593b7f9711
--- /dev/null
+++ b/.scripts/assets-manager/assetGroups.mjs
@@ -0,0 +1,43 @@
+import { glob } from "glob";
+import JSON5 from "json5";
+import fs from "fs-extra";
+import path from "path";
+
+import buildConfig from "./config.mjs";
+
+// Gets an object representation of all Assets.json in the solution.
+export default function getAllAssetGroups() {
+ var assetGroups = [];
+ getAssetsJsonPaths().forEach(function (assetManifestPath) {
+ var assetManifest = JSON5.parse(fs.readFileSync(assetManifestPath, "utf-8"));
+ assetManifest.forEach(function (assetGroup) {
+ resolveAssetGroupPaths(assetGroup, assetManifestPath);
+ assetGroups.push(assetGroup);
+ });
+ });
+ return assetGroups;
+}
+
+// Expands the paths to full paths.
+function resolveAssetGroupPaths(assetGroup, assetManifestPath) {
+ assetGroup.manifestPath = assetManifestPath;
+ assetGroup.basePath = path.dirname(assetManifestPath);
+ if (assetGroup.source) {
+ assetGroup.originalSource = assetGroup.source;
+ // since node_modules all resolve at the root (because of yarn)
+ if (assetGroup.source.startsWith("node_modules")) {
+ assetGroup.source = path.resolve(path.join(process.cwd(), assetGroup.source)).replace(/\\/g, "/");
+ } else {
+ assetGroup.source = path.resolve(path.join(assetGroup.basePath, assetGroup.source)).replace(/\\/g, "/");
+ }
+ // The source path relative to the root of the solution.
+ assetGroup.relativeSource = assetGroup.source.substring(process.cwd().length);
+ }
+ if (assetGroup.dest) {
+ assetGroup.dest = path.resolve(path.join(assetGroup.basePath, assetGroup.dest)).replace(/\\/g, "/");
+ }
+}
+
+function getAssetsJsonPaths() {
+ return glob.sync(buildConfig("assetsLookupGlob"), {});
+}
diff --git a/.scripts/assets-manager/build.mjs b/.scripts/assets-manager/build.mjs
new file mode 100755
index 00000000000..46b626c442d
--- /dev/null
+++ b/.scripts/assets-manager/build.mjs
@@ -0,0 +1,272 @@
+import JSON5 from "json5";
+import fs from "fs-extra";
+import path from "path";
+import chalk from "chalk";
+import concurrently from "concurrently";
+import { fileURLToPath } from "url";
+import parseArgs from "minimist";
+import _ from "lodash";
+
+import buildConfig from "./config.mjs";
+import clean from "./clean.mjs";
+import getAllAssetGroups from "./assetGroups.mjs";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// eslint-disable-next-line no-unused-vars
+let parsedArgs = parseArgs(process.argv.slice(2));
+
+let task = parsedArgs._[0];
+
+if (!task) {
+ task = "build";
+}
+console.log(chalk.green("Task: ", task));
+
+let groups = getAllAssetGroups();
+
+// Filter the packages if the user passes the -n cli flag
+let packagesStr = parsedArgs.n;
+if (packagesStr != undefined) {
+ const packages = packagesStr.split(" ");
+ if (packages.length > 0) {
+ console.log(chalk.yellow("Filtering groups based on packages: "), packages.join(", "));
+ groups = groups.filter((g) => packages.includes(g.name));
+ }
+}
+
+// Filter the tags if the user passes the -t cli flag
+let tagsStr = parsedArgs.t;
+if (tagsStr != undefined) {
+ const tags = tagsStr.split(" ");
+ if (tags.length > 0) {
+ console.log(chalk.yellow("Filtering groups based on tag: "), tags.join(", "));
+ groups = groups.filter((g) => {
+ if (Array.isArray(g.tags)) {
+ return _.intersection(tags, g.tags)?.length > 0;
+ } else {
+ return tags.includes(g.tags);
+ }
+ });
+ }
+}
+
+if (task === "watch" && tagsStr != undefined) {
+ console.log(chalk.yellow("Cannot watch based on tags, Specify packages to watch with -n cli flag"));
+ process.exit(0);
+}
+
+if (task === "watch" && packagesStr == undefined) {
+ console.log(chalk.yellow("Specify packages to watch with -n cli flag"));
+ process.exit(0);
+}
+
+if (task === "host" && tagsStr != undefined) {
+ console.log(chalk.yellow("Cannot host based on tags, Specify packages to host with -n cli flag"));
+ process.exit(0);
+}
+
+if (task === "host" && packagesStr == undefined) {
+ console.log(chalk.yellow("Specify packages to host with -n cli flag"));
+ process.exit(0);
+}
+
+// Filter the tags if the user passes the -b cli flag
+let bundleStr = parsedArgs.b;
+if (bundleStr != undefined) {
+ console.log(chalk.yellow("Filtering groups for orchardcore-bundle"));
+ groups = groups.filter((g) => g.bundleEntrypoint);
+}
+
+if (task === "clean") {
+ const returnCode = await clean(groups);
+ process.exit(returnCode);
+}
+
+if (task === "build" || task === "watch" || task === "host") {
+ // Group the parcel groups by bundle
+ const parcelGroupsByBundle = _.groupBy(
+ groups.filter((g) => g.bundleEntrypoint),
+ (g) => g.bundleEntrypoint,
+ );
+
+ // Remove those groups from the groups array as we will push new bundled groups instead.
+ groups = groups.filter((g) => !g.bundleEntrypoint);
+
+ if (parcelGroupsByBundle) {
+ const entries = [];
+ // loop through each bundle and generate the new combined group
+ _.forEach(parcelGroupsByBundle, (value, bundleEntrypoint) => {
+ // generate a file that will be the main import for this group
+ const bundleImports = _.map(value, (g) => `import "${g.relativeSource}";`).join("\n");
+ const importPath = path.join(__dirname, `dist/${bundleEntrypoint}.js`);
+ fs.outputFileSync(importPath, bundleImports, "utf8"); //bury this folder
+ entries.push(importPath);
+ });
+
+ const parcelBundleOutput = buildConfig("parcelBundleOutput");
+ if (entries.length > 0) {
+ console.log(chalk.yellow("Building Parcel bundles: "), _.keys(parcelGroupsByBundle).join(", "));
+
+ groups.push({
+ action: "parcel",
+ name: `orchardcore-bundle`,
+ source: entries,
+ dest: parcelBundleOutput,
+ });
+ }
+ }
+}
+
+const buildProcesses = groups
+ .map((group) => {
+ //b64 encode the command group for the next process. Was easier to pass the group json to the subprocess
+ const encodedGroup = Buffer.from(JSON5.stringify(group)).toString("base64");
+ let script = "";
+
+ switch (group.action) {
+ case "parcel":
+ // parcel only handles build and watch
+ if (!(task === "build" || task === "watch" || task === "host")) {
+ console.log(chalk.yellow(`Parcel action does not handle build type: ${task} for ${group.name}`));
+ break;
+ }
+ return {
+ order: group.order,
+ name: group.name ?? "PARCEL",
+ command: `node ${path.join(__dirname, "parcel.mjs")} ${task} ${encodedGroup}`,
+ };
+ case "webpack":
+ // parcel only handles build and watch
+ if (!(task === "build" || task === "watch" || task === "host")) {
+ console.log(chalk.yellow(`Webpack action does not handle build type: ${task} for ${group.name}`));
+ break;
+ }
+ return {
+ order: group.order,
+ name: group.name ?? "WEBPACK",
+ command: `node ${path.join(__dirname, "webpack.mjs")} ${task} ${encodedGroup}`,
+ };
+ case "vite":
+ // parcel only handles build and watch
+ if (!(task === "build" || task === "watch" || task === "host")) {
+ console.log(chalk.yellow(`Vite action does not handle build type: ${task} for ${group.name}`));
+ break;
+ }
+ return {
+ order: group.order,
+ name: group.name ?? "VITE",
+ command: `node ${path.join(__dirname, "vite.mjs")} ${task} ${encodedGroup}`,
+ };
+ case "run":
+ script = group?.scripts[task]; //get's the script that matches the current type of command (build/watch or others)
+
+ if (script) {
+ const cmd = {
+ order: group.order,
+ name: group.name ?? "RUN",
+ command: `cd ${group.source} && ${script}`,
+ };
+ console.log("run command: ", cmd);
+ return cmd;
+ }
+ console.log(chalk.yellow(group.name, "run action does not have a script for build type: ", task));
+ break;
+ case "copy":
+ if (task === "copy" || task === "build" || task === "dry-run") {
+ return {
+ order: group.order,
+ name: group.name ?? "COPY",
+ command: `node ${path.join(__dirname, "copy.mjs")} ${task} ${encodedGroup}`,
+ };
+ }
+ console.log(chalk.yellow("Use copy or build type to copy files from group: ", group.name));
+ break;
+ case "min":
+ if (task === "build" || task === "dry-run") {
+ return {
+ order: group.order,
+ name: group.name ?? "MIN",
+ command: `node ${path.join(__dirname, "min.mjs")} ${task} ${encodedGroup}`,
+ };
+ }
+ console.log(chalk.yellow("Use min or build type to minify files from group: ", group.name));
+ break;
+ case "sass":
+ if (task === "build" || task === "watch") {
+ return {
+ order: group.order,
+ name: group.name ?? "SASS",
+ command: `node ${path.join(__dirname, "sass.mjs")} ${task} ${encodedGroup}`,
+ };
+ }
+ console.log(chalk.yellow("Use sass or build type to transpile/minify files from group: ", group.name));
+ break;
+ default:
+ console.log(chalk.yellow("The following group was not handled by our build process"), group.name);
+ break;
+ }
+ })
+ .filter((el) => el != undefined); // remove undefined entries
+
+// Add the gulp build process if the user passes the -g cli flag
+let gulpBuildStr = parsedArgs["g"];
+let gulpRebuildStr = parsedArgs["r"];
+
+if (gulpBuildStr != undefined && gulpRebuildStr == undefined) {
+ buildProcesses.push({
+ order: 0,
+ name: "gulp build",
+ command: `gulp build`,
+ });
+} else if (gulpBuildStr != undefined && gulpRebuildStr != undefined) {
+ buildProcesses.push({
+ order: 0,
+ name: "gulp rebuild",
+ command: `gulp rebuild`,
+ });
+}
+
+if (buildProcesses.length <= 0) {
+ console.log(chalk.yellow("Nothing to build, exiting..."));
+ process.exit(0);
+}
+
+// run all builds in parallel. I chose to use concurrently here as it does nice colors + console log prefixes
+const { result } = concurrently(
+ buildProcesses.sort(function (a, b) {
+ return a.order - b.order;
+ }),
+ {
+ restartTries: 3,
+ prefixColors: ["green", "yellow", "blue", "magenta", "cyan", "greenBright", "yellowBright", "blueBright", "magentaBright", "cyanBright"],
+ },
+);
+
+result.then(
+ () => {
+ console.log(chalk.bold.green("Success!"));
+ process.exit(0);
+ },
+ (closeEvents) => {
+ let actualErrors = false;
+
+ closeEvents.forEach((evt) => {
+ //3221225477 is an intermittent issue with windows ? and does not seem to represent an actual issue.
+ if (evt.exitCode != 0 && evt.exitCode != 3221225477) {
+ console.log(chalk.red(evt.command.name, "exited with code", evt.exitCode));
+ actualErrors = true;
+ } else if (evt.exitCode == 3221225477) {
+ console.log(chalk.yellow(evt.command.name, "exited with code", evt.exitCode));
+ }
+ });
+
+ if (actualErrors) {
+ console.log(chalk.bold.redBright("Errors occurred!"));
+ process.exit(1);
+ } else {
+ process.exit(0);
+ }
+ },
+);
diff --git a/.scripts/assets-manager/clean.mjs b/.scripts/assets-manager/clean.mjs
new file mode 100644
index 00000000000..f5159c31504
--- /dev/null
+++ b/.scripts/assets-manager/clean.mjs
@@ -0,0 +1,45 @@
+import chalk from "chalk";
+import path from "path";
+import fs from "fs-extra";
+
+export default async function clean(groups) {
+ console.log(chalk.redBright("Clean task called. This wipes all folders referenced as destinations of all groups. Waiting 3 seconds before starting."));
+ await new Promise((resolve) => setTimeout(resolve, 3000));
+ try {
+ const promises = [];
+ for (const g of groups) {
+ if (g.dest) {
+ if (!g.dest || typeof g.dest !== "string" || !(g.dest.includes("wwwroot") || g.dest.includes("dist"))) {
+ console.log(chalk.red("We only support deleting folders that contain wwwroot or dist"), chalk.gray(g.dest));
+ throw chalk.red("Error cleaning: ") + chalk.gray(g.dest);
+ }
+
+ promises.push(
+ fs
+ .rm(g.dest, { recursive: true, force: true })
+ .then(() => console.log(chalk.green("Deleted folder:"), chalk.gray(g.dest)))
+ .catch((err) => {
+ console.log(chalk.red("Error deleting folder:"), chalk.gray(g.dest));
+ throw err;
+ }),
+ );
+ }
+ }
+ const parcelCache = path.join(process.cwd(), ".parcel-cache");
+ promises.push(
+ fs
+ .rm(parcelCache, { recursive: true, force: true })
+ .then(() => console.log(chalk.green("Deleted folder:"), chalk.gray(parcelCache)))
+ .catch((err) => {
+ console.log(chalk.red("Error deleting folder:"), chalk.gray(parcelCache));
+ throw err;
+ }),
+ );
+ await Promise.all(promises);
+ console.log(chalk.green("Cleaned successfully! exiting..."));
+ return 0;
+ } catch (e) {
+ console.log(chalk.red("Error: "), e);
+ return 1;
+ }
+}
diff --git a/.scripts/assets-manager/config.mjs b/.scripts/assets-manager/config.mjs
new file mode 100644
index 00000000000..222a971186f
--- /dev/null
+++ b/.scripts/assets-manager/config.mjs
@@ -0,0 +1,34 @@
+import path from "path";
+import chalk from "chalk";
+
+let userProvidedConfig = await import(path.join("file://", process.cwd(), "build.config.mjs").replace(/\\/g,'/'));
+
+export default function getConfig(key) {
+ switch (key) {
+ case "vite":
+ if (typeof userProvidedConfig?.vite !== "function") {
+ console.log(chalk.yellow("build.config.mjs did not provide the viteConfig function. Using defaults."));
+ return () => ({});
+ }
+ return userProvidedConfig.vite;
+ case "parcel":
+ if (typeof userProvidedConfig?.parcel !== "function") {
+ console.log(chalk.yellow("build.config.mjs did not provide the parcelConfig function. Using defaults."));
+ return () => ({});
+ }
+ return userProvidedConfig.parcel;
+ case "assetsLookupGlob":
+ if (!userProvidedConfig?.assetsLookupGlob) {
+ console.log(chalk.yellow("build.config.mjs did not provide the assetsLookupGlob string. Using default of 'src/{Modules,Themes}/*/Assets.json'"));
+ return "src/{Modules,Themes}/*/Assets.json";
+ }
+ return userProvidedConfig.assetsLookupGlob;
+ case "parcelBundleOutput":
+ if (!userProvidedConfig?.parcelBundleOutput) {
+ throw "build.config.mjs did not provide the parcelDistDirForBundle but parcel bundles are present. Please add the parcelBundleOutput constant in build.config.mjs";
+ }
+ return userProvidedConfig?.parcelBundleOutput;
+ }
+ console.log(chalk.yellow("Key not found in build.config.mjs"), key);
+ return;
+}
diff --git a/.scripts/assets-manager/copy.mjs b/.scripts/assets-manager/copy.mjs
new file mode 100644
index 00000000000..2990cbde85c
--- /dev/null
+++ b/.scripts/assets-manager/copy.mjs
@@ -0,0 +1,78 @@
+import fs from "fs-extra";
+import { glob } from "glob";
+import JSON5 from "json5";
+import chalk from "chalk";
+import path from "path";
+
+let action = process.argv[2];
+const config = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+
+let dest = config.dest;
+let fileExtension = config.source.split(".").pop();
+
+if (config.dest == undefined) {
+ if (config.tags.includes("js") || fileExtension == "js") {
+ dest = config.basePath + "/wwwroot/Scripts/";
+ } else if (config.tags.includes("css") || fileExtension == "css") {
+ dest = config.basePath + "/wwwroot/Styles/";
+ }
+}
+
+if (config.dryRun) {
+ action = "dry-run";
+}
+
+// console.log(`copy ${action}`, config);
+
+glob(config.source).then((files) => {
+ if (files.length == 0) {
+ console.log(chalk.yellow("No files to copy", config.source));
+ return;
+ }
+
+ const destExists = fs.existsSync(dest);
+
+ if (destExists) {
+ const stats = fs.lstatSync(dest);
+ if (!stats.isDirectory()) {
+ console.log(chalk.red("Destination is not a directory"));
+ console.log("Files:", files);
+ console.log("Destination:", dest);
+ return;
+ }
+ console.log(chalk.yellow(`Destination ${dest} already exists, files may be overwritten`));
+ }
+
+ let baseFolder;
+
+ if (config.source.indexOf("**") > 0) {
+ baseFolder = config.source.substring(0, config.source.indexOf("**"));
+ }
+
+ files.forEach((file) => {
+ file = file.replace(/\\/g, "/");
+ let relativePath;
+ if (baseFolder) {
+ relativePath = file.replace(baseFolder, "");
+ } else {
+ relativePath = path.basename(file);
+ }
+
+ const target = path.join(dest, relativePath);
+
+ if (action === "dry-run") {
+ console.log(`Dry run (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target));
+ } else {
+ fs.stat(file).then((stat) => {
+ if (!stat.isDirectory()) {
+ fs.copy(file, target)
+ .then(() => console.log(`Copied (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target)))
+ .catch((err) => {
+ console.log(`${chalk.red("Error copying")} (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target), chalk.red(err));
+ throw err;
+ });
+ }
+ });
+ }
+ });
+});
diff --git a/.scripts/assets-manager/min.mjs b/.scripts/assets-manager/min.mjs
new file mode 100644
index 00000000000..97254f52b6f
--- /dev/null
+++ b/.scripts/assets-manager/min.mjs
@@ -0,0 +1,157 @@
+import fs from "fs-extra";
+import { glob } from "glob";
+import JSON5 from "json5";
+import chalk from "chalk";
+import path from "path";
+import swc from "@swc/core";
+import { transform } from "lightningcss";
+import postcss from "postcss";
+import postcssRTLCSS from "postcss-rtlcss";
+import { Mode, Source } from "postcss-rtlcss/options";
+
+let action = process.argv[2];
+let mode = action === "build" ? "production" : "development";
+
+const config = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+
+let dest = config.dest;
+let fileExtension = config.source.split(".").pop();
+
+if (config.dest == undefined) {
+ if (config.tags.includes("js") || fileExtension == "js") {
+ dest = config.basePath + "/wwwroot/Scripts/";
+ } else if (config.tags.includes("css") || fileExtension == "css") {
+ dest = config.basePath + "/wwwroot/Styles/";
+ }
+}
+
+if (config.dryRun) {
+ action = "dry-run";
+}
+
+// console.log(`minify ${action}`, config);
+
+glob(config.source).then((files) => {
+ if (files.length == 0) {
+ console.log(chalk.yellow("No files to minify", config.source));
+ return;
+ }
+
+ const destExists = fs.existsSync(dest);
+
+ if (destExists) {
+ const stats = fs.lstatSync(dest);
+ if (!stats.isDirectory()) {
+ console.log(chalk.red("Destination is not a directory"));
+ console.log("Files:", files);
+ console.log("Destination:", dest);
+ return;
+ }
+ console.log(chalk.yellow(`Destination ${dest} already exists, files may be overwritten`));
+ }
+
+ let baseFolder;
+
+ if (config.source.indexOf("**") > 0) {
+ baseFolder = config.source.substring(0, config.source.indexOf("**"));
+ }
+
+ files.forEach((file) => {
+ file = file.replace(/\\/g, "/");
+ let relativePath;
+
+ if (baseFolder) {
+ relativePath = file.replace(baseFolder, "");
+ } else {
+ relativePath = path.basename(file);
+ }
+
+ const target = path.join(dest, relativePath);
+
+ if (action === "dry-run") {
+ console.log(`Dry run (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target));
+ } else {
+ fs.stat(file).then(async (stat) => {
+ if (!stat.isDirectory()) {
+ let fileInfo = path.parse(file);
+
+ if (fileInfo.ext === ".js") {
+ let reader = await fs.readFile(file, "utf8");
+
+ swc.minify(reader, {
+ compress: true,
+ sourceMap: mode === "development",
+ }).then((output) => {
+ const minifiedTarget = path.join(dest, path.parse(target).name + ".min.js");
+ fs.outputFile(minifiedTarget, output.code);
+ console.log(`Minified (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(minifiedTarget));
+
+ if (mode === "development" && output.map) {
+ const mappedTarget = path.join(dest, path.parse(target).name + ".map");
+ const normalized = output.map.replace(/(?:\\[rn])+/g, "\\n");
+ fs.outputFile(mappedTarget, normalized + "\n");
+ console.log(`Mapped (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(mappedTarget));
+ }
+ });
+
+ fs.copy(file, target)
+ .then(() => console.log(`Copied (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target)))
+ .catch((err) => {
+ console.log(`${chalk.red("Error copying")} (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target), chalk.red(err));
+ throw err;
+ });
+ } else if (fileInfo.ext === ".css") {
+ let reader = await fs.readFile(file, "utf8");
+
+ if (config.generateRTL) {
+ const rtlTarget = path.join(dest, path.parse(target).name + ".css");
+
+ const options = {
+ mode: Mode.combined,
+ from: Source.css,
+ };
+
+ const result = await postcss([postcssRTLCSS(options)]).process(reader);
+ reader = result.css;
+
+ console.log(`RTL (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(rtlTarget), chalk.cyan(rtlTarget));
+ }
+
+ const copyTarget = path.join(dest, path.parse(target).base);
+
+ await fs
+ .outputFile(copyTarget, reader)
+ .then(() => {
+ console.log(`Copied (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target));
+ })
+ .catch((err) => {
+ console.log(`${chalk.red("Error copying")} (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target), chalk.red(err));
+ throw err;
+ });
+
+ let { code, map } = transform({
+ code: Buffer.from(reader),
+ minify: true,
+ sourceMap: mode === "development",
+ });
+
+ if (code) {
+ const minifiedTarget = path.join(dest, path.parse(target).name + ".min.css");
+ await fs.outputFile(minifiedTarget, code.toString());
+ console.log(`Minified (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(minifiedTarget));
+ }
+
+ if (mode === "development" && map) {
+ const mappedTarget = path.join(dest, path.parse(target).name + ".map");
+ const normalized = map.toString().replace(/(?:\\[rn])+/g, "\\n");
+ await fs.outputFile(mappedTarget, normalized + "\n");
+ console.log(`Mapped (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(mappedTarget));
+ }
+ } else {
+ console.log("Trying to minify a file with an extension that is not allowed.");
+ }
+ }
+ });
+ }
+ });
+});
diff --git a/.scripts/assets-manager/package.json b/.scripts/assets-manager/package.json
new file mode 100644
index 00000000000..30604d48867
--- /dev/null
+++ b/.scripts/assets-manager/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@orchardcore/assets-manager",
+ "version": "1.0.0",
+ "main": "build.js",
+ "type": "module",
+ "bin": "./build.mjs",
+ "dependencies": {
+ "@parcel/config-default": "2.13.3",
+ "@parcel/core": "2.13.3",
+ "@parcel/diagnostic": "2.13.3",
+ "@parcel/events": "2.13.3",
+ "@parcel/fs": "2.13.3",
+ "@parcel/logger": "2.13.3",
+ "@parcel/optimizer-terser": "2.13.3",
+ "@parcel/reporter-bundle-analyzer": "2.13.3",
+ "@parcel/reporter-cli": "2.13.3",
+ "@parcel/reporter-sourcemap-visualiser": "2.13.3",
+ "@parcel/resolver-glob": "2.13.3",
+ "@parcel/transformer-css": "2.13.3",
+ "@parcel/transformer-image": "2.13.3",
+ "@parcel/transformer-inline-string": "2.13.3",
+ "@parcel/transformer-sass": "2.13.3",
+ "@parcel/transformer-vue": "2.13.3",
+ "@parcel/utils": "2.13.3",
+ "@swc/core": "1.10.16",
+ "@vitejs/plugin-vue": "^5.2.1",
+ "chalk": "^5.2.0",
+ "chokidar": "^4.0.3",
+ "concurrently": "9.1.2",
+ "fs-extra": "^11.3.0",
+ "glob": "^11.0.1",
+ "json5": "^2.2.3",
+ "lightningcss": "1.29.1",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.8",
+ "parcel": "2.13.3",
+ "parcel-config-vue2": "^0.1.3",
+ "parcel-transformer-vue2": "^0.1.7",
+ "postcss": "8.5.2",
+ "postcss-rtlcss": "5.6.0",
+ "sass": "^1.84.0",
+ "vite": "^6.1.0",
+ "vue": "^3.5.13",
+ "vue-hot-reload-api": "^2.3.4",
+ "webpack": "^5.98.0",
+ "webpack-dev-server": "^5.2.0"
+ },
+ "eslintConfig": {
+ "parserOptions": {
+ "ecmaVersion": "latest"
+ }
+ }
+}
diff --git a/.scripts/assets-manager/parcel.mjs b/.scripts/assets-manager/parcel.mjs
new file mode 100644
index 00000000000..efb0149bb13
--- /dev/null
+++ b/.scripts/assets-manager/parcel.mjs
@@ -0,0 +1,142 @@
+import path from "path";
+import fs from "fs-extra";
+import JSON5 from "json5";
+import { Parcel } from "@parcel/core";
+import { fileURLToPath } from "url";
+import chalk from "chalk";
+import _ from "lodash";
+import buildConfig from "./config.mjs";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const parcelConfig = path.join(__dirname, ".parcelrc");
+const action = process.argv[2];
+const config = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+const isWatching = action === "watch";
+const isHosting = action === "host";
+
+async function runParcel(command, assetConfig) {
+ //console.log(`parcel ${command}`, assetConfig);
+
+ if (assetConfig.source.length === 0) {
+ console.error("No source provided for", assetConfig);
+ return;
+ }
+
+ let options = buildParcelOptions(command, assetConfig);
+
+ let parcel = new Parcel({
+ entries: assetConfig.source,
+ config: parcelConfig,
+ shouldPatchConsole: true,
+ ...options,
+ });
+
+ if (isWatching || isHosting) {
+ const parcelCacheFolder = options.cacheDir;
+ fs.rmSync(parcelCacheFolder, { recursive: true, force: true });
+
+ console.log(chalk.green("Deleted folder:"), chalk.gray(parcelCacheFolder));
+
+ const { unsubscribe } = await parcel.watch((err, event) => {
+ if (err) {
+ throw err;
+ }
+ // if (event.type === "buildSuccess") {
+ // console.log(
+ // `✨ Built ${assetConfig.source} in ${event.buildTime}ms!`
+ // );
+ // }
+ });
+
+ process.on("SIGINT", () => {
+ console.log(chalk.bold.yellow("Parcel is shutting down... "));
+ unsubscribe().finally(() => process.exit(130));
+ });
+ process.on("SIGTERM", () => {
+ console.log(chalk.bold.yellow("Parcel is shutting down... "));
+ unsubscribe().finally(() => process.exit(143));
+ });
+ } else {
+ try {
+ await parcel.run();
+ } catch (err) {
+ console.log(err.diagnostics);
+ process.exit(1);
+ }
+ process.exit(0);
+ }
+}
+
+// Builds the options to pass to the parcel constructor.
+function buildParcelOptions(command, assetConfig) {
+ let nodeEnv;
+ if (command === "build") {
+ nodeEnv = process.env.NODE_ENV || "production";
+ } else {
+ // watch
+ nodeEnv = process.env.NODE_ENV || "development";
+ }
+
+ // Set process.env.NODE_ENV to a default if undefined so that it is
+ // available in JS configs and plugins.
+ process.env.NODE_ENV = nodeEnv;
+
+ let mode = command === "build" ? "production" : "development";
+ let defaultOptions = {
+ shouldDisableCache: mode === "production",
+ cacheDir: path.join(".parcel-cache", assetConfig.name + "-" + hashCode(JSON.stringify(assetConfig))),
+ mode,
+ shouldContentHash: mode === "production",
+ serveOptions: isHosting ? { port: 3000 } : false,
+ hmrOptions: isHosting ? { port: 3000 } : false,
+ shouldAutoInstall: false,
+ logLevel: null,
+ shouldProfile: false,
+ shouldBuildLazily: false,
+ detailedReport: null,
+ targets: {
+ default: {
+ context: "browser",
+ scopeHoist: true,
+ optimize: mode === "production",
+ distDir: assetConfig.dest,
+ engines: {
+ browsers: "> 1%, last 2 versions, not dead",
+ },
+ outputFormat: "global",
+ sourceMap: mode === "development",
+ },
+ },
+ env: {
+ NODE_ENV: nodeEnv,
+ },
+ additionalReporters: [
+ {
+ packageName: "@parcel/reporter-cli",
+ resolveFrom: __filename,
+ },
+ ],
+ };
+
+ return _.merge(defaultOptions, buildConfig("parcel")(action, assetConfig, defaultOptions), assetConfig.options);
+}
+
+// run the process
+await runParcel(action, config);
+
+function hashCode(str) {
+ let hash = 0,
+ i,
+ chr;
+
+ if (str.length === 0) return hash;
+
+ for (i = 0; i < str.length; i++) {
+ chr = str.charCodeAt(i);
+ hash = (hash << 5) - hash + chr;
+ hash |= 0; // Convert to 32bit integer
+ }
+
+ return hash < 0 ? -hash : hash; // Convert to positive number
+}
diff --git a/.scripts/assets-manager/sass.mjs b/.scripts/assets-manager/sass.mjs
new file mode 100644
index 00000000000..784b45fde98
--- /dev/null
+++ b/.scripts/assets-manager/sass.mjs
@@ -0,0 +1,179 @@
+import fs from "fs-extra";
+import { glob } from "glob";
+import JSON5 from "json5";
+import chalk from "chalk";
+import path from "path";
+import { transform } from "lightningcss";
+import * as sass from "sass";
+import postcss from "postcss";
+import postcssRTLCSS from "postcss-rtlcss";
+import { Mode } from "postcss-rtlcss/options";
+import chokidar from "chokidar";
+
+let action = process.argv[2];
+let mode = action === "build" ? "production" : "development";
+const config = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+const dest = config.dest ?? config.basePath + "/wwwroot/Styles/";
+
+if (config.dryRun) {
+ action = "dry-run";
+}
+
+const isWatching = action === "watch";
+
+const resolveImports = (filePath, fileContent, resolvedFiles = new Set()) => {
+ const importRegex = /@import\s+['"](.+?)['"]/g;
+ let match;
+ while ((match = importRegex.exec(fileContent)) !== null) {
+ let importPath = path.resolve(path.dirname(filePath), match[1]);
+ if (!importPath.endsWith(".scss")) {
+ importPath += ".scss";
+ }
+ if (!resolvedFiles.has(importPath)) {
+ resolvedFiles.add(importPath);
+
+ let content;
+ try {
+ content = fs.readFileSync(importPath, "utf8");
+ } catch {
+ // Try with file name starting with '_'
+ const altImportPath = path.join(path.dirname(importPath), "_" + path.basename(importPath));
+ try {
+ content = fs.readFileSync(altImportPath, "utf8");
+ importPath = altImportPath; // Update to the underscore-prefixed path
+ } catch (altErr) {
+ console.error(`Failed to resolve import at ${importPath} or ${altImportPath}:`, altErr);
+ continue;
+ }
+ }
+ resolveImports(importPath, content, resolvedFiles);
+ }
+ }
+ return resolvedFiles;
+};
+
+if (isWatching) {
+ glob(config.source).then((files) => {
+ const watchFiles = new Set();
+ watchFiles.add(path.dirname(config.source));
+
+ files.forEach((file) => {
+ const content = fs.readFileSync(file, "utf8");
+ const resolvedFiles = resolveImports(file, content);
+ resolvedFiles.forEach((resolvedFile) => watchFiles.add(resolvedFile));
+ });
+
+ chokidar
+ .watch([...watchFiles], {
+ ignored: (path, stats) => stats?.isFile() && !path.endsWith(".scss"),
+ persistent: true,
+ })
+ .on("change", () => {
+ runSass(config);
+ });
+ });
+} else {
+ runSass(config);
+}
+
+function runSass(config) {
+ glob(config.source).then((files) => {
+ if (files.length == 0) {
+ console.log(chalk.yellow("No files to copy", config.source));
+ return;
+ }
+
+ const destExists = fs.existsSync(dest);
+
+ if (destExists) {
+ const stats = fs.lstatSync(dest);
+ if (!stats.isDirectory()) {
+ console.log(chalk.red("Destination is not a directory"));
+ console.log("Files:", files);
+ console.log("Destination:", dest);
+ return;
+ }
+ console.log(chalk.yellow(`Destination ${dest} already exists, files may be overwritten`));
+ }
+
+ let baseFolder;
+
+ if (config.source.indexOf("**") > 0) {
+ baseFolder = config.source.substring(0, config.source.indexOf("**"));
+ }
+
+ files.forEach((file) => {
+ file = file.replace(/\\/g, "/");
+ let relativePath;
+
+ if (baseFolder) {
+ relativePath = file.replace(baseFolder, "");
+ } else {
+ relativePath = path.basename(file);
+ }
+
+ const target = path.join(dest, relativePath);
+
+ if (action === "dry-run") {
+ console.log(`Dry run (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(target));
+ } else {
+ fs.stat(file).then(async (stat) => {
+ if (!stat.isDirectory()) {
+ let fileInfo = path.parse(file);
+
+ if (fileInfo.ext === ".scss") {
+ const scssResult = await sass.compileAsync(file, {
+ sourceMap: mode === "development",
+ sourceMapIncludeSources: false,
+ });
+
+ if (mode === "development" && scssResult.sourceMap) {
+ const mappedTarget = path.join(dest, path.parse(target).name + ".scss.map");
+ fs.outputFile(mappedTarget, JSON5.stringify(scssResult.sourceMap));
+ console.log(`Mapped (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(mappedTarget));
+ }
+
+ if (scssResult.css) {
+ const normalTarget = path.join(dest, path.parse(target).name + ".css");
+ await fs.outputFile(normalTarget, scssResult.css);
+ console.log(`Tranpiled (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(file), chalk.cyan(normalTarget));
+
+ if (config.generateRTL) {
+ const options = {
+ mode: Mode.combined,
+ };
+
+ const result = await postcss([postcssRTLCSS(options)]).process(scssResult.css, { from: file });
+
+ await fs.outputFile(normalTarget, result.css);
+ scssResult.css = result.css;
+ console.log(`RTL (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(normalTarget), chalk.cyan(normalTarget));
+ }
+
+ let { code, map } = transform({
+ code: Buffer.from(scssResult.css),
+ minify: true,
+ sourceMap: true,
+ });
+
+ if (code) {
+ const minifiedTarget = path.join(dest, path.parse(target).name + ".min.css");
+ fs.outputFile(minifiedTarget, code);
+ console.log(`Minified (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(normalTarget), chalk.cyan(minifiedTarget));
+ }
+
+ if (mode === "development" && map) {
+ const mappedTarget = path.join(dest, path.parse(target).name + ".css.map");
+ fs.outputFile(mappedTarget, map);
+ console.log(`Mapped (${chalk.gray("from")}, ${chalk.cyan("to")})`, chalk.gray(normalTarget), chalk.cyan(mappedTarget));
+ }
+ }
+ } else {
+ console.log("Trying to transpile a SASS file with an extension that is not allowed.");
+ }
+ }
+ });
+ }
+ });
+ });
+}
diff --git a/.scripts/assets-manager/vite.mjs b/.scripts/assets-manager/vite.mjs
new file mode 100644
index 00000000000..7784f450cb6
--- /dev/null
+++ b/.scripts/assets-manager/vite.mjs
@@ -0,0 +1,31 @@
+import { build, createServer } from "vite";
+import JSON5 from "json5";
+
+async function runVite(command, assetConfig) {
+ if (command === "build") {
+ await build({
+ root: assetConfig.source,
+ });
+ } else if (command === "watch") {
+ await build({
+ root: assetConfig.source,
+ build: { watch: {} },
+ });
+ } else if (command === "host") {
+ // Could be changed to "serve" command
+ const server = await createServer({
+ root: assetConfig.source,
+ });
+
+ await server.listen();
+
+ server.printUrls();
+ server.bindCLIShortcuts({ print: true });
+ }
+}
+
+// run the process
+const action = process.argv[2];
+const config = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+
+await runVite(action, config);
diff --git a/.scripts/assets-manager/webpack.mjs b/.scripts/assets-manager/webpack.mjs
new file mode 100644
index 00000000000..1e68dedcf7c
--- /dev/null
+++ b/.scripts/assets-manager/webpack.mjs
@@ -0,0 +1,47 @@
+// Webpack API see: https://webpack.js.org/api/node/
+import webpack from "webpack";
+import JSON5 from "json5";
+import WebpackDevServer from "webpack-dev-server";
+
+const action = process.argv[2];
+const assetConfig = JSON5.parse(Buffer.from(process.argv[3], "base64").toString("utf-8"));
+const webpackConfig = await import("../../" + assetConfig.basePath + assetConfig.config);
+const compiler = webpack(webpackConfig.default);
+
+if (action === "build") {
+ // run webpack
+ compiler.run(() => {
+ compiler.close(() => {});
+ });
+} else if (action === "watch") {
+ // watch webpack
+ compiler.watch(
+ {
+ // Example
+ aggregateTimeout: 300,
+ poll: undefined,
+ },
+ (err, stats) => {
+ // Print watch/build result here...
+ console.log(
+ stats.toString({
+ colors: true,
+ chunks: false,
+ modules: false,
+ children: false,
+ entrypoints: false,
+ }),
+ );
+ },
+ );
+} else if (action === "host") {
+ const devServerOptions = { ...webpackConfig.default.devServer, open: true };
+ const server = new WebpackDevServer(devServerOptions, compiler);
+
+ const runServer = async () => {
+ console.log("Starting server...");
+ await server.start();
+ };
+
+ runServer();
+}
diff --git a/.scripts/bloom/README.md b/.scripts/bloom/README.md
new file mode 100644
index 00000000000..de63e211dd6
--- /dev/null
+++ b/.scripts/bloom/README.md
@@ -0,0 +1,5 @@
+# Bloom
+
+Bloom is the javascript framework for shared components and services in Orchard Core.
+
+
\ No newline at end of file
diff --git a/.scripts/bloom/assets/bloom.ai b/.scripts/bloom/assets/bloom.ai
new file mode 100644
index 00000000000..e556197d5d2
--- /dev/null
+++ b/.scripts/bloom/assets/bloom.ai
@@ -0,0 +1,5698 @@
+%PDF-1.6
%
+1 0 obj
<>/OCGs[31 0 R]>>/Pages 3 0 R/Type/Catalog>>
endobj
2 0 obj
<>stream
+
+
+
+
+ application/pdf
+
+
+ bloom
+
+
+ 2025-02-15T13:14:08-05:00
+ 2025-02-15T13:14:08-05:00
+ 2025-02-15T13:14:08-04:00
+ Adobe Illustrator 26.3 (Windows)
+
+
+
+ 256
+ 248
+ JPEG
+ /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK
DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f
Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgA+AEAAwER
AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA
AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB
UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE
1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ
qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy
obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp
0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo
+DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7
FXYq7FXYq7FWI+ePzV8keS4z+mb9frlKpp1vSW5bp/usEcdmrVyoOKvAfNf/ADlf5tvXaLy5YwaT
b1HCeYC5uCKUNeQ9Ib7/AGT88Fpp5ZrP5i+e9ap+k9evrlVNVjM7qgJ7hFKr+GKpBJLLK/OV2kc9
WYkn7zircNxPA/OCRon6ckYqfHqMVZHof5nfmDoZX9GeYL2BFYOIWlMsXIU6xS80PTuMVen+Vv8A
nLHzXZssXmKwg1WCp5zw/wCjT0pt0rGd/wDJGNrT3ryN+bXkfzqPT0a+431CW065AiuABuSEqQwp
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
UDsFGwxVfirsVdirsVdirsVdirsVdirsVf/Z
+
+
+
+ uuid:a91d7c08-a9fc-4b1a-bac3-06d5e0a2166a
+ xmp.did:966df503-5368-e54e-b3e2-1b84a2b1e5e1
+ uuid:5D20892493BFDB11914A8590D31508C8
+ proof:pdf
+
+ uuid:c9914b06-205d-4060-b620-fae57f2f5102
+ xmp.did:526773d2-a98f-654f-bf01-cbbe9b3a6f9d
+ uuid:5D20892493BFDB11914A8590D31508C8
+ proof:pdf
+
+
+
+
+ saved
+ xmp.iid:966df503-5368-e54e-b3e2-1b84a2b1e5e1
+ 2025-02-15T13:09:06-05:00
+ Adobe Illustrator 26.3 (Windows)
+ /
+
+
+
+ Document
+ Print
+ AIRobin
+ False
+ False
+ 1
+
+ 1000.000000
+ 1000.000000
+ Points
+
+
+
+ Cyan
+ Magenta
+ Yellow
+ Black
+
+
+
+
+
+ Default Swatch Group
+ 0
+
+
+
+ White
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 0.000000
+
+
+ Black
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 100.000000
+
+
+ CMYK Red
+ CMYK
+ PROCESS
+ 0.000000
+ 100.000000
+ 100.000000
+ 0.000000
+
+
+ CMYK Yellow
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ CMYK Green
+ CMYK
+ PROCESS
+ 100.000000
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ CMYK Cyan
+ CMYK
+ PROCESS
+ 100.000000
+ 0.000000
+ 0.000000
+ 0.000000
+
+
+ CMYK Blue
+ CMYK
+ PROCESS
+ 100.000000
+ 100.000000
+ 0.000000
+ 0.000000
+
+
+ CMYK Magenta
+ CMYK
+ PROCESS
+ 0.000000
+ 100.000000
+ 0.000000
+ 0.000000
+
+
+ C=15 M=100 Y=90 K=10
+ CMYK
+ PROCESS
+ 15.000000
+ 100.000000
+ 90.000000
+ 10.000000
+
+
+ C=0 M=90 Y=85 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 90.000000
+ 85.000000
+ 0.000000
+
+
+ C=0 M=80 Y=95 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 80.000000
+ 95.000000
+ 0.000000
+
+
+ C=0 M=50 Y=100 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 50.000000
+ 100.000000
+ 0.000000
+
+
+ C=0 M=35 Y=85 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 35.000000
+ 85.000000
+ 0.000000
+
+
+ C=5 M=0 Y=90 K=0
+ CMYK
+ PROCESS
+ 5.000000
+ 0.000000
+ 90.000000
+ 0.000000
+
+
+ C=20 M=0 Y=100 K=0
+ CMYK
+ PROCESS
+ 20.000000
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ C=50 M=0 Y=100 K=0
+ CMYK
+ PROCESS
+ 50.000000
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ C=75 M=0 Y=100 K=0
+ CMYK
+ PROCESS
+ 75.000000
+ 0.000000
+ 100.000000
+ 0.000000
+
+
+ C=85 M=10 Y=100 K=10
+ CMYK
+ PROCESS
+ 85.000000
+ 10.000000
+ 100.000000
+ 10.000000
+
+
+ C=90 M=30 Y=95 K=30
+ CMYK
+ PROCESS
+ 90.000000
+ 30.000000
+ 95.000000
+ 30.000000
+
+
+ C=75 M=0 Y=75 K=0
+ CMYK
+ PROCESS
+ 75.000000
+ 0.000000
+ 75.000000
+ 0.000000
+
+
+ C=80 M=10 Y=45 K=0
+ CMYK
+ PROCESS
+ 80.000000
+ 10.000000
+ 45.000000
+ 0.000000
+
+
+ C=70 M=15 Y=0 K=0
+ CMYK
+ PROCESS
+ 70.000000
+ 15.000000
+ 0.000000
+ 0.000000
+
+
+ C=85 M=50 Y=0 K=0
+ CMYK
+ PROCESS
+ 85.000000
+ 50.000000
+ 0.000000
+ 0.000000
+
+
+ C=100 M=95 Y=5 K=0
+ CMYK
+ PROCESS
+ 100.000000
+ 95.000000
+ 5.000000
+ 0.000000
+
+
+ C=100 M=100 Y=25 K=25
+ CMYK
+ PROCESS
+ 100.000000
+ 100.000000
+ 25.000000
+ 25.000000
+
+
+ C=75 M=100 Y=0 K=0
+ CMYK
+ PROCESS
+ 75.000000
+ 100.000000
+ 0.000000
+ 0.000000
+
+
+ C=50 M=100 Y=0 K=0
+ CMYK
+ PROCESS
+ 50.000000
+ 100.000000
+ 0.000000
+ 0.000000
+
+
+ C=35 M=100 Y=35 K=10
+ CMYK
+ PROCESS
+ 35.000000
+ 100.000000
+ 35.000000
+ 10.000000
+
+
+ C=10 M=100 Y=50 K=0
+ CMYK
+ PROCESS
+ 10.000000
+ 100.000000
+ 50.000000
+ 0.000000
+
+
+ C=0 M=95 Y=20 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 95.000000
+ 20.000000
+ 0.000000
+
+
+ C=25 M=25 Y=40 K=0
+ CMYK
+ PROCESS
+ 25.000000
+ 25.000000
+ 40.000000
+ 0.000000
+
+
+ C=40 M=45 Y=50 K=5
+ CMYK
+ PROCESS
+ 40.000000
+ 45.000000
+ 50.000000
+ 5.000000
+
+
+ C=50 M=50 Y=60 K=25
+ CMYK
+ PROCESS
+ 50.000000
+ 50.000000
+ 60.000000
+ 25.000000
+
+
+ C=55 M=60 Y=65 K=40
+ CMYK
+ PROCESS
+ 55.000000
+ 60.000000
+ 65.000000
+ 40.000000
+
+
+ C=25 M=40 Y=65 K=0
+ CMYK
+ PROCESS
+ 25.000000
+ 40.000000
+ 65.000000
+ 0.000000
+
+
+ C=30 M=50 Y=75 K=10
+ CMYK
+ PROCESS
+ 30.000000
+ 50.000000
+ 75.000000
+ 10.000000
+
+
+ C=35 M=60 Y=80 K=25
+ CMYK
+ PROCESS
+ 35.000000
+ 60.000000
+ 80.000000
+ 25.000000
+
+
+ C=40 M=65 Y=90 K=35
+ CMYK
+ PROCESS
+ 40.000000
+ 65.000000
+ 90.000000
+ 35.000000
+
+
+ C=40 M=70 Y=100 K=50
+ CMYK
+ PROCESS
+ 40.000000
+ 70.000000
+ 100.000000
+ 50.000000
+
+
+ C=50 M=70 Y=80 K=70
+ CMYK
+ PROCESS
+ 50.000000
+ 70.000000
+ 80.000000
+ 70.000000
+
+
+
+
+
+ Grays
+ 1
+
+
+
+ C=0 M=0 Y=0 K=100
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 100.000000
+
+
+ C=0 M=0 Y=0 K=90
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 89.999400
+
+
+ C=0 M=0 Y=0 K=80
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 79.998800
+
+
+ C=0 M=0 Y=0 K=70
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 69.999700
+
+
+ C=0 M=0 Y=0 K=60
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 59.999100
+
+
+ C=0 M=0 Y=0 K=50
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 50.000000
+
+
+ C=0 M=0 Y=0 K=40
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 39.999400
+
+
+ C=0 M=0 Y=0 K=30
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 29.998800
+
+
+ C=0 M=0 Y=0 K=20
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 19.999700
+
+
+ C=0 M=0 Y=0 K=10
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 9.999100
+
+
+ C=0 M=0 Y=0 K=5
+ CMYK
+ PROCESS
+ 0.000000
+ 0.000000
+ 0.000000
+ 4.998800
+
+
+
+
+
+ Brights
+ 1
+
+
+
+ C=0 M=100 Y=100 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 100.000000
+ 100.000000
+ 0.000000
+
+
+ C=0 M=75 Y=100 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 75.000000
+ 100.000000
+ 0.000000
+
+
+ C=0 M=10 Y=95 K=0
+ CMYK
+ PROCESS
+ 0.000000
+ 10.000000
+ 95.000000
+ 0.000000
+
+
+ C=85 M=10 Y=100 K=0
+ CMYK
+ PROCESS
+ 85.000000
+ 10.000000
+ 100.000000
+ 0.000000
+
+
+ C=100 M=90 Y=0 K=0
+ CMYK
+ PROCESS
+ 100.000000
+ 90.000000
+ 0.000000
+ 0.000000
+
+
+ C=60 M=90 Y=0 K=0
+ CMYK
+ PROCESS
+ 60.000000
+ 90.000000
+ 0.003100
+ 0.003100
+
+
+
+
+
+
+ Adobe PDF library 16.07
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+endstream
endobj
3 0 obj
<>
endobj
5 0 obj
<>/Resources<>/ExtGState<>/Properties<>>>/Thumb 36 0 R/TrimBox[0.0 0.0 1000.0 1000.0]/Type/Page>>
endobj
33 0 obj
<>stream
+HdKeGEw9{OBh` O7Hg[Y猛'>;v/O-~}Tr?_?}kN_^wwnĦ5QsV[OZ}iv[*e=cT~ZZg
+i̧.O#R-ic(ϙ5U\ee3˕VZۉmɽw·^KѻR{'sI6Wz2aU_^~zXvO9ɸet:>>ўnPL9{zX5}n`]Ib:3p4O-C9dpsq).Cp1|
+y鄰 p<{b2Yi9~Sz%/En`R9JqXv
+
+HV͆\5-3wHF9^)y:_&ޅm;?{Z0^6=q@=MбDI:|igIQOB:Gei^SqYuDyߵs۞/ǿE
+@>^fZmrORϸWIO!j
+qN-{i
+kΨHC!X߾?dP!6 Ծp/)qfah3Hyst!b5 m.LjfD[,-T}رkupwf`<ʫAI1Uwꀅ&tǣO"Rw.6FTD4,6Yk*;>=myPbсD!;;P zAX3$eMɓ>f
K/tQ'u8 INJ6W
+ã@#"%4'QI H2Q$tLO{'Szqy-y8K̉e0lcv!(ߵ{],^cSׄDyJ;qk:'@m+HkMngd^A.QD?l#1$
/e/9H]<@U\Y9䊓0 )381aFYmpE AUF-)f}fm>-Tv+E"W4N9O('R KmÓvxwFsLcWWT93*=//}A;0UԐ\4BECX;J__)dIQNsF]]{ڣU)Dˌ*I7408%_f4'c8+Xuwꐠul,1EB;D-IB8m4Z+QY1*a:2J&{9ӿgʚK@8Z60Mj!Lu*0낕VC:8J"Zp|WEYxN&` v`MUuE8AMhziSAJDsRl;LEUhEV5
+hа["$0(eP۪"M/+pc@8aEZe8TAM! {+n6*dOؓ}@pXcq2<F^jCe6mB
YXU'ےy:AWSh&ZfP`Pjs'a&xgjg djS1hv:d[C{>`Zߣ>,v?e|IUN?m:P]aQGh
Sc0%0оrWnuADS
Yfޑ%ynDB A{!Ix-3peޚjaNY;Ԯ
'u_> *M7B_@(5%"qp#PMpR :[pǥwYBцSB>jW[N3i̬.y@2"p
+ƋMB┝`ART)4h).
n`ۉV@rؿ#r3>嫷{H2i>.6ɉܤ\h A7%
+t)g[GB׃Pr"@ nrhC2hv13 -I
+mlGjnoKa\#c4e++d:.^>SCNj2ef=hom;bל#`ܦ$h'9}%vpR4T9)9_>E~<
O"ZC8!i~~0U-{)˹oť wrf/~;o_p(2S2W 믴I;!@C
mo`e]W*tMEcWIfYw~2xPk2=~Ӹ }J4QO].k(8S?g0qoR>ZZs<&0Q>+mAi @1
+$G܊