@asor-studio/asor-cli 1.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 (33) hide show
  1. package/LICENSE +24 -0
  2. package/README.md +293 -0
  3. package/cli-command/generate-atom.js +2 -0
  4. package/cli-command/generate-molecule.js +2 -0
  5. package/cli-command/generate-organism.js +2 -0
  6. package/cli-command/generate-page.js +2 -0
  7. package/cli-command/setup-asor-core.js +2 -0
  8. package/cli.js +2 -0
  9. package/gen-template/generate-atom/atom.html.template +26 -0
  10. package/gen-template/generate-atom/atom.ts.template +69 -0
  11. package/gen-template/generate-molecule/molecule.html.template +41 -0
  12. package/gen-template/generate-molecule/molecule.ts.template +69 -0
  13. package/gen-template/generate-organism/organism.html.template +41 -0
  14. package/gen-template/generate-organism/organism.ts.template +69 -0
  15. package/gen-template/generate-page/page.component.html.template +63 -0
  16. package/gen-template/generate-page/page.component.ts.template +56 -0
  17. package/gen-template/setup-asor-core/src/app/app.config.ts.template +46 -0
  18. package/gen-template/setup-asor-core/src/app/app.routes.ts.template +40 -0
  19. package/gen-template/setup-asor-core/src/app/config/app-asor.config.ts.template +64 -0
  20. package/gen-template/setup-asor-core/src/app/config/app-cache.config.ts.template +26 -0
  21. package/gen-template/setup-asor-core/src/app/config/app-state.config.ts.template +26 -0
  22. package/gen-template/setup-asor-core/src/app/config/app.config.ts.template +62 -0
  23. package/gen-template/setup-asor-core/src/app/config/interfaces/app-state.interfaces.ts.template +5 -0
  24. package/gen-template/setup-asor-core/src/app/molecules/molecule-sample-widget/sample-widget.molecule.html.template +84 -0
  25. package/gen-template/setup-asor-core/src/app/molecules/molecule-sample-widget/sample-widget.molecule.ts.template +56 -0
  26. package/gen-template/setup-asor-core/src/app/pages/page-home/asor-logo.ts.template +1 -0
  27. package/gen-template/setup-asor-core/src/app/pages/page-home/home.component.html.template +521 -0
  28. package/gen-template/setup-asor-core/src/app/pages/page-home/home.component.ts.template +99 -0
  29. package/gen-template/setup-asor-core/src/assets/i18n/app/molecule-sample-widget/en.json.template +7 -0
  30. package/gen-template/setup-asor-core/src/assets/i18n/app/molecule-sample-widget/it.json.template +7 -0
  31. package/gen-template/setup-asor-core/src/assets/i18n/app/page-home/en.json.template +19 -0
  32. package/gen-template/setup-asor-core/src/assets/i18n/app/page-home/it.json.template +19 -0
  33. package/package.json +31 -0
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ Software: asor-cli
2
+ Proprietary / All Rights Reserved
3
+ Copyright (c) 2026 robrosc (Owner of asor-studio). All rights reserved.
4
+
5
+ NOTICE: All information contained herein is, and remains the property of robrosc (Owner of asor-studio).
6
+ The intellectual and technical concepts contained herein are proprietary to robrosc (Owner of asor-studio) and may be covered by patents, patents in process, and are protected by trade secret or copyright law.
7
+
8
+ BY DOWNLOADING, INSTALLING, OR USING THE SOFTWARE, YOU AGREE TO BE BOUND BY THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO THESE TERMS, DO NOT DOWNLOAD, INSTALL, OR USE THE SOFTWARE.
9
+
10
+ 1. LICENSE GRANT
11
+ Subject to the terms of this Agreement, you are granted a non-exclusive, non-transferable, revocable license to download and use the "asor-cli" tool (the "Software") solely to initialize and scaffold your software projects with the "@asor-studio/asor-core" library, whether for commercial or non-commercial purposes.
12
+
13
+ 2. RESTRICTIONS
14
+ You are strictly prohibited from:
15
+ a. Modifying, translating, adapting, or otherwise creating derivative works or improvements, whether or not patentable, of the Software or any part thereof.
16
+ b. Decompiling, disassembling, reverse engineering, or attempting to derive the source code, underlying ideas, algorithms, structure, or organization of the Software.
17
+ c. Redistributing, selling, leasing, licensing, renting, or re-publishing the Software, in whole or in part, including any modified versions or derivative works, on npm, GitHub Packages, or any other public or private registry or repository.
18
+ d. Removing, altering, or obscuring any copyright, trademark, patent, or other intellectual property or proprietary rights notices from the Software.
19
+
20
+ 3. TERMINATION
21
+ This license will terminate automatically if you fail to comply with any of the limitations or requirements described herein. Upon termination, you must immediately cease all use of the Software and destroy all copies, full or partial, of the Software.
22
+
23
+ 4. NO WARRANTY
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # @asor-studio/asor-cli
2
+
3
+ Official CLI for the **ASOR Studio** ecosystem. It scaffolds and configures Angular projects with [`@asor-studio/asor-core`](https://www.npmjs.com/package/@asor-studio/asor-core).
4
+
5
+ ## Mission
6
+
7
+ **ASOR Studio** (_Application Stability & Organizational Research_) focuses on two software engineering pillars:
8
+
9
+ 1. **Code Organization**: Atomic Design structure and strict separation of concerns for scalable projects.
10
+ 2. **Data Conservation**: Persistent, resilient state that survives navigation and refreshes.
11
+
12
+ `asor-cli` automates setup and code generation so you can focus on application logic.
13
+
14
+ ---
15
+
16
+ ## Requirements
17
+
18
+ - **Node.js** >= 18
19
+ - An existing **Angular** project with `angular.json` at the root
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ Install globally with npm:
26
+
27
+ ```bash
28
+ npm install -g @asor-studio/asor-cli
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Usage
34
+
35
+ `asor-cli` is designed to be used **inside an Angular project**. If you are starting from scratch, create the Angular app first, move into the project folder, and then run the ASOR setup.
36
+
37
+ ### Typical startup flow
38
+
39
+ ```bash
40
+ ng new my-angular-project
41
+ cd my-angular-project
42
+ asor setup
43
+ ```
44
+
45
+ This means:
46
+
47
+ 1. `ng new my-angular-project` creates a standard Angular workspace
48
+ 2. `cd my-angular-project` moves into the project root where `angular.json` is located
49
+ 3. `asor setup` configures the Angular project for the ASOR Studio ecosystem
50
+
51
+ Once setup is complete, you can use the generators to create pages, atoms, molecules, and organisms following the ASOR structure.
52
+
53
+ ```bash
54
+ asor <command>
55
+ asor -g <type> <name>
56
+ ```
57
+
58
+ To list commands, run:
59
+
60
+ ```bash
61
+ asor help
62
+ ```
63
+
64
+ ### Current CLI Help
65
+
66
+ The current CLI output is:
67
+
68
+ ```bash
69
+ >>> Asor CLI
70
+
71
+ Usage:
72
+
73
+ asor <command>
74
+ asor -g <type> <name>
75
+
76
+ Commands:
77
+ setup Initialize a project with @asor-studio/asor-core
78
+ help Show this help message
79
+
80
+ Generate:
81
+ -g [-full] [-storage] -page <name> Generate a new page component
82
+ -g [-full] [-in <page>] -molecule <name> Generate a new molecule component
83
+ -g [-full] [-in <page>] -atom <name> Generate a new atom component
84
+ -g [-full] [-in <page>] -organism <name> Generate a new organism component
85
+
86
+ Flags (for -page, -molecule, -atom, -organism):
87
+ -storage Create using BaseStorage* version instead of Base*
88
+ -full Enable i18n and automatic injection
89
+
90
+ Specific Flags (for -molecule, -atom, -organism):
91
+ -in <page> Target page for injection (requires -full)
92
+ ```
93
+
94
+ ## Commands
95
+
96
+ ### `asor setup`
97
+
98
+ Run `asor setup` from the root of an Angular project.
99
+
100
+ If you do not have an Angular project yet, create one first:
101
+
102
+ ```bash
103
+ ng new my-angular-project
104
+ cd my-angular-project
105
+ ```
106
+
107
+ Then run:
108
+
109
+ ```bash
110
+ asor setup
111
+ ```
112
+
113
+ `asor setup` must be executed in the folder that contains `angular.json`, otherwise the CLI cannot scaffold and configure the project correctly.
114
+
115
+ The CLI asks for:
116
+
117
+ | Prompt | Example | Description |
118
+ | ------------ | --------------------------- | --------------------------------------------------------- |
119
+ | **Prefix** | `App`, `Nexus`, `MyProject` | Used for config classes, files, and i18n namespaces |
120
+ | **baseHref** | `/`, `/nexus-os/` | Angular base href injected into `angular.json` and config |
121
+
122
+ #### What `asor setup` does
123
+
124
+ 1. Installs `@asor-studio/asor-core` and `crypto-js`
125
+ 2. Creates Atomic Design folders in `src/app/`
126
+ 3. Copies scaffold files into the Angular app
127
+ 4. Generates i18n folders for `home`, `login`, and `common`
128
+ 5. Updates `angular.json` with the chosen `baseHref`
129
+ 6. Injects `<asor-core-widget>` in the root component when possible
130
+ 7. Saves CLI metadata into `asor-core.json`
131
+
132
+ #### Generated structure
133
+
134
+ Main generated files include:
135
+
136
+ - `<prefix>.config.ts`
137
+ - `<prefix>-cache.config.ts`
138
+ - `<prefix>-asor.config.ts`
139
+ - `<prefix>-state.config.ts`
140
+ - `app.routes.ts`
141
+
142
+ #### Next steps after setup
143
+
144
+ 1. Edit `src/app/config/<prefix>-asor.config.ts`
145
+ 2. Review `src/app/app.routes.ts`
146
+ 3. Start generating pages and design building blocks
147
+
148
+ ### `asor help`
149
+
150
+ Shows the command list and generator flags:
151
+
152
+ ```bash
153
+ asor help
154
+ ```
155
+
156
+ ### `asor -g` (Generate)
157
+
158
+ The generator scaffolds Atomic Design building blocks following the ASOR conventions.
159
+
160
+ #### Common flags
161
+
162
+ - `-storage`: generate the storage-aware base class variant
163
+ - `-full`: enable extra integration such as i18n/config/route injection
164
+
165
+ #### `asor -g -page <name>`
166
+
167
+ ```bash
168
+ asor -g -page user-profile
169
+ ```
170
+
171
+ Creates:
172
+
173
+ - `src/app/pages/page-<name>/<name>.component.ts`
174
+ - `src/app/pages/page-<name>/<name>.component.html`
175
+
176
+ Behavior:
177
+
178
+ - embeds a minimal, overridable CSS base directly inside the HTML template
179
+ - generates `en.json` and `it.json`
180
+ - updates config extension placeholders
181
+ - with `-full`, injects the page route into `src/app/app.routes.ts`
182
+
183
+ Example:
184
+
185
+ ```bash
186
+ asor -g -full -storage -page user-profile
187
+ ```
188
+
189
+ #### `asor -g -molecule <name>`
190
+
191
+ ```bash
192
+ asor -g -molecule menu-bar
193
+ ```
194
+
195
+ Creates:
196
+
197
+ - `src/app/molecules/molecule-<name>/<name>.molecule.ts`
198
+ - `src/app/molecules/molecule-<name>/<name>.molecule.html`
199
+
200
+ Behavior:
201
+
202
+ - embeds a minimal, overridable CSS base directly inside the HTML template
203
+ - generates i18n files
204
+ - updates translation config placeholders
205
+ - registers logs in ASOR config
206
+ - with `-full -in <page>`, injects the molecule into the target page definition in `app.routes.ts`
207
+
208
+ Example:
209
+
210
+ ```bash
211
+ asor -g -full -molecule menu-bar -in dashboard
212
+ ```
213
+
214
+ #### `asor -g -atom <name>`
215
+
216
+ ```bash
217
+ asor -g -atom status-badge
218
+ ```
219
+
220
+ Creates:
221
+
222
+ - `src/app/atoms/atom-<name>/<name>.atom.ts`
223
+ - `src/app/atoms/atom-<name>/<name>.atom.html`
224
+
225
+ Behavior:
226
+
227
+ - embeds a minimal, overridable CSS base directly inside the HTML template
228
+ - generates i18n files
229
+ - updates translation config placeholders
230
+ - registers logs in ASOR config
231
+ - with `-full -in <page>`, injects the atom into the target page definition in `app.routes.ts`
232
+
233
+ Example:
234
+
235
+ ```bash
236
+ asor -g -full -atom status-badge -in dashboard
237
+ ```
238
+
239
+ #### `asor -g -organism <name>`
240
+
241
+ ```bash
242
+ asor -g -organism dashboard-shell
243
+ ```
244
+
245
+ Creates:
246
+
247
+ - `src/app/organisms/organism-<name>/<name>.organism.ts`
248
+ - `src/app/organisms/organism-<name>/<name>.organism.html`
249
+
250
+ Behavior:
251
+
252
+ - embeds a minimal, overridable CSS base directly inside the HTML template
253
+ - generates i18n files
254
+ - updates translation config placeholders
255
+ - registers logs in ASOR config
256
+ - with `-full -in <page>`, injects the organism into the target page definition in `app.routes.ts`
257
+
258
+ Example:
259
+
260
+ ```bash
261
+ asor -g -full -storage -organism dashboard-shell -in dashboard
262
+ ```
263
+
264
+ ## Notes
265
+
266
+ The generator logic relies on placeholders inside ASOR scaffolded files such as:
267
+
268
+ - `// [ASOR-INJECT:ROUTE-IMPORTS]`
269
+ - `// [ASOR-INJECT:ROUTE-DEFS]`
270
+ - `// [ASOR-INJECT:MOLECULE-DEFS(<page>)]`
271
+ - `// [ASOR-INJECT:ATOM-DEFS(<page>)]`
272
+ - `// [ASOR-INJECT:ORGANISM-DEFS(<page>)]`
273
+ - `// [ASOR-INJECT:ROUTE-EXTENSIONS]`
274
+ - `// [ASOR-INJECT:TRANSLATION-EXTENSIONS]`
275
+ - `// [ASOR-INJECT:LOG-IMPORTS]`
276
+ - `// [ASOR-INJECT:LOG-REGISTRATIONS]`
277
+
278
+ If these placeholders are removed from the generated project, automatic injection may stop working.
279
+
280
+ ---
281
+
282
+ ## License
283
+
284
+ This software is the property of **robrosc**, owner and creator of the **asor-studio** organization.
285
+
286
+ See the [LICENSE](./LICENSE) file for the full license agreement.
287
+
288
+ ---
289
+
290
+ ## Documentation
291
+
292
+ Full documentation and usage examples:
293
+ [ASOR Studio Wiki](https://asor-studio.github.io/asor-angular-wiki)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const e=require("fs"),n=require("path");function parseComponentName(e){const n=e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase().split("-").filter(Boolean);return{kebab:n.join("-"),pascal:n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}}function getAngularRoot(t){let o=n.resolve(t);for(;o!==n.parse(o).root;){if(e.existsSync(n.join(o,"angular.json")))return o;o=n.dirname(o)}return null}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildI18nEn(e,n){return{[`${n}.TITLE`]:`${e} Atom`,[`${n}.VALUE`]:`${e} value`}}function buildI18nIt(e,n){return{[`${n}.TITLE`]:`Atomo ${e}`,[`${n}.VALUE`]:`Valore ${e}`}}function patchExtensionsBlock(e,n,t,o){const s=`// [ASOR-INJECT:${n}]`;if(!e.includes(s))return e;const i=e.split(s)[0],r=i.lastIndexOf("{");if(-1!==r){const n=i.slice(r);if(new RegExp(`\\b${t}\\b`).test(n))return e}else if(new RegExp(`\\b${t}\\b`).test(e))return e;const c=`${t}: '${o}',`,a=new RegExp(`^([ \\t]*)${escapeRegExp(s)}`,"m"),l=e.match(a),p=l?l[1]:" ";return e.replace(s,`${c}\n${p}${s}`)}function findAppConfigFile(t){const o=n.join(t,"src","app","config");if(!e.existsSync(o))return null;const s=e.readdirSync(o).filter(e=>e.endsWith(".config.ts"));for(const t of s)if(e.readFileSync(n.join(o,t),"utf8").includes("extends ConfigConst"))return n.join(o,t);return null}function patchAsorConfigLogsBlock(t,o,s,i){const r=n.join(t,"src","app","config");let c=null;if(e.existsSync(r)){const t=e.readdirSync(r).filter(e=>e.endsWith("-asor.config.ts")),o=i||"app";t.includes(`${o}-asor.config.ts`)?c=n.join(r,`${o}-asor.config.ts`):t.length>0&&(c=n.join(r,t[0]))}if(!c){const o=n.join(t,"src","app","app.root.ts");e.existsSync(o)&&(c=o)}if(!c)return!1;let a=e.readFileSync(c,"utf8");const l=c.endsWith("app.root.ts"),p=l?"import { RouterModule":"// [ASOR-INJECT:LOG-IMPORTS]",u=`import { ${s}Atom } from '${l?`./atoms/atom-${o}/${o}.atom`:`../atoms/atom-${o}/${o}.atom`}';\n`;a.includes(`import { ${s}Atom }`)||(a.includes(p)?a=a.replace(p,`${u}${p}`):l&&(a=`${u}${a}`));const f=`ConsoleLogsConfig.setClassLevels(${s}Atom.className, []);`,g=l?"this.stateService.initialize":"// [ASOR-INJECT:LOG-REGISTRATIONS]";if(!a.includes(f)){if(a.includes(g)){const e=new RegExp(`^([ \\t]*)${escapeRegExp(g)}`,"m"),n=a.match(e),t=n?n[1]:" ";a=a.replace(g,`${f}\n${t}${g}`)}else{if(!l)return!1;a=a.replace(/constructor\(\) \{/,`constructor() {\n\t\t${f}`)}return e.writeFileSync(c,a,"utf8"),!0}return!1}function patchRoutesWithAtom(t,o,s,i,r,c){const a=n.join(t,"src","app","app.routes.ts");if(!e.existsSync(a))return console.warn(`⚠️ Cannot find app.routes.ts at ${a}. Skipping injection.`),!1;let l=e.readFileSync(a,"utf8");const p=`${r.charAt(0).toUpperCase()+r.slice(1)}Config`,u="// [ASOR-INJECT:ROUTE-IMPORTS]",f=`import { ${s}Atom } from './atoms/atom-${o}/${o}.atom';\n`;!l.includes(`import { ${s}Atom }`)&&l.includes(u)&&(l=l.replace(u,`${f}${u}`));const g=`{\n Atom: ${s}Atom,\n I18nPath: [${p}.TranslationUrl.${c}],\n },`,m=`// [ASOR-INJECT:ATOM-DEFS(${i})]`;if(l.includes(m)){const n=new RegExp(`^([ \\t]*)${escapeRegExp(m)}`,"m"),t=l.match(n),o=t?t[1]:" ";return l=l.replace(m,`${g}\n${o}${m}`),e.writeFileSync(a,l,"utf8"),!0}return console.warn(`⚠️ Could not find '${m}' placeholder in app.routes.ts.`),!1}function generateAtom(t,o={isStorage:!1,isFull:!1,inPage:null}){t&&""!==t.trim()||(console.error("❌ Please provide an atom name."),process.exit(1));const{kebab:s,pascal:i}=parseComponentName(t),r=s.replace(/-/g,"_").toUpperCase(),c=getAngularRoot(process.cwd());c||(console.error("❌ No angular.json found."),process.exit(1));const a=n.join(c,"src","app","atoms");e.existsSync(a)||e.mkdirSync(a,{recursive:!0});const l=`atom-${s}`,p=n.join(a,l);e.existsSync(p)&&(console.error(`❌ Component folder already exists: ${p}`),process.exit(1)),e.mkdirSync(p,{recursive:!0}),console.log(`\n📁 Created folder: src/app/atoms/${l}`);const u=o.isStorage?"BaseStorageAtom":"BaseAtom",f=`extends ${u}`,g=n.join(__dirname,"..","gen-template","generate-atom");let m=e.readFileSync(n.join(g,"atom.ts.template"),"utf8"),$=e.readFileSync(n.join(g,"atom.html.template"),"utf8");const replacePlaceholders=e=>e.replace(/__KEBAB__/g,s).replace(/__PASCAL__/g,i).replace(/__ROUTE_KEY__/g,r).replace(/__BASE_CLASS_NAME__/g,u).replace(/__BASE_CLASS_EXTENDS__/g,f);m=replacePlaceholders(m),$=replacePlaceholders($);const d=[{name:`${s}.atom.ts`,content:m},{name:`${s}.atom.html`,content:$}];for(const t of d)e.writeFileSync(n.join(p,t.name),t.content,"utf8"),console.log(`✅ Generated: src/app/atoms/${l}/${t.name}`);let S=null;const A=n.join(c,"asor-core.json");if(e.existsSync(A))try{S=JSON.parse(e.readFileSync(A,"utf8")).prefix}catch(e){}if(!S){const t=e.existsSync(n.join(c,"public"))?n.join(c,"public","assets"):n.join(c,"src","assets"),o=n.join(t,"i18n");e.existsSync(o)&&(S=e.readdirSync(o,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)[0]||null)}if(S){console.log("\n🌍 Creating i18n structure...");const t=e.existsSync(n.join(c,"public"))?n.join(c,"public","assets"):n.join(c,"src","assets"),o=n.join(t,"i18n"),a=`atom-${s}`,l=n.join(o,S,a);e.existsSync(l)||e.mkdirSync(l,{recursive:!0});const p=[{lang:"en",content:buildI18nEn(i,r)},{lang:"it",content:buildI18nIt(i,r)}];for(const{lang:t,content:o}of p){const s=n.join(l,`${t}.json`);e.writeFileSync(s,JSON.stringify(o,null,"\t")+"\n","utf8"),console.log(`✅ Generated: assets/i18n/${S}/${a}/${t}.json`)}console.log("\n⚙️ Patching translation URL in central config...");const u=findAppConfigFile(c);if(u){let n=e.readFileSync(u,"utf8");const t=patchExtensionsBlock(n,"TRANSLATION-EXTENSIONS",r,`/${S}/${a}`);t!==n&&(e.writeFileSync(u,t,"utf8"),console.log(`✅ Added translation key ${r} in central config`))}console.log("\n📝 Registering atom logs..."),patchAsorConfigLogsBlock(c,s,i,S)&&console.log(`✅ Registered ${i}Atom logs in asor config`)}else console.warn("⚠️ Could not detect i18n prefix folder. Skipping i18n generation.");if(o.isFull&&o.inPage){console.log(`\n🔗 Injecting atom in app.routes.ts for page "${o.inPage}"...`);const e=S||"app";patchRoutesWithAtom(c,s,i,o.inPage,e,r)&&console.log("✅ Atom successfully injected into app.routes.ts")}console.log(`\n🎉 Atom "${i}Atom" created successfully using ${u}!\n`)}module.exports={generateAtom};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const e=require("fs"),n=require("path");function parseComponentName(e){const n=e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase().split("-").filter(Boolean);return{kebab:n.join("-"),pascal:n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}}function getAngularRoot(o){let t=n.resolve(o);for(;t!==n.parse(t).root;){if(e.existsSync(n.join(t,"angular.json")))return t;t=n.dirname(t)}return null}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildI18nEn(e,n){return{[`${n}.TITLE`]:`${e} Molecule`,[`${n}.DESCRIPTION`]:`Description for ${e} molecule`}}function buildI18nIt(e,n){return{[`${n}.TITLE`]:`Molecola ${e}`,[`${n}.DESCRIPTION`]:`Descrizione per la molecola ${e}`}}function patchExtensionsBlock(e,n,o,t){const l=`// [ASOR-INJECT:${n}]`;if(!e.includes(l))return e;const s=e.split(l)[0],c=s.lastIndexOf("{");if(-1!==c){const n=s.slice(c);if(new RegExp(`\\b${o}\\b`).test(n))return e}else if(new RegExp(`\\b${o}\\b`).test(e))return e;const r=`${o}: '${t}',`,i=new RegExp(`^([ \\t]*)${escapeRegExp(l)}`,"m"),a=e.match(i),u=a?a[1]:" ";return e.replace(l,`${r}\n${u}${l}`)}function findAppConfigFile(o){const t=n.join(o,"src","app","config");if(!e.existsSync(t))return null;const l=e.readdirSync(t).filter(e=>e.endsWith(".config.ts"));for(const o of l)if(e.readFileSync(n.join(t,o),"utf8").includes("extends ConfigConst"))return n.join(t,o);return null}function patchAsorConfigLogsBlock(o,t,l,s){const c=n.join(o,"src","app","config");let r=null;if(e.existsSync(c)){const o=e.readdirSync(c).filter(e=>e.endsWith("-asor.config.ts")),t=s||"app";o.includes(`${t}-asor.config.ts`)?r=n.join(c,`${t}-asor.config.ts`):o.length>0&&(r=n.join(c,o[0]))}if(!r){const t=n.join(o,"src","app","app.root.ts");e.existsSync(t)&&(r=t)}if(!r)return!1;let i=e.readFileSync(r,"utf8");const a=r.endsWith("app.root.ts"),u=a?"import { RouterModule":"// [ASOR-INJECT:LOG-IMPORTS]",p=`import { ${l}Molecule } from '${a?`./molecules/molecule-${t}/${t}.molecule`:`../molecules/molecule-${t}/${t}.molecule`}';\n`;i.includes(`import { ${l}Molecule }`)||(i.includes(u)?i=i.replace(u,`${p}${u}`):a&&(i=`${p}${i}`));const f=`ConsoleLogsConfig.setClassLevels(${l}Molecule.className, []);`,g=a?"this.stateService.initialize":"// [ASOR-INJECT:LOG-REGISTRATIONS]";if(!i.includes(f)){if(i.includes(g)){const e=new RegExp(`^([ \\t]*)${escapeRegExp(g)}`,"m"),n=i.match(e),o=n?n[1]:" ";i=i.replace(g,`${f}\n${o}${g}`)}else{if(!a)return!1;i=i.replace(/constructor\(\) \{/,`constructor() {\n\t\t${f}`)}return e.writeFileSync(r,i,"utf8"),!0}return!1}function patchRoutesWithMolecule(o,t,l,s,c,r){const i=n.join(o,"src","app","app.routes.ts");if(!e.existsSync(i))return console.warn(`⚠️ Cannot find app.routes.ts at ${i}. Skipping injection.`),!1;let a=e.readFileSync(i,"utf8");const u=`${c.charAt(0).toUpperCase()+c.slice(1)}Config`,p="// [ASOR-INJECT:ROUTE-IMPORTS]",f=`import { ${l}Molecule } from './molecules/molecule-${t}/${t}.molecule';\n`;!a.includes(`import { ${l}Molecule }`)&&a.includes(p)&&(a=a.replace(p,`${f}${p}`));const g=`{\n Molecule: ${l}Molecule,\n I18nPath: [${u}.TranslationUrl.${r}],\n },`,$=`// [ASOR-INJECT:MOLECULE-DEFS(${s})]`;if(a.includes($)){const n=new RegExp(`^([ \\t]*)${escapeRegExp($)}`,"m"),o=a.match(n),t=o?o[1]:" ";return a=a.replace($,`${g}\n${t}${$}`),e.writeFileSync(i,a,"utf8"),!0}return console.warn(`⚠️ Could not find '${$}' placeholder in app.routes.ts.`),!1}function generateMolecule(o,t={isStorage:!1,isFull:!1,inPage:null}){o&&""!==o.trim()||(console.error("❌ Please provide a molecule name."),console.error(" Usage: asor -g -molecule <name>"),console.error(" Example: asor -g -molecule -full menu-bar -in dashboard"),process.exit(1));const{kebab:l,pascal:s}=parseComponentName(o),c=l.replace(/-/g,"_").toUpperCase(),r=getAngularRoot(process.cwd());r||(console.error("❌ No angular.json found. Run this command from inside an Angular project."),process.exit(1));const i=n.join(r,"src","app","molecules");e.existsSync(i)||(console.error(`❌ Molecules folder not found at: ${i}`),console.error(' Make sure you have run "asor setup" first.'),process.exit(1));const a=`molecule-${l}`,u=n.join(i,a);e.existsSync(u)&&(console.error(`❌ Component folder already exists: ${u}`),process.exit(1)),e.mkdirSync(u,{recursive:!0}),console.log(`\n📁 Created folder: src/app/molecules/${a}`);const p=t.isStorage?"BaseStorageMolecule":"BaseMolecule",f=t.isStorage?`extends ${p}<any>`:`extends ${p}`,g=n.join(__dirname,"..","gen-template","generate-molecule");let $=e.readFileSync(n.join(g,"molecule.ts.template"),"utf8"),m=e.readFileSync(n.join(g,"molecule.html.template"),"utf8");const replacePlaceholders=e=>e.replace(/__KEBAB__/g,l).replace(/__PASCAL__/g,s).replace(/__ROUTE_KEY__/g,c).replace(/__BASE_CLASS_NAME__/g,p).replace(/__BASE_CLASS_EXTENDS__/g,f);$=replacePlaceholders($),m=replacePlaceholders(m);const d=[{name:`${l}.molecule.ts`,content:$},{name:`${l}.molecule.html`,content:m}];for(const o of d)e.writeFileSync(n.join(u,o.name),o.content,"utf8"),console.log(`✅ Generated: src/app/molecules/${a}/${o.name}`);let S=null;const y=n.join(r,"asor-core.json");if(e.existsSync(y))try{S=JSON.parse(e.readFileSync(y,"utf8")).prefix}catch(e){}if(!S){const o=e.existsSync(n.join(r,"public"))?n.join(r,"public","assets"):n.join(r,"src","assets"),t=n.join(o,"i18n");e.existsSync(t)&&(S=e.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)[0]||null)}if(S){console.log("\n🌍 Creating i18n structure...");const o=e.existsSync(n.join(r,"public"))?n.join(r,"public","assets"):n.join(r,"src","assets"),t=n.join(o,"i18n"),i=`molecule-${l}`,a=n.join(t,S,i);e.existsSync(a)||e.mkdirSync(a,{recursive:!0});const u=[{lang:"en",content:buildI18nEn(s,c)},{lang:"it",content:buildI18nIt(s,c)}];for(const{lang:o,content:t}of u){const l=n.join(a,`${o}.json`);e.writeFileSync(l,JSON.stringify(t,null,"\t")+"\n","utf8"),console.log(`✅ Generated: assets/i18n/${S}/${i}/${o}.json`)}console.log("\n⚙️ Patching translation URL in central config...");const p=findAppConfigFile(r);if(p){let n=e.readFileSync(p,"utf8");const o=patchExtensionsBlock(n,"TRANSLATION-EXTENSIONS",c,`/${S}/${i}`);o!==n&&(e.writeFileSync(p,o,"utf8"),console.log(`✅ Added translation key ${c} in central config`))}console.log("\n📝 Registering molecule logs..."),patchAsorConfigLogsBlock(r,l,s,S)&&console.log(`✅ Registered ${s}Molecule logs in asor config`)}else console.warn("⚠️ Could not detect i18n prefix folder. Skipping i18n generation.");if(t.isFull&&t.inPage){console.log(`\n🔗 Injecting molecule in app.routes.ts for page "${t.inPage}"...`);const e=S||"app";patchRoutesWithMolecule(r,l,s,t.inPage,e,c)&&console.log("✅ Molecule successfully injected into app.routes.ts")}console.log(`\n🎉 Molecule "${s}Molecule" created successfully using ${p}!\n`)}module.exports={generateMolecule};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const n=require("fs"),e=require("path");function parseComponentName(n){const e=n.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase().split("-").filter(Boolean);return{kebab:e.join("-"),pascal:e.map(n=>n.charAt(0).toUpperCase()+n.slice(1)).join("")}}function getAngularRoot(s){let o=e.resolve(s);for(;o!==e.parse(o).root;){if(n.existsSync(e.join(o,"angular.json")))return o;o=e.dirname(o)}return null}function escapeRegExp(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function buildI18nEn(n,e){return{[`${e}.TITLE`]:`${n} Organism`,[`${e}.WELCOME`]:`Welcome to the ${n} organism`}}function buildI18nIt(n,e){return{[`${e}.TITLE`]:`Organismo ${n}`,[`${e}.WELCOME`]:`Benvenuto nell'organismo ${n}`}}function patchExtensionsBlock(n,e,s,o){const t=`// [ASOR-INJECT:${e}]`;if(!n.includes(t))return n;const i=n.split(t)[0],r=i.lastIndexOf("{");if(-1!==r){const e=i.slice(r);if(new RegExp(`\\b${s}\\b`).test(e))return n}else if(new RegExp(`\\b${s}\\b`).test(n))return n;const c=`${s}: '${o}',`,a=new RegExp(`^([ \\t]*)${escapeRegExp(t)}`,"m"),l=n.match(a),g=l?l[1]:" ";return n.replace(t,`${c}\n${g}${t}`)}function findAppConfigFile(s){const o=e.join(s,"src","app","config");if(!n.existsSync(o))return null;const t=n.readdirSync(o).filter(n=>n.endsWith(".config.ts"));for(const s of t)if(n.readFileSync(e.join(o,s),"utf8").includes("extends ConfigConst"))return e.join(o,s);return null}function patchAsorConfigLogsBlock(s,o,t,i){const r=e.join(s,"src","app","config");let c=null;if(n.existsSync(r)){const s=n.readdirSync(r).filter(n=>n.endsWith("-asor.config.ts")),o=i||"app";s.includes(`${o}-asor.config.ts`)?c=e.join(r,`${o}-asor.config.ts`):s.length>0&&(c=e.join(r,s[0]))}if(!c){const o=e.join(s,"src","app","app.root.ts");n.existsSync(o)&&(c=o)}if(!c)return!1;let a=n.readFileSync(c,"utf8");const l=c.endsWith("app.root.ts"),g=l?"import { RouterModule":"// [ASOR-INJECT:LOG-IMPORTS]",p=`import { ${t}Organism } from '${l?`./organisms/organism-${o}/${o}.organism`:`../organisms/organism-${o}/${o}.organism`}';\n`;a.includes(`import { ${t}Organism }`)||(a.includes(g)?a=a.replace(g,`${p}${g}`):l&&(a=`${p}${a}`));const u=`ConsoleLogsConfig.setClassLevels(${t}Organism.className, []);`,f=l?"this.stateService.initialize":"// [ASOR-INJECT:LOG-REGISTRATIONS]";if(!a.includes(u)){if(a.includes(f)){const n=new RegExp(`^([ \\t]*)${escapeRegExp(f)}`,"m"),e=a.match(n),s=e?e[1]:" ";a=a.replace(f,`${u}\n${s}${f}`)}else{if(!l)return!1;a=a.replace(/constructor\(\) \{/,`constructor() {\n\t\t${u}`)}return n.writeFileSync(c,a,"utf8"),!0}return!1}function patchRoutesWithOrganism(s,o,t,i,r,c){const a=e.join(s,"src","app","app.routes.ts");if(!n.existsSync(a))return console.warn(`⚠️ Cannot find app.routes.ts at ${a}. Skipping injection.`),!1;let l=n.readFileSync(a,"utf8");const g=`${r.charAt(0).toUpperCase()+r.slice(1)}Config`,p="// [ASOR-INJECT:ROUTE-IMPORTS]",u=`import { ${t}Organism } from './organisms/organism-${o}/${o}.organism';\n`;!l.includes(`import { ${t}Organism }`)&&l.includes(p)&&(l=l.replace(p,`${u}${p}`));const f=`{\n Organism: ${t}Organism,\n I18nPath: [${g}.TranslationUrl.${c}],\n },`,m=`// [ASOR-INJECT:ORGANISM-DEFS(${i})]`;if(l.includes(m)){const e=new RegExp(`^([ \\t]*)${escapeRegExp(m)}`,"m"),s=l.match(e),o=s?s[1]:" ";return l=l.replace(m,`${f}\n${o}${m}`),n.writeFileSync(a,l,"utf8"),!0}return console.warn(`⚠️ Could not find '${m}' placeholder in app.routes.ts.`),!1}function generateOrganism(s,o={isStorage:!1,isFull:!1,inPage:null}){s&&""!==s.trim()||(console.error("❌ Please provide an organism name."),process.exit(1));const{kebab:t,pascal:i}=parseComponentName(s),r=t.replace(/-/g,"_").toUpperCase(),c=getAngularRoot(process.cwd());c||(console.error("❌ No angular.json found."),process.exit(1));const a=e.join(c,"src","app","organisms");n.existsSync(a)||n.mkdirSync(a,{recursive:!0});const l=`organism-${t}`,g=e.join(a,l);n.existsSync(g)&&(console.error(`❌ Component folder already exists: ${g}`),process.exit(1)),n.mkdirSync(g,{recursive:!0}),console.log(`\n📁 Created folder: src/app/organisms/${l}`);const p=o.isStorage?"BaseStorageOrganism":"BaseOrganism",u=o.isStorage?`extends ${p}<any>`:`extends ${p}`,f=e.join(__dirname,"..","gen-template","generate-organism");let m=n.readFileSync(e.join(f,"organism.ts.template"),"utf8"),$=n.readFileSync(e.join(f,"organism.html.template"),"utf8");const replacePlaceholders=n=>n.replace(/__KEBAB__/g,t).replace(/__PASCAL__/g,i).replace(/__ROUTE_KEY__/g,r).replace(/__BASE_CLASS_NAME__/g,p).replace(/__BASE_CLASS_EXTENDS__/g,u);m=replacePlaceholders(m),$=replacePlaceholders($);const d=[{name:`${t}.organism.ts`,content:m},{name:`${t}.organism.html`,content:$}];for(const s of d)n.writeFileSync(e.join(g,s.name),s.content,"utf8"),console.log(`✅ Generated: src/app/organisms/${l}/${s.name}`);let S=null;const O=e.join(c,"asor-core.json");if(n.existsSync(O))try{S=JSON.parse(n.readFileSync(O,"utf8")).prefix}catch(n){}if(!S){const s=n.existsSync(e.join(c,"public"))?e.join(c,"public","assets"):e.join(c,"src","assets"),o=e.join(s,"i18n");n.existsSync(o)&&(S=n.readdirSync(o,{withFileTypes:!0}).filter(n=>n.isDirectory()).map(n=>n.name)[0]||null)}if(S){console.log("\n🌍 Creating i18n structure...");const s=n.existsSync(e.join(c,"public"))?e.join(c,"public","assets"):e.join(c,"src","assets"),o=e.join(s,"i18n"),a=`organism-${t}`,l=e.join(o,S,a);n.existsSync(l)||n.mkdirSync(l,{recursive:!0});const g=[{lang:"en",content:buildI18nEn(i,r)},{lang:"it",content:buildI18nIt(i,r)}];for(const{lang:s,content:o}of g){const t=e.join(l,`${s}.json`);n.writeFileSync(t,JSON.stringify(o,null,"\t")+"\n","utf8"),console.log(`✅ Generated: assets/i18n/${S}/${a}/${s}.json`)}console.log("\n⚙️ Patching translation URL in central config...");const p=findAppConfigFile(c);if(p){let e=n.readFileSync(p,"utf8");const s=patchExtensionsBlock(e,"TRANSLATION-EXTENSIONS",r,`/${S}/${a}`);s!==e&&(n.writeFileSync(p,s,"utf8"),console.log(`✅ Added translation key ${r} in central config`))}console.log("\n📝 Registering organism logs..."),patchAsorConfigLogsBlock(c,t,i,S)&&console.log(`✅ Registered ${i}Organism logs in asor config`)}else console.warn("⚠️ Could not detect i18n prefix folder. Skipping i18n generation.");if(o.isFull&&o.inPage){console.log(`\n🔗 Injecting organism in app.routes.ts for page "${o.inPage}"...`);const n=S||"app";patchRoutesWithOrganism(c,t,i,o.inPage,n,r)&&console.log("✅ Organism successfully injected into app.routes.ts")}console.log(`\n🎉 Organism "${i}Organism" created successfully using ${p}!\n`)}module.exports={generateOrganism};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const e=require("fs"),n=require("path");function parseComponentName(e){const n=e.trim().replace(/([a-z])([A-Z])/g,"$1-$2").replace(/[\s_]+/g,"-").toLowerCase().split("-").filter(Boolean);return{kebab:n.join("-"),pascal:n.map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}}function escapeRegExp(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function getAngularRoot(o){let t=n.resolve(o);for(;t!==n.parse(t).root;){if(e.existsSync(n.join(t,"angular.json")))return t;t=n.dirname(t)}return null}function buildI18nEn(e,n){return{[`${n}.TITLE`]:`${e} Page`,[`${n}.WELCOME`]:`Welcome to the ${e} page`}}function buildI18nIt(e,n){return{[`${n}.TITLE`]:`Pagina ${e}`,[`${n}.WELCOME`]:`Benvenuto nella pagina ${e}`}}function patchExtensionsBlock(e,n,o,t){const s=`// [ASOR-INJECT:${n}]`;if(!e.includes(s))return console.warn(`⚠️ Placeholder "${s}" not found in config — skipping patch.`),e;const i=e.split(s)[0],r=i.lastIndexOf("{");if(-1!==r){const n=i.slice(r);if(new RegExp(`\\b${o}\\b`).test(n))return e}else if(new RegExp(`\\b${o}\\b`).test(e))return e;const c=`${o}: '${t}',`,a=new RegExp(`^([ \\t]*)${escapeRegExp(s)}`,"m"),l=e.match(a),p=l?l[1]:" ";return e.replace(s,`${c}\n${p}${s}`)}function findAppConfigFile(o){const t=n.join(o,"src","app","config");if(!e.existsSync(t))return null;const s=e.readdirSync(t).filter(e=>e.endsWith(".config.ts"));for(const o of s)if(e.readFileSync(n.join(t,o),"utf8").includes("extends ConfigConst"))return n.join(t,o);return null}function patchRoutesBlock(o,t,s,i,r){const c=n.join(o,"src","app","app.routes.ts");if(!e.existsSync(c))return console.warn(`⚠️ Cannot find app.routes.ts at ${c}. Skipping route injection.`),!1;let a=e.readFileSync(c,"utf8");const l=`${r.charAt(0).toUpperCase()+r.slice(1)}Config`,p="// [ASOR-INJECT:ROUTE-IMPORTS]";let g="";a.includes(`import { Page${s}Component }`)||(g+=`import { Page${s}Component } from './pages/page-${t}/${t}.component';\n`),a.includes(`import { ${l} }`)||(g+=`import { ${l} } from './config/${r}.config';\n`),g&&a.includes(p)&&(a=a.replace(p,`${g}${p}`));const u=`{\n path: ${l}.Route.${i},\n component: Page${s}Component,\n data: {\n I18nPath: [${l}.TranslationUrl.${i}],\n Components: [\n // [ASOR-INJECT:COMPONENT-DEFS(${t})]\n ],\n\t\t\tMolecules: [\n // [ASOR-INJECT:MOLECULE-DEFS(${t})]\n ],\n\t\t\tAtoms: [\n\t\t\t\t// [ASOR-INJECT:ATOM-DEFS(${t})]\n\t\t\t],\n\t\t\tOrganisms: [\n\t\t\t\t// [ASOR-INJECT:ORGANISM-DEFS(${t})]\n\t\t\t],\n } as IAsorRoute,\n },`,f="// [ASOR-INJECT:ROUTE-DEFS]";if(a.includes(f)){const n=new RegExp(`^([ \\t]*)${escapeRegExp(f)}`,"m"),o=a.match(n),t=o?o[1]:" ",s=u.split("\\n").map((e,n)=>0===n?e:`${t}${e}`).join("\\n");return a=a.replace(f,`${s}\n${t}${f}`),e.writeFileSync(c,a,"utf8"),!0}return console.warn(`⚠️ Could not find '${f}' placeholder. Skipping route injection.`),!1}function patchAsorConfigLogsBlock(o,t,s,i){const r=n.join(o,"src","app","config");if(!e.existsSync(r))return!1;const c=e.readdirSync(r).filter(e=>e.endsWith("-asor.config.ts"));let a=null;const l=i||"app";if(c.includes(`${l}-asor.config.ts`)?a=n.join(r,`${l}-asor.config.ts`):c.length>0&&(a=n.join(r,c[0])),!a)return!1;let p=e.readFileSync(a,"utf8");const g="// [ASOR-INJECT:LOG-IMPORTS]",u=`import { Page${s}Component } from '../pages/page-${t}/${t}.component';\n`;!p.includes(`import { Page${s}Component }`)&&p.includes(g)&&(p=p.replace(g,`${u}${g}`));const f=`ConsoleLogsConfig.setClassLevels(Page${s}Component.className, levels);`,d="// [ASOR-INJECT:LOG-REGISTRATIONS]";if(!p.includes(f)&&p.includes(d)){const n=new RegExp(`^([ \\t]*)${escapeRegExp(d)}`,"m"),o=p.match(n),t=o?o[1]:" ";return p=p.replace(d,`${f}\n${t}${d}`),e.writeFileSync(a,p,"utf8"),!0}return!1}function generatePage(o,t={isStorage:!1,isFull:!1}){o&&""!==o.trim()||(console.error("❌ Please provide a page component name."),console.error(" Usage: asor -g -page <name>"),console.error(" Example: asor -g -full -storage -page user-profile"),process.exit(1));const{kebab:s,pascal:i}=parseComponentName(o),r=`page-${s}`,c=s.replace(/-/g,"_").toUpperCase(),a=getAngularRoot(process.cwd());a||(console.error("❌ No angular.json found. Run this command from inside an Angular project."),process.exit(1));const l=n.join(a,"src","app","pages");e.existsSync(l)||(console.error(`❌ Pages folder not found at: ${l}`),console.error(' Make sure you have run "asor setup" first.'),process.exit(1));const p=r,g=n.join(l,p);e.existsSync(g)&&(console.error(`❌ Component folder already exists: ${g}`),process.exit(1)),e.mkdirSync(g,{recursive:!0}),console.log(`\n📁 Created folder: src/app/pages/${p}`);const u=t.isStorage?"BaseStorageComponent":"BaseComponent",f=t.isStorage?"extends BaseStorageComponent<any>":"extends BaseComponent",d=n.join(__dirname,"..","gen-template","generate-page");let $=e.readFileSync(n.join(d,"page.component.ts.template"),"utf8"),m=e.readFileSync(n.join(d,"page.component.html.template"),"utf8");const replacePlaceholders=e=>e.replace(/__KEBAB__/g,s).replace(/__PASCAL__/g,i).replace(/__ROUTE_KEY__/g,c).replace(/__BASE_CLASS_NAME__/g,u).replace(/__BASE_CLASS_EXTENDS__/g,f);$=replacePlaceholders($),m=replacePlaceholders(m);const S=[{name:`${s}.component.ts`,content:$},{name:`${s}.component.html`,content:m}];for(const o of S)e.writeFileSync(n.join(g,o.name),o.content,"utf8"),console.log(`✅ Generated: src/app/pages/${p}/${o.name}`);console.log("\n🌍 Creating i18n structure...");let C=null;const E=n.join(a,"asor-core.json");if(e.existsSync(E))try{C=JSON.parse(e.readFileSync(E,"utf8")).prefix,console.log(`ℹ️ Loaded prefix from asor-core.json: ${C}`)}catch(e){console.warn("⚠️ Could not parse asor-core.json. Falling back to folder detection.")}if(!C){const o=e.existsSync(n.join(a,"public"))?n.join(a,"public","assets"):n.join(a,"src","assets"),t=n.join(o,"i18n");e.existsSync(t)&&(C=e.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name)[0]||null)}if(C){const o=e.existsSync(n.join(a,"public"))?n.join(a,"public","assets"):n.join(a,"src","assets"),t=n.join(o,"i18n"),s=n.join(t,C,r);e.mkdirSync(s,{recursive:!0});const l=[{lang:"en",content:buildI18nEn(i,c)},{lang:"it",content:buildI18nIt(i,c)}];for(const{lang:o,content:t}of l){const i=n.join(s,`${o}.json`);e.existsSync(i)||(e.writeFileSync(i,JSON.stringify(t,null,"\t")+"\n","utf8"),console.log(`✅ Created i18n: assets/i18n/${C}/${r}/${o}.json`))}}else console.warn("⚠️ Could not detect i18n prefix folder. Skipping i18n generation.");console.log("\n⚙️ Updating app config...");const j=findAppConfigFile(a);if(j){let n=e.readFileSync(j,"utf8"),o=!1;const t=patchExtensionsBlock(n,"ROUTE-EXTENSIONS",c,r);t&&t!==n&&(n=t,o=!0,console.log(`✅ Added route key ${c} in _routeExtensions using placeholder`));const s=C?`/${C}/${r}`:`/${r}`,i=patchExtensionsBlock(n,"TRANSLATION-EXTENSIONS",c,s);i&&i!==n&&(n=i,o=!0,console.log(`✅ Added translation key ${c} in _translationUrlExtensions using placeholder (${s})`)),o?e.writeFileSync(j,n,"utf8"):console.log("ℹ️ Config keys already present, nothing to update.")}else console.warn("⚠️ Could not locate the app config file (extends ConfigConst). Skipping config patch.");patchAsorConfigLogsBlock(a,s,i,C)?console.log("✅ Registered Class Level logs in asor config"):console.warn("⚠️ Could not locate *-asor.config.ts or suitable block. Skipping Class Level log registration."),t.isFull&&(console.log("\n🔗 Injecting route in app.routes.ts..."),patchRoutesBlock(a,s,i,c,C||"app")&&console.log("✅ Route successfully injected into app.routes.ts")),console.log(`\n🎉 Page component "Page${i}Component" created successfully using ${u}!\n`),t.isFull?console.log(`💡 Next steps:\n 1. Customize the template in:\n src/app/pages/${p}/${s}.component.html\n 2. Add translations in:\n assets/i18n/<prefix>/${r}/en.json | it.json`):console.log(`💡 Next steps:\n 1. Add the route in app.routes.ts:\n {\n path: '${s}',\n loadComponent: () =>\n import('./pages/${p}/${s}.component')\n .then(m => m.Page${i}Component)\n }\n 2. Customize the template in:\n src/app/pages/${p}/${s}.component.html\n 3. Add translations in:\n assets/i18n/<prefix>/${r}/en.json | it.json`)}module.exports={generatePage};
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ const e=require("fs"),o=require("path"),{execSync:t}=require("child_process"),r=require("readline");function getAngularRoot(t){let r=o.resolve(t);for(;r!==o.parse(r).root;){if(e.existsSync(o.join(r,"angular.json")))return r;r=o.dirname(r)}return null}console.log("🚀 Initializing project with @asor-studio/asor-core...");const s=getAngularRoot(process.cwd())||process.cwd(),n=s,i=o.join(n,"src","app"),c=o.join(__dirname,"..","gen-template","setup-asor-core","src","app");o.resolve(i)===o.resolve(c)&&(console.error("❌ Error: The script is trying to overwrite its own templates. Please verify you are running this targeting a real Angular project."),process.exit(1)),e.existsSync(i)||(console.error(`❌ Error: src/app folder not found in ${n}. Make sure to run the command inside an existing Angular project.`),process.exit(1));const a=r.createInterface({input:process.stdin,output:process.stdout});a.question("🔤 Enter a prefix for core classes (e.g. App, Nexus, MyProject): ",r=>{a.question("🌐 Enter the baseHref for the project (e.g. /, /nexus-os/): ",s=>{let l=r.trim()||"App",p=s.trim()||"/";p.startsWith("/")||(p="/"+p),p.endsWith("/")||(p+="/");const g=l.charAt(0).toUpperCase()+l.slice(1),f=l.toLowerCase();console.log(`\n⚙️ Using prefix: ${g} / ${f}`),console.log(`⚙️ Using baseHref: ${p}`),console.log("📦 Installing dependencies (asor-core, crypto-js)...");try{t("npm install @asor-studio/asor-core crypto-js",{stdio:"inherit"})}catch(e){console.error("❌ Error during dependencies installation."),a.close(),process.exit(1)}["atoms","molecules","organisms","pages","services","config"].forEach(t=>{const r=o.join(i,t);e.existsSync(r)||(e.mkdirSync(r,{recursive:!0}),console.log(`📁 Created folder: src/app/${t}`))}),console.log("🛠️ Copying and configuring base files in src/app..."),function copyAndReplaceDir(t,r){e.existsSync(r)||e.mkdirSync(r,{recursive:!0});const s=e.readdirSync(t,{withFileTypes:!0});for(let n of s){const s=o.join(t,n.name);let i=n.name;i.endsWith(".template")&&(i=i.slice(0,-9));let a="app.config.ts"===i&&t===c;"app"!==f&&i.includes("app")&&"app.routes.ts"!==i&&!a&&(i=i.replace("app",f));const l=o.join(r,i);if(n.isDirectory())copyAndReplaceDir(s,l);else{let o=e.readFileSync(s,"utf8");if(o=o.replace(/AppConfig/g,`${g}Config`),o=o.replace(/AppCacheConfig/g,`${g}CacheConfig`),o=o.replace(/AppMockAuthControllerService/g,`${g}MockAuthControllerService`),o=o.replace(/AppGlobalStateDataSet/g,`${g}GlobalStateDataSet`),o=o.replace(/AppHomeDataSetConnection/g,`${g}HomeDataSetConnection`),o=o.replace(/AppStateConnectionDesktop/g,`${g}StateConnectionDesktop`),o=o.replace(/AppStateCreateSystemDataSet/g,`${g}StateCreateSystemDataSet`),o=o.replace(/AppStateConnectionSystem/g,`${g}StateConnectionSystem`),o=o.replace(/IAppSystemStatus/g,`I${g}SystemStatus`),o=o.replace(/APP_BASE_I18N/g,`${g.toUpperCase()}_BASE_I18N`),o=o.replace(/i18n\/app/g,`i18n/${f}`),"app"!==f&&(o=o.replace(/from '.\/app.config'/g,`from './${f}.config'`),o=o.replace(/from '.\/config\/app.config'/g,`from './config/${f}.config'`),o=o.replace(/from '.\/app-cache.config'/g,`from './${f}-cache.config'`),o=o.replace(/from '.\/app-asor.config'/g,`from './${f}-asor.config'`),o=o.replace(/from '.\/app-state.config'/g,`from './${f}-state.config'`),o=o.replace(/from '.\/config\/app-state.config'/g,`from './config/${f}-state.config'`),o=o.replace(/from '.\/config\/app-asor.config'/g,`from './config/${f}-asor.config'`),o=o.replace(/from '.\/config\/app.config'/g,`from './config/${f}.config'`),o=o.replace(/from '..\/..\/config\/interfaces\/app-state.interfaces'/g,`from '../../config/interfaces/${f}-state.interfaces'`),o=o.replace(/from '.\/interfaces\/app-state.interfaces'/g,`from './interfaces/${f}-state.interfaces'`),o=o.replace(/app-sample-widget/g,`${f}-sample-widget`)),o=o.replace(/<placeholder-globalStateName>/g,f),i===`${f}.config.ts`||"app.config.ts"===i){o=o.replace(/static override SiteBaseUrl = '\/';/,`static override SiteBaseUrl = '${p}';`);let e="/"===p?"/assets":p.replace(/\/?$/,"")+"/assets";o=o.replace(/static override SiteAssetsUrl = '\/assets';/,`static override SiteAssetsUrl = '${e}';`);let t="/"===p?"":p.replace(/\/?$/,"");o=o.replace(/protected static override _translationUrlExtensions = \{([^}]+)\};/m,(e,o)=>`protected static override _translationUrlExtensions = {${o.replace(/HOME: '\/page-home'/g,`HOME: '/${f}/page-home'`).replace(/SAMPLE_WIDGET: '\/molecule-sample-widget'/g,`SAMPLE_WIDGET: '/${f}/molecule-sample-widget'`)}};`),o=o.replace(/protected static override _urlExtensions = \{([^}]+)\};/m,(e,o)=>`protected static override _urlExtensions = {${o.replace(/HOME: '\/home'/g,`HOME: '${t}/home'`)}};`);let r=new RegExp(`${g.toUpperCase()}_BASE_I18N: 'i18n\\\\/${f}',`,"g");o=o.replace(r,`${g.toUpperCase()}_BASE_I18N: 'i18n/${f}',`),o=o.replace(/useValue: '\/', \/\/ Customize for your application/,`useValue: '${p}', // Customize for your application`)}e.writeFileSync(l,o),console.log(`✅ Copied and configured: ${l}`)}}}(c,i),console.log("🌍 Creating i18n translation folders in assets...");const u=e.existsSync(o.join(n,"public"))?o.join(n,"public","assets"):o.join(n,"src","assets"),d=o.join(u,"i18n",f),m=["en","it"];["page-home","molecule-sample-widget"].forEach(t=>{const r=o.join(d,t);e.existsSync(r)||e.mkdirSync(r,{recursive:!0}),m.forEach(s=>{const n=o.join(r,`${s}.json`);if(!e.existsSync(n)){let r;const i=o.join(__dirname,"..","gen-template","setup-asor-core","src","assets","i18n","app",t,`${s}.json.template`);if(e.existsSync(i)){const o=e.readFileSync(i,"utf8");r=JSON.parse(o),r.TITLE=`${g} ${t.charAt(0).toUpperCase()+t.slice(1)}`}else r={TITLE:`${g} ${t.charAt(0).toUpperCase()+t.slice(1)}`};e.writeFileSync(n,JSON.stringify(r,null,"\t")+"\n")}})}),console.log(`✅ Created i18n structure in: ${d}`),console.log("⚙️ Updating angular.json baseHref...");const S=o.join(n,"angular.json");if(e.existsSync(S))try{const o=JSON.parse(e.readFileSync(S,"utf8")),t=Object.keys(o.projects)[0];t&&o.projects[t].architect?.build?.options&&(o.projects[t].architect.build.options.baseHref=p,e.writeFileSync(S,JSON.stringify(o,null,"\t")+"\n"),console.log(`✅ Updated baseHref in angular.json for project: ${t}`))}catch(e){console.warn("⚠️ Could not automatically update angular.json baseHref. You may need to update it manually.",e.message)}console.log("🧩 Injecting <asor-core-widget> in the root component...");let y=o.join(i,"app.component.html");e.existsSync(y)||(y=o.join(i,"app.html"));const $=e.existsSync(o.join(i,"app.ts"))?o.join(i,"app.ts"):o.join(i,"app.component.ts");if(e.existsSync(y)){let o=e.readFileSync(y,"utf8");o.includes("<asor-core-widget")||(o=o.replace(/(<router-outlet[^>]*>[\s\S]*?(?:<\/router-outlet>)?|<router-outlet\s*\/>)/i,e=>`${e}\n<asor-core-widget mode="on" />`),e.writeFileSync(y,o),console.log("✅ Injected <asor-core-widget> into app.component.html"))}else if(e.existsSync($)){let o=e.readFileSync($,"utf8");o.includes("<asor-core-widget")||(o=o.replace(/(<router-outlet[^>]*>[\s\S]*?(?:<\/router-outlet>)?|<router-outlet\s*\/>)/i,e=>`${e}\n\t\t<asor-core-widget mode="on" />`),e.writeFileSync($,o),console.log("✅ Injected <asor-core-widget> into root component inline template"))}if(e.existsSync($)){let o=e.readFileSync($,"utf8");(o.includes("standalone: true")||o.includes("imports: ["))&&(o.includes("AsorWidgetComponent")||(o="import { AsorWidgetComponent } from '@asor-studio/asor-core';\n"+o,o=o.replace(/imports:\s*\[(.*?)\]/s,(e,o)=>{const t=o.trim()&&!o.trim().endsWith(",")?", ":"";return`imports: [${o}${t}AsorWidgetComponent]`}),e.writeFileSync($,o),console.log("✅ Imported AsorWidgetComponent in root component")))}const j={prefix:f,capitalizedPrefix:g,baseHref:p},h=o.join(n,"asor-core.json");e.writeFileSync(h,JSON.stringify(j,null,"\t")+"\n"),console.log("✅ Saved CLI configuration to: asor-core.json"),console.log("\n🎉 Setup completed successfully!"),console.log("💡 Next steps:"),console.log(`1. Edit src/app/config/${f}-asor.config.ts adding your DataSets and Controllers.`),console.log("2. Update 'app.routes.ts' emptying example comments."),console.log("3. Start creating your components!"),a.close()})});
package/cli.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ "use strict";const{generatePage:e}=require("./cli-command/generate-page"),{generateMolecule:o}=require("./cli-command/generate-molecule"),{generateAtom:n}=require("./cli-command/generate-atom"),{generateOrganism:a}=require("./cli-command/generate-organism"),l=process.argv.slice(2);if("-g"===l[0]){const s=l.slice(1);let r=null,t=null,c=!1,g=!1,i=null;for(let e=0;e<s.length;e++){const o=s[e];"-storage"===o?c=!0:"-full"===o?g=!0:"-in"===o?i=s[++e]:"-page"===o||"-molecule"===o||"-atom"===o||"-organism"===o?r=o:o.startsWith("-")||(t=o)}switch(r&&t||(console.error("[X] Missing generate type or name. Example: asor -g -page <name>"),console.error(" Available types: -page, -molecule, -atom, -organism"),process.exit(1)),r){case"-page":e(t,{isStorage:c,isFull:g});break;case"-molecule":o(t,{isStorage:c,isFull:g,inPage:i});break;case"-atom":n(t,{isStorage:c,isFull:g,inPage:i});break;case"-organism":a(t,{isStorage:c,isFull:g,inPage:i})}process.exit(0)}const s={setup:{description:"Initialize a project with @asor-studio/asor-core",run:()=>require("./cli-command/setup-asor-core")},help:{description:"Show this help message",run:()=>printHelp(0)}};function printHelp(e=0){console.log("\n>>> Asor CLI\n"),console.log("Usage:\n"),console.log(" asor <command>"),console.log(" asor -g <type> <name>\n"),console.log("Commands:");for(const[e,o]of Object.entries(s))console.log(` ${e.padEnd(16)} ${o.description}`);console.log("\nGenerate:"),console.log(` ${"-g [-full] [-storage] -page <name>".padEnd(46)} Generate a new page component`),console.log(` ${"-g [-full] [-in <page>] -molecule <name>".padEnd(46)} Generate a new molecule component`),console.log(` ${"-g [-full] [-in <page>] -atom <name>".padEnd(46)} Generate a new atom component`),console.log(` ${"-g [-full] [-in <page>] -organism <name>".padEnd(46)} Generate a new organism component`),console.log(""),console.log(" Flags (for -page, -molecule, -atom, -organism):"),console.log(` ${"-storage".padEnd(46)} Create using BaseStorage* version instead of Base*`),console.log(` ${"-full".padEnd(46)} Enable i18n and automatic injection`),console.log(""),console.log(" Specific Flags (for -molecule, -atom, -organism):"),console.log(` ${"-in <page>".padEnd(46)} Target page for injection (requires -full)`),console.log(""),process.exit(e)}const[r]=l;r&&"help"!==r&&"--help"!==r&&"-h"!==r||printHelp(0);const t=s[r];t||(console.error(`[X] Unknown command: "${r}"\n`),printHelp(1)),t.run();
@@ -0,0 +1,26 @@
1
+ <style>
2
+ /*
3
+ * IMPORTANT:
4
+ * This CSS block is only a minimal base generated by the template.
5
+ * In a real project, styles should be moved to a dedicated file
6
+ * based on the standard chosen by the team: CSS, SCSS, Sass, Less,
7
+ * or handled through global styles, utility classes, a design system, or inline styles.
8
+ */
9
+ .__KEBAB__-atom {
10
+ display: inline-flex;
11
+ align-items: center;
12
+ justify-content: center;
13
+ min-height: 2.5rem;
14
+ padding: 0.5rem 0.875rem;
15
+ border: 1px solid rgba(226, 232, 240, 0.95);
16
+ border-radius: 0.85rem;
17
+ background: #ffffff;
18
+ color: #334155;
19
+ font: inherit;
20
+ font-size: 0.92rem;
21
+ }
22
+ </style>
23
+
24
+ <div class="__KEBAB__-atom">
25
+ {{ '__ROUTE_KEY__.VALUE' | translate }}
26
+ </div>
@@ -0,0 +1,69 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { __BASE_CLASS_NAME__, TranslatePipe } from '@asor-studio/asor-core';
4
+
5
+ /**
6
+ * Atom Component: __PASCAL__Atom
7
+ *
8
+ * ATOMIC DESIGN ROLE:
9
+ * An Atom is the smallest functional unit of the UI (e.g., a button, an input, a label).
10
+ * It is designed to be highly reusable and independent of business logic.
11
+ *
12
+ * I18N / TRANSLATIONS:
13
+ * Translations for this atom can be found (or added) in:
14
+ * `assets/i18n/<prefix>/atom-__KEBAB__/<lang>.json`
15
+ *
16
+ * ROUTE CONFIGURATION & PAGE BELONGING:
17
+ * To see which page(s) this atom belongs to, check `src/app/app.routes.ts`.
18
+ * It is injected into a page's metadata via the `data.Atoms` array.
19
+ * Look for the following placeholder in the routes file:
20
+ * `// [ASOR-INJECT:ATOM-DEFS(<page-name>)]`
21
+ */
22
+ @Component({
23
+ selector: 'app-__KEBAB__',
24
+ standalone: true,
25
+ imports: [CommonModule, TranslatePipe],
26
+ templateUrl: './__KEBAB__.atom.html',
27
+ })
28
+ export class __PASCAL__Atom __BASE_CLASS_EXTENDS__ {
29
+ /**
30
+ * Unique identifier for the component class.
31
+ * CRITICAL for asor-core: used by the framework to identify this component
32
+ * within route metadata and correctly resolve its configuration/i18n paths.
33
+ */
34
+ public static override readonly className: string = '__PASCAL__Atom';
35
+
36
+ constructor() {
37
+ super();
38
+ }
39
+
40
+ /**
41
+ * Lifecycle hook called when the component view is about to enter at a generic level.
42
+ * Used for system-wide initialization logic common to all asor components.
43
+ */
44
+ override baseCompViewEnter(): void { }
45
+
46
+ /**
47
+ * Lifecycle hook called when the component view is about to leave at a generic level.
48
+ * Used for system-wide cleanup logic common to all asor components.
49
+ * Calls super.baseCompViewLeave() to ensure proper cleanup of base class / storage resources.
50
+ */
51
+ override baseCompViewLeave(): void {
52
+ super.baseCompViewLeave();
53
+ }
54
+
55
+ /**
56
+ * Specialized lifecycle hook for Atom components called when the view is about to enter.
57
+ * Use this for atom-specific initialization logic (e.g. internal state, animations).
58
+ */
59
+ override baseAtomViewWillEnter(): void { }
60
+
61
+ /**
62
+ * Specialized lifecycle hook for Atom components called when the view is about to leave.
63
+ * Use this for atom-specific cleanup logic.
64
+ * Calls super.baseAtomViewWillLeave() to ensure proper cleanup of base class / storage resources.
65
+ */
66
+ override baseAtomViewWillLeave(): void {
67
+ super.baseAtomViewWillLeave();
68
+ }
69
+ }
@@ -0,0 +1,41 @@
1
+ <style>
2
+ /*
3
+ * IMPORTANT:
4
+ * This CSS block is only a minimal base generated by the template.
5
+ * In a real project, styles should be moved to a dedicated file
6
+ * based on the standard chosen by the team: CSS, SCSS, Sass, Less,
7
+ * or handled through global styles, utility classes, a design system, or inline styles.
8
+ */
9
+ .__KEBAB__-molecule {
10
+ display: grid;
11
+ gap: 0.5rem;
12
+ width: min(100%, 28rem);
13
+ padding: 1rem;
14
+ border: 1px solid rgba(226, 232, 240, 0.95);
15
+ border-radius: 1rem;
16
+ background: #ffffff;
17
+ color: #0f172a;
18
+ }
19
+
20
+ .__KEBAB__-title,
21
+ .__KEBAB__-description {
22
+ margin: 0;
23
+ }
24
+
25
+ .__KEBAB__-title {
26
+ font-size: 1rem;
27
+ line-height: 1.2;
28
+ }
29
+
30
+ .__KEBAB__-description {
31
+ max-width: 42ch;
32
+ color: #475569;
33
+ font-size: 0.92rem;
34
+ line-height: 1.5;
35
+ }
36
+ </style>
37
+
38
+ <section class="__KEBAB__-molecule">
39
+ <h3 class="__KEBAB__-title">{{ '__ROUTE_KEY__.TITLE' | translate }}</h3>
40
+ <p class="__KEBAB__-description">{{ '__ROUTE_KEY__.DESCRIPTION' | translate }}</p>
41
+ </section>
@@ -0,0 +1,69 @@
1
+ import { Component } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { __BASE_CLASS_NAME__, TranslatePipe } from '@asor-studio/asor-core';
4
+
5
+ /**
6
+ * Molecule Component: __PASCAL__Molecule
7
+ *
8
+ * ATOMIC DESIGN ROLE:
9
+ * A Molecule is a group of atoms functioning together as a unit (e.g., a search bar with a button).
10
+ * It represents a simple UI pattern that is more complex than a single atom but smaller than an organism.
11
+ *
12
+ * I18N / TRANSLATIONS:
13
+ * Translations for this molecule can be found (or added) in:
14
+ * `assets/i18n/<prefix>/molecule-__KEBAB__/<lang>.json`
15
+ *
16
+ * ROUTE CONFIGURATION & PAGE BELONGING:
17
+ * To see which page(s) this molecule belongs to, check `src/app/app.routes.ts`.
18
+ * It is injected into a page's metadata via the `data.Molecules` array.
19
+ * Look for the following placeholder in the routes file:
20
+ * `// [ASOR-INJECT:MOLECULE-DEFS(<page-name>)]`
21
+ */
22
+ @Component({
23
+ selector: 'app-__KEBAB__',
24
+ standalone: true,
25
+ imports: [CommonModule, TranslatePipe],
26
+ templateUrl: './__KEBAB__.molecule.html',
27
+ })
28
+ export class __PASCAL__Molecule __BASE_CLASS_EXTENDS__ {
29
+ /**
30
+ * Unique identifier for the component class.
31
+ * CRITICAL for asor-core: used by the framework to identify this component
32
+ * within route metadata and correctly resolve its configuration/i18n paths.
33
+ */
34
+ public static override readonly className: string = '__PASCAL__Molecule';
35
+
36
+ constructor() {
37
+ super();
38
+ }
39
+
40
+ /**
41
+ * Lifecycle hook called when the component view is about to enter at a generic level.
42
+ * Used for system-wide initialization logic common to all asor components.
43
+ */
44
+ override baseCompViewEnter(): void { }
45
+
46
+ /**
47
+ * Lifecycle hook called when the component view is about to leave at a generic level.
48
+ * Used for system-wide cleanup logic common to all asor components.
49
+ * Calls super.baseCompViewLeave() to ensure proper cleanup of base class / storage resources.
50
+ */
51
+ override baseCompViewLeave(): void {
52
+ super.baseCompViewLeave();
53
+ }
54
+
55
+ /**
56
+ * Specialized lifecycle hook for Molecule components called when the view is about to enter.
57
+ * Use this for molecule-specific initialization logic (e.g. data fetching, store connection).
58
+ */
59
+ override baseMoleculeViewWillEnter(): void { }
60
+
61
+ /**
62
+ * Specialized lifecycle hook for Molecule components called when the view is about to leave.
63
+ * Use this for molecule-specific cleanup logic.
64
+ * Calls super.baseMoleculeViewWillLeave() to ensure proper cleanup of base class / storage resources.
65
+ */
66
+ override baseMoleculeViewWillLeave(): void {
67
+ super.baseMoleculeViewWillLeave();
68
+ }
69
+ }