mage-obsidian 1.1.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.github/workflows/ci.yml +38 -0
  2. package/.github/workflows/publish.yml +26 -25
  3. package/.oxlintrc.json +19 -0
  4. package/.prettierignore +7 -0
  5. package/.prettierrc.json +7 -0
  6. package/LICENSE +1 -1
  7. package/README.md +1 -0
  8. package/eslint.config.js +24 -0
  9. package/package.json +39 -13
  10. package/pnpm-workspace.yaml +4 -0
  11. package/src/cli/buildThemes.ts +263 -0
  12. package/src/config/default.ts +31 -0
  13. package/src/config/eslint.js +60 -0
  14. package/src/core/configResolver.ts +141 -0
  15. package/src/core/contractValidator.ts +56 -0
  16. package/src/core/cssResolver.ts +153 -0
  17. package/src/{service/generateInterceptors.js → core/generateInterceptors.ts} +79 -46
  18. package/src/core/generateJsconfig.ts +168 -0
  19. package/src/{service/moduleResolver.js → core/moduleResolver.ts} +79 -46
  20. package/src/core/preCompileFiles.ts +105 -0
  21. package/src/core/preCompileMagentoFiles.ts +34 -0
  22. package/src/core/themeResolverSync.ts +66 -0
  23. package/src/runtime/i18nCore.ts +113 -0
  24. package/src/runtime/interceptorManager.ts +186 -0
  25. package/src/runtime/islands.ts +84 -0
  26. package/src/runtime/sectionStoreCore.ts +259 -0
  27. package/src/utils/{findComponents.js → findComponents.ts} +29 -16
  28. package/src/utils/runWithConcurrency.ts +35 -0
  29. package/src/vite/defaultNodeResolver.ts +17 -0
  30. package/src/{service/inheritAssetsModuleResolver.js → vite/inheritAssetsModuleResolver.ts} +15 -11
  31. package/src/{service/inheritModuleResolver.js → vite/inheritModuleResolver.ts} +6 -10
  32. package/src/{service/interceptorsPlugin.js → vite/interceptorsPlugin.ts} +25 -19
  33. package/src/vite/magentoHrmRewrite.ts +44 -0
  34. package/src/vite/sharedPlugins.ts +54 -0
  35. package/src/vite/themeSourceWatcher.ts +96 -0
  36. package/src/vite/unresolvedModuleGuard.ts +87 -0
  37. package/tsconfig.json +34 -0
  38. package/vitest.config.js +18 -0
  39. package/jest.config.js +0 -5
  40. package/src/__tests__/__mocks__/configResolver.js +0 -35
  41. package/src/__tests__/fixtures/interceptors/emptyTarget.js +0 -2
  42. package/src/__tests__/fixtures/interceptors/pluginForNonFunction.js +0 -7
  43. package/src/__tests__/fixtures/interceptors/pluginModule.js +0 -13
  44. package/src/__tests__/fixtures/interceptors/targetModule.js +0 -12
  45. package/src/__tests__/fixtures/interceptors/targetNonFunction.js +0 -7
  46. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/components/ComponentA.vue +0 -0
  47. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/components/ComponentB.vue +0 -0
  48. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/css/module.extend.css +0 -0
  49. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/js/main.js +0 -0
  50. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/main.js +0 -0
  51. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameA/view/frontend/web/module.config.js +0 -9
  52. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameB/view/frontend/web/components/ComponentA.vue +0 -0
  53. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameB/view/frontend/web/components/ComponentB.vue +0 -0
  54. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameB/view/frontend/web/css/module.extend.css +0 -2
  55. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameB/view/frontend/web/js/main.js +0 -0
  56. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameB/view/frontend/web/module.config.js +0 -7
  57. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameC/view/frontend/web/components/ComponentA.vue +0 -0
  58. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameC/view/frontend/web/components/ComponentB.vue +0 -0
  59. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameC/view/frontend/web/css/module.extend.css +0 -2
  60. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameC/view/frontend/web/js/main.js +0 -0
  61. package/src/__tests__/magento_scenarios/app/code/Vendor/ModuleNameC/view/frontend/web/module.config.js +0 -7
  62. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameA/web/components/ComponentA.vue +0 -0
  63. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameA/web/components/ComponentB.vue +0 -0
  64. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameA/web/css/module.extend.css +0 -0
  65. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameA/web/js/main.js +0 -0
  66. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameA/web/module.config.js +0 -2
  67. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameB/web/components/ComponentA.vue +0 -0
  68. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameB/web/components/ComponentB.vue +0 -0
  69. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameB/web/css/module.extend.css +0 -0
  70. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameB/web/js/main.js +0 -0
  71. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameB/web/module.config.js +0 -2
  72. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameNoConfig/web/components/ComponentA.vue +0 -0
  73. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameNoConfig/web/components/ComponentB.vue +0 -0
  74. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameNoConfig/web/css/module.extend.css +0 -0
  75. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameNoConfig/web/js/main.js +0 -0
  76. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/Vendor_ModuleNameNoConfig/web/module.config.js +0 -2
  77. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/web/components/ComponentA.vue +0 -0
  78. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/web/components/ComponentB.vue +0 -0
  79. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/web/css/theme.source.css +0 -6
  80. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/web/js/main.js +0 -0
  81. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-a/web/theme.config.js +0 -5
  82. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-b/web/css/theme.source.css +0 -6
  83. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-b/web/theme.config.js +0 -5
  84. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/Vendor_ModuleNameB/web/components/ComponentA.vue +0 -0
  85. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/Vendor_ModuleNameB/web/components/ComponentB.vue +0 -0
  86. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/Vendor_ModuleNameB/web/css/module.extend.css +0 -0
  87. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/Vendor_ModuleNameB/web/js/main.js +0 -0
  88. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/Vendor_ModuleNameB/web/module.config.js +0 -2
  89. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/web/css/theme.source.css +0 -6
  90. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-c/web/theme.config.js +0 -2
  91. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-d/web/css/theme.source.css +0 -6
  92. package/src/__tests__/magento_scenarios/app/design/frontend/Vendor/theme-d/web/theme.config.js +0 -3
  93. package/src/__tests__/magento_scenarios/app/etc/mage_obsidian_frontend_modules_a.json +0 -53
  94. package/src/__tests__/magento_scenarios/app/etc/mage_obsidian_frontend_modules_b.json +0 -50
  95. package/src/__tests__/service/generateInterceptors.test.js +0 -196
  96. package/src/__tests__/service/inheritModuleResolver.test.js +0 -61
  97. package/src/__tests__/service/interceptorManager_magento_behavior.test.js +0 -137
  98. package/src/__tests__/service/moduleResolver.test.js +0 -194
  99. package/src/__tests__/service/themeResolverSync.test.js +0 -97
  100. package/src/__tests__/vite-plugins/interceptorsPlugin.test.js +0 -121
  101. package/src/config/default.js +0 -27
  102. package/src/scripts/buildThemes.js +0 -132
  103. package/src/service/configResolver.js +0 -63
  104. package/src/service/cssResolver.js +0 -73
  105. package/src/service/defaultNodeResolver.js +0 -16
  106. package/src/service/interceptorManager.js +0 -137
  107. package/src/service/magentoHrmRewrite.js +0 -43
  108. package/src/service/preCompileFiles.js +0 -37
  109. package/src/service/preCompileMagentoFiles.js +0 -16
  110. package/src/service/setupGlobals.js +0 -19
  111. package/src/service/themeResolverSync.js +0 -67
@@ -0,0 +1,38 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ jobs:
8
+ unit:
9
+ name: Unit tests (Node ${{ matrix.node }})
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ node: ["20", "22"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-node@v4
17
+ with:
18
+ node-version: ${{ matrix.node }}
19
+ - run: npm install
20
+ - run: npm test
21
+
22
+ lint:
23
+ name: Lint & format
24
+ runs-on: ubuntu-latest
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-node@v4
28
+ with:
29
+ node-version: "22"
30
+ - run: npm install
31
+ - name: Typecheck
32
+ run: npm run typecheck
33
+ - name: Oxlint (fast pre-filter)
34
+ run: npm run lint:fast
35
+ - name: ESLint
36
+ run: npm run lint
37
+ - name: Prettier format check
38
+ run: npm run format:check
@@ -1,35 +1,36 @@
1
1
  name: Publish to npm
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - master
4
+ push:
5
+ branches:
6
+ - master
7
7
 
8
8
  jobs:
9
- publish:
10
- runs-on: ubuntu-latest
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ contents: read
11
14
 
12
- steps:
13
- - name: Checkout code
14
- uses: actions/checkout@v4
15
+ steps:
16
+ - name: Checkout code
17
+ uses: actions/checkout@v4
15
18
 
16
- - uses: pnpm/action-setup@v4
17
- name: Install pnpm
18
- with:
19
- version: 9
20
- run_install: false
19
+ - uses: pnpm/action-setup@v4
20
+ name: Install pnpm
21
+ with:
22
+ version: 11
23
+ run_install: false
21
24
 
22
- - name: Install Node.js
23
- uses: actions/setup-node@v4
24
- with:
25
- node-version: 22
26
- cache: 'pnpm'
27
- registry-url: 'https://registry.npmjs.org'
25
+ - name: Install Node.js
26
+ uses: actions/setup-node@v4
27
+ with:
28
+ node-version: 22
29
+ cache: "pnpm"
30
+ registry-url: "https://registry.npmjs.org"
28
31
 
29
- - name: Install dependencies
30
- run: pnpm install
32
+ - name: Install dependencies
33
+ run: pnpm install
31
34
 
32
- - name: Publish to npm
33
- env:
34
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
35
- run: pnpm publish --no-git-checks
35
+ - name: Publish to npm
36
+ run: pnpm publish --no-git-checks
package/.oxlintrc.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "ignorePatterns": [
4
+ "node_modules",
5
+ ".precompiled",
6
+ "src/__tests__/magento_scenarios",
7
+ "src/__tests__/fixtures",
8
+ "src/__tests__/__mocks__",
9
+ "src/templates"
10
+ ],
11
+ "categories": {
12
+ "correctness": "error"
13
+ },
14
+ "env": {
15
+ "node": true,
16
+ "browser": true,
17
+ "es2024": true
18
+ }
19
+ }
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .precompiled
3
+ pnpm-lock.yaml
4
+ src/__tests__/magento_scenarios
5
+ src/__tests__/fixtures
6
+ src/__tests__/__mocks__
7
+ src/templates
@@ -0,0 +1,7 @@
1
+ {
2
+ "tabWidth": 4,
3
+ "printWidth": 100,
4
+ "semi": true,
5
+ "singleQuote": false,
6
+ "trailingComma": "all"
7
+ }
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Jeanmarcos
3
+ Copyright (c) 2024 Jeanmarcos Juarez
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/Q5Q816Z9WN)
2
+
2
3
  # MageObsidian JS Utils
3
4
 
4
5
  [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
@@ -0,0 +1,24 @@
1
+ import globals from "globals";
2
+ import preset from "./src/config/eslint.js";
3
+
4
+ // The build engine itself: ESM, Node runtime. Data trees under __tests__ and
5
+ // generated/template files are not linted.
6
+ export default [
7
+ {
8
+ ignores: [
9
+ "node_modules/**",
10
+ ".precompiled/**",
11
+ "src/__tests__/magento_scenarios/**",
12
+ "src/__tests__/fixtures/**",
13
+ "src/__tests__/__mocks__/**",
14
+ "src/templates/**",
15
+ ],
16
+ },
17
+ ...preset,
18
+ {
19
+ files: ["**/*.test.{js,ts}", "src/__tests__/**"],
20
+ languageOptions: {
21
+ globals: { ...globals.vitest },
22
+ },
23
+ },
24
+ ];
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "mage-obsidian",
3
- "version": "1.1.2",
3
+ "version": "2.0.0",
4
4
  "description": "Mage Obsidian Ultis",
5
- "main": "./src/scripts/buildThemes.js",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/mage-obsidian/js-package-utils.git"
8
+ },
9
+ "main": "./src/cli/buildThemes.ts",
6
10
  "keywords": [
7
11
  "Magento",
8
12
  "MageObsidian"
@@ -11,31 +15,53 @@
11
15
  "license": "MIT",
12
16
  "type": "module",
13
17
  "bin": {
14
- "mage-obsidian:build-themes": "./src/scripts/buildThemes.js"
18
+ "mage-obsidian:build-themes": "./src/cli/buildThemes.ts"
15
19
  },
16
20
  "imports": {
21
+ "#core/*": "./src/core/*",
22
+ "#vite/*": "./src/vite/*",
23
+ "#runtime/*": "./src/runtime/*",
24
+ "#cli/*": "./src/cli/*",
17
25
  "#utils/*": "./src/utils/*",
18
- "#config/*": "./src/config/*",
19
- "#service/*": "./src/service/*"
26
+ "#config/*": "./src/config/*"
20
27
  },
21
28
  "exports": {
22
- ".": "./src/scripts/buildThemes.js",
23
- "./service/": "./src/service/",
24
- "./utils/": "./src/utils/",
25
- "./config/": "./src/config/",
26
- "./service/*": "./src/service/*"
29
+ ".": "./src/cli/buildThemes.ts",
30
+ "./core/*": "./src/core/*",
31
+ "./vite/*": "./src/vite/*",
32
+ "./runtime/*": "./src/runtime/*",
33
+ "./cli/*": "./src/cli/*",
34
+ "./utils/*": "./src/utils/*",
35
+ "./config/*": "./src/config/*"
27
36
  },
28
37
  "dependencies": {
29
38
  "chalk": "^5.4.1",
30
39
  "commander": "^13.1.0",
31
40
  "deepmerge": "^4.3.1",
41
+ "dotenv": "^16.6.1",
32
42
  "readline-sync": "^1.4.10"
33
43
  },
34
44
  "devDependencies": {
35
- "jest": "^29.7.0",
36
- "jest-environment-node": "^29.7.0"
45
+ "@eslint/js": "^10.0.1",
46
+ "@types/node": "^22.19.21",
47
+ "eslint": "^10.5.0",
48
+ "eslint-config-prettier": "^10.1.8",
49
+ "eslint-plugin-vue": "^10.9.2",
50
+ "globals": "^17.6.0",
51
+ "oxlint": "^1.70.0",
52
+ "prettier": "^3.8.4",
53
+ "typescript": "^5.9.3",
54
+ "typescript-eslint": "^8.61.1",
55
+ "vitest": "^3.2.6"
37
56
  },
38
57
  "scripts": {
39
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
58
+ "test": "vitest run",
59
+ "test:watch": "vitest",
60
+ "typecheck": "tsc --noEmit",
61
+ "lint": "eslint .",
62
+ "lint:fix": "eslint . --fix",
63
+ "lint:fast": "oxlint",
64
+ "format": "prettier --write .",
65
+ "format:check": "prettier --check ."
40
66
  }
41
67
  }
@@ -0,0 +1,4 @@
1
+ # esbuild (pulled in by Vitest) ships its binary via the platform-specific
2
+ # @esbuild/<platform> package, so its postinstall script is not required.
3
+ allowBuilds:
4
+ esbuild: false
@@ -0,0 +1,263 @@
1
+ #! /usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { execSync, spawn } from "child_process";
4
+ import os from "os";
5
+ import readlineSync from "readline-sync";
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import chalk from "chalk";
9
+ import configResolver from "../core/configResolver.ts";
10
+ import preCompileMagentoFiles from "../core/preCompileMagentoFiles.ts";
11
+ import runWithConcurrency from "../utils/runWithConcurrency.ts";
12
+ import dotenv from "dotenv";
13
+ dotenv.config();
14
+
15
+ const program = new Command();
16
+
17
+ program
18
+ .option("--theme <theme>", "Specify the active theme")
19
+ .option("--dev-server", "Run the development server for a specific theme")
20
+ .option("--typecheck", "Type-check theme sources with vue-tsc instead of building");
21
+
22
+ program.parse(process.argv);
23
+
24
+ const options = program.opts();
25
+ const themesConfig = configResolver.getMagentoConfig().themes;
26
+
27
+ if (options.devServer && !options.theme) {
28
+ console.error(chalk.red("Error: The --theme option is required when using --dev-server."));
29
+ process.exit(1);
30
+ }
31
+
32
+ const theme = options.theme || "all";
33
+
34
+ const executeCommand = (command) => {
35
+ try {
36
+ execSync(command, { stdio: "inherit" });
37
+ } catch (error) {
38
+ console.error(chalk.red(`Command failed: ${command}`), error);
39
+ process.exit(1);
40
+ }
41
+ };
42
+
43
+ function tryCreateEnvFile() {
44
+ const defaultEnvVars = {
45
+ VITE_SERVER_HOST: "phpfpm",
46
+ VITE_SERVER_PORT: "5173",
47
+ VITE_SERVER_SECURE: "true",
48
+ VITE_HMR_PATH: "/__vite_ping",
49
+ MAGENTO_HOST: "magento.test",
50
+ VITE_SERVER_ALLOWED_HOSTS: "magento.test,localhost",
51
+ };
52
+
53
+ console.log(chalk.blue("Creating `.env` file with default or user-provided values..."));
54
+
55
+ const envFilePath = path.resolve(process.cwd(), ".env");
56
+ if (fs.existsSync(envFilePath)) {
57
+ console.log(chalk.green(".env file already exists."));
58
+ return;
59
+ }
60
+
61
+ let envContent = "";
62
+ for (const [key, defaultValue] of Object.entries(defaultEnvVars)) {
63
+ const userInput = readlineSync.question(
64
+ chalk.yellow(`Enter value for ${key} (default: ${defaultValue}): `),
65
+ {
66
+ defaultInput: defaultValue,
67
+ },
68
+ );
69
+ envContent += `${key}=${userInput || defaultValue}\n`;
70
+ }
71
+
72
+ fs.writeFileSync(envFilePath, envContent);
73
+ console.log(chalk.green(".env file created successfully."));
74
+ }
75
+
76
+ function validateEnv() {
77
+ const requiredEnvVars = [
78
+ "VITE_SERVER_HOST",
79
+ "VITE_SERVER_PORT",
80
+ "VITE_SERVER_SECURE",
81
+ "VITE_HMR_PATH",
82
+ "MAGENTO_HOST",
83
+ "VITE_SERVER_ALLOWED_HOSTS",
84
+ ];
85
+ const missingEnvVars = [];
86
+ for (const envVar of requiredEnvVars) {
87
+ if (!process.env[envVar]) {
88
+ missingEnvVars.push(envVar);
89
+ }
90
+ }
91
+ if (missingEnvVars.length > 0) {
92
+ console.error(
93
+ chalk.red(`Missing required environment variables: ${missingEnvVars.join(", ")}`),
94
+ );
95
+ tryCreateEnvFile();
96
+ console.log(chalk.green("Please restart the script after setting up the .env file."));
97
+ process.exit(1);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Build a single theme in its own child process. Each child gets its own
103
+ * CURRENT_THEME env so several themes can build concurrently without sharing
104
+ * in-process state. Output is buffered and printed as a labeled block on exit
105
+ * to keep parallel logs readable. Never rejects — failures surface via the
106
+ * returned exit code so sibling builds aren't aborted.
107
+ *
108
+ * @param {string} themeName
109
+ * @returns {Promise<{ themeName: string, code: number }>}
110
+ */
111
+ const spawnBuild = (themeName) =>
112
+ new Promise<{ themeName: string; code: number }>((resolve) => {
113
+ const child = spawn("vite build", {
114
+ shell: true,
115
+ env: { ...process.env, CURRENT_THEME: themeName },
116
+ });
117
+ const chunks = [];
118
+ child.stdout.on("data", (data) => chunks.push(data));
119
+ child.stderr.on("data", (data) => chunks.push(data));
120
+ const finish = (code) => {
121
+ console.log(chalk.cyan(`\n=== ${themeName} ===`));
122
+ process.stdout.write(Buffer.concat(chunks).toString());
123
+ if (code === 0) {
124
+ console.log(chalk.green(`✓ ${themeName} built`));
125
+ } else {
126
+ console.error(chalk.red(`✗ ${themeName} failed (exit ${code})`));
127
+ }
128
+ resolve({ themeName, code });
129
+ };
130
+ child.on("close", finish);
131
+ child.on("error", (error) => {
132
+ chunks.push(Buffer.from(String(error?.stack ?? error)));
133
+ finish(1);
134
+ });
135
+ });
136
+
137
+ const buildThemes = async (themeNames) => {
138
+ const cores = os.cpus()?.length ?? 2;
139
+ const limit = Math.max(1, Math.min(themeNames.length, cores - 1));
140
+ console.log(
141
+ chalk.cyan(
142
+ `Building ${themeNames.length} theme(s) (concurrency ${limit}): ${themeNames.join(", ")}`,
143
+ ),
144
+ );
145
+
146
+ const results = await runWithConcurrency(
147
+ themeNames,
148
+ (themeName) => spawnBuild(themeName),
149
+ limit,
150
+ );
151
+
152
+ const failed = results.filter((result) => result.code !== 0);
153
+ if (failed.length > 0) {
154
+ console.error(
155
+ chalk.red(`Build failed for: ${failed.map((result) => result.themeName).join(", ")}`),
156
+ );
157
+ process.exit(1);
158
+ }
159
+ };
160
+
161
+ /**
162
+ * Type-check one assembled theme with vue-tsc. The theme's generated
163
+ * tsconfig.typecheck.json (materialized by precompile) carries the full
164
+ * `Vendor_Module::` paths, so every cross-module seam resolves. vue-tsc (not
165
+ * plain tsc) is required to check inside `<script setup lang="ts">` SFCs. Output
166
+ * is buffered and printed as a labeled block; never rejects — failures surface
167
+ * via the exit code so sibling themes still run.
168
+ *
169
+ * @param {string} themeName
170
+ * @returns {Promise<{ themeName: string, code: number }>}
171
+ */
172
+ const spawnTypecheck = (themeName) =>
173
+ new Promise<{ themeName: string; code: number }>((resolve) => {
174
+ const themeDefinition = configResolver.getThemeDefinition(themeName);
175
+ const tsconfigPath = path.join(themeDefinition.src, "tsconfig.typecheck.json");
176
+ const child = spawn(`vue-tsc --noEmit -p "${tsconfigPath}"`, {
177
+ shell: true,
178
+ env: { ...process.env, CURRENT_THEME: themeName },
179
+ });
180
+ const chunks = [];
181
+ child.stdout.on("data", (data) => chunks.push(data));
182
+ child.stderr.on("data", (data) => chunks.push(data));
183
+ const finish = (code) => {
184
+ console.log(chalk.cyan(`\n=== ${themeName} (typecheck) ===`));
185
+ process.stdout.write(Buffer.concat(chunks).toString());
186
+ if (code === 0) {
187
+ console.log(chalk.green(`✓ ${themeName} type-checks`));
188
+ } else {
189
+ console.error(chalk.red(`✗ ${themeName} has type errors (exit ${code})`));
190
+ }
191
+ resolve({ themeName, code });
192
+ };
193
+ child.on("close", finish);
194
+ child.on("error", (error) => {
195
+ chunks.push(Buffer.from(String(error?.stack ?? error)));
196
+ finish(1);
197
+ });
198
+ });
199
+
200
+ const typecheckThemes = async (themeNames) => {
201
+ // Materialize each theme's tsconfig.typecheck.json + inheritance map in-process
202
+ // (idempotent, cached by contract hash) before spawning vue-tsc against it.
203
+ for (const themeName of themeNames) {
204
+ await preCompileMagentoFiles(themeName);
205
+ }
206
+ console.log(
207
+ chalk.cyan(`Type-checking ${themeNames.length} theme(s): ${themeNames.join(", ")}`),
208
+ );
209
+
210
+ const results = await runWithConcurrency(
211
+ themeNames,
212
+ (themeName) => spawnTypecheck(themeName),
213
+ 1,
214
+ );
215
+
216
+ const failed = results.filter((result) => result.code !== 0);
217
+ if (failed.length > 0) {
218
+ console.error(
219
+ chalk.red(`Type check failed for: ${failed.map((r) => r.themeName).join(", ")}`),
220
+ );
221
+ process.exit(1);
222
+ }
223
+ };
224
+
225
+ const runDevServer = (themeName) => {
226
+ console.log(chalk.cyan(`Starting development server for theme: ${themeName}`));
227
+ try {
228
+ const theme = themesConfig[themeName];
229
+ if (!theme) {
230
+ console.error(chalk.red(`Theme "${themeName}" does not exist.`));
231
+ process.exit(1);
232
+ }
233
+
234
+ process.env.CURRENT_THEME = themeName;
235
+ executeCommand("vite");
236
+ } catch (error) {
237
+ console.error(
238
+ chalk.red(`Failed to start development server for theme "${themeName}":`),
239
+ error,
240
+ );
241
+ }
242
+ };
243
+
244
+ validateEnv();
245
+
246
+ if (options.devServer) {
247
+ runDevServer(theme);
248
+ } else {
249
+ let themeNames;
250
+ if (theme === "all") {
251
+ themeNames = Object.keys(themesConfig);
252
+ } else if (themesConfig[theme]) {
253
+ themeNames = [theme];
254
+ } else {
255
+ console.error(chalk.red(`Theme "${theme}" does not exist.`));
256
+ process.exit(1);
257
+ }
258
+ if (options.typecheck) {
259
+ await typecheckThemes(themeNames);
260
+ } else {
261
+ await buildThemes(themeNames);
262
+ }
263
+ }
@@ -0,0 +1,31 @@
1
+ import path from "node:path";
2
+
3
+ // The Vite harness runs from the `vite/` dir one level below the Magento root.
4
+ // Allow an explicit root override so the engine is not tied to that cwd layout
5
+ // (e.g. when invoked from elsewhere or in tests).
6
+ export const MAGENTO_ROOT = process.env.MAGE_OBSIDIAN_MAGENTO_ROOT
7
+ ? path.resolve(process.env.MAGE_OBSIDIAN_MAGENTO_ROOT)
8
+ : path.resolve(process.cwd(), "..");
9
+
10
+ export const DEPENDENCY_CONFIG_FILENAME = "mage_obsidian_frontend_modules.json";
11
+ export const MAGENTO_CONFIG_RELATIVE_PATH = "app/etc/";
12
+ export const DEPENDENCY_CONFIG_FILE_PATH = path.resolve(
13
+ MAGENTO_ROOT,
14
+ MAGENTO_CONFIG_RELATIVE_PATH,
15
+ DEPENDENCY_CONFIG_FILENAME,
16
+ );
17
+
18
+ export const MODULE_WEB_PATH = "view/frontend/web/";
19
+ export const MODULE_TEMPLATES_PATH = "view/frontend/templates";
20
+ export const THEME_MODULE_WEB_PATH = "web/";
21
+ export const MODULE_CSS_FOLDER = "css";
22
+
23
+ export const THEME_CSS_FOLDER = "css";
24
+
25
+ export const OUTPUT_THEME_DIR = "web/generated";
26
+ export const OUTPUT_JS_DIR = "js/";
27
+ export const OUTPUT_CSS_DIR = "css/";
28
+ export const PRECOMPILED_FOLDER = ".precompiled";
29
+ export const INTERCEPTORS_FOLDER = "interceptors";
30
+
31
+ export const ALL_JS_VUE_FILES_WITH_INHERITANCE_FILE_NAME = "allJsVueFilesWithInheritance.json";
@@ -0,0 +1,60 @@
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import pluginVue from "eslint-plugin-vue";
4
+ import tseslint from "typescript-eslint";
5
+ import prettier from "eslint-config-prettier/flat";
6
+
7
+ /**
8
+ * Shared flat ESLint preset for the MageObsidian stack. Consumers spread the
9
+ * default export and add their own `files`/`ignores`. Every plugin is imported
10
+ * here (inside the mage-obsidian package), so a consuming repo only needs
11
+ * `eslint` itself installed — the plugins resolve relative to this file.
12
+ *
13
+ * JS today, TypeScript after the engine migration: the TS rules are scoped to
14
+ * `*.ts*` and the Vue `<script>` parser is already the TS parser, so `.vue`
15
+ * files lint whether their script block is JS or TS.
16
+ */
17
+ const unusedVarsOptions = {
18
+ argsIgnorePattern: "^_",
19
+ varsIgnorePattern: "^_",
20
+ caughtErrors: "none",
21
+ };
22
+
23
+ export default tseslint.config(
24
+ js.configs.recommended,
25
+ {
26
+ languageOptions: {
27
+ ecmaVersion: "latest",
28
+ sourceType: "module",
29
+ globals: { ...globals.node, ...globals.browser },
30
+ },
31
+ },
32
+ {
33
+ // Plain JS (author code, engine bits not yet migrated): the base rule.
34
+ files: ["**/*.{js,mjs,cjs}"],
35
+ rules: {
36
+ "no-unused-vars": ["error", unusedVarsOptions],
37
+ },
38
+ },
39
+ {
40
+ files: ["**/*.{ts,mts,cts,tsx}"],
41
+ extends: [tseslint.configs.recommended],
42
+ rules: {
43
+ // tseslint disables the base rule for TS; use the TS-aware one so
44
+ // labels in function-type annotations aren't flagged as unused.
45
+ "@typescript-eslint/no-unused-vars": ["error", unusedVarsOptions],
46
+ // Lenient migration baseline: the engine is typed progressively, so
47
+ // explicit `any` is allowed but surfaced as a warning (ratchet
48
+ // target, alongside tsconfig `strict`).
49
+ "@typescript-eslint/no-explicit-any": "warn",
50
+ },
51
+ },
52
+ {
53
+ files: ["**/*.vue"],
54
+ extends: [pluginVue.configs["flat/recommended"]],
55
+ languageOptions: {
56
+ parserOptions: { parser: tseslint.parser },
57
+ },
58
+ },
59
+ prettier,
60
+ );