@resolve-components/theme 1.2.1 → 1.2.4
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.
- package/package.json +2 -2
- package/schematics/ng-add/index.js +172 -30
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@resolve-components/theme",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.4",
|
|
4
4
|
"schematics": "./collection.json",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "ðŸâ€ÂÂÂÂÂÂ¥ Fully customizable Angular UI components made with â¤︠using Angular CDK ðŸâ€ÂÂÂÂÂÂ¥",
|
|
6
6
|
"peerDependencies": {
|
|
7
7
|
"@angular/common": ">=17.0.0",
|
|
8
8
|
"@angular/core": ">=17.0.0",
|
|
@@ -7,6 +7,16 @@ const tasks_1 = require("@angular-devkit/schematics/tasks");
|
|
|
7
7
|
// Helpers
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
const isNxWorkspace = (tree) => tree.exists('nx.json');
|
|
10
|
+
/** Returns true if a project's build target uses an Angular builder. */
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
function isAngularProject(content) {
|
|
13
|
+
const buildExecutor = content?.['architect']?.['build']?.['executor'] ??
|
|
14
|
+
content?.['targets']?.['build']?.['executor'] ??
|
|
15
|
+
'';
|
|
16
|
+
return (buildExecutor.startsWith('@angular-devkit/build-angular') ||
|
|
17
|
+
buildExecutor.startsWith('@angular/build') ||
|
|
18
|
+
buildExecutor.startsWith('@nx/angular'));
|
|
19
|
+
}
|
|
10
20
|
/**
|
|
11
21
|
* Try to resolve project config from a root angular.json / workspace.json.
|
|
12
22
|
* Works for standard Angular workspaces AND Nx workspaces that still maintain
|
|
@@ -46,47 +56,116 @@ function getAngularWorkspaceProject(tree, projectName) {
|
|
|
46
56
|
isNxProjectJson: false,
|
|
47
57
|
};
|
|
48
58
|
}
|
|
59
|
+
/** Scan all apps/ and projects/ subdirectories and return every application project.json. */
|
|
60
|
+
function discoverNxApplicationProjects(tree) {
|
|
61
|
+
const results = [];
|
|
62
|
+
for (const root of ['apps', 'projects']) {
|
|
63
|
+
const dir = tree.getDir(root);
|
|
64
|
+
for (const sub of dir.subdirs) {
|
|
65
|
+
const path = `${root}/${sub}/project.json`;
|
|
66
|
+
if (!tree.exists(path))
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
const content = JSON.parse(tree.read(path).toString('utf-8'));
|
|
71
|
+
if (content['projectType'] && content['projectType'] !== 'application')
|
|
72
|
+
continue;
|
|
73
|
+
const name = content['name'] ?? sub;
|
|
74
|
+
const defaultSourceRoot = `${root}/${sub}/src`;
|
|
75
|
+
const sourceRoot = content['sourceRoot'] ?? defaultSourceRoot;
|
|
76
|
+
results.push({ path, name, sourceRoot, content });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return results;
|
|
84
|
+
}
|
|
49
85
|
/**
|
|
50
86
|
* In Nx v16+ workspaces there is no writable root angular.json.
|
|
51
87
|
* Discover project.json files under apps/ or projects/.
|
|
88
|
+
* Filters to Angular projects, asks for --project when multiple match.
|
|
52
89
|
*/
|
|
53
90
|
function getNxProjectJsonConfig(tree, projectName) {
|
|
54
|
-
const
|
|
91
|
+
const allApps = discoverNxApplicationProjects(tree);
|
|
92
|
+
// If a project name was explicitly given, find it (any executor — user knows best)
|
|
55
93
|
if (projectName) {
|
|
56
|
-
candidates
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
94
|
+
// Try exact path candidates first
|
|
95
|
+
for (const root of ['apps', 'projects']) {
|
|
96
|
+
const path = `${root}/${projectName}/project.json`;
|
|
97
|
+
if (tree.exists(path)) {
|
|
98
|
+
try {
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
100
|
+
const content = JSON.parse(tree.read(path).toString('utf-8'));
|
|
101
|
+
const name = content['name'] ?? projectName;
|
|
102
|
+
const sourceRoot = content['sourceRoot'] ?? `${root}/${projectName}/src`;
|
|
103
|
+
return {
|
|
104
|
+
file: path,
|
|
105
|
+
projectName: name,
|
|
106
|
+
content,
|
|
107
|
+
sourceRoot,
|
|
108
|
+
isNxProjectJson: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* skip */
|
|
113
|
+
}
|
|
114
|
+
}
|
|
65
115
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
continue;
|
|
70
|
-
try {
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
-
const content = JSON.parse(tree.read(path).toString('utf-8'));
|
|
73
|
-
// Skip non-application projects (libraries, e2e, etc.)
|
|
74
|
-
if (content['projectType'] && content['projectType'] !== 'application')
|
|
75
|
-
continue;
|
|
76
|
-
const projName = content['name'] ?? path.split('/')[1];
|
|
77
|
-
const defaultSourceRoot = path.split('/').slice(0, -1).join('/') + '/src';
|
|
78
|
-
const sourceRoot = content['sourceRoot'] ?? defaultSourceRoot;
|
|
116
|
+
// Also search by name in discovered list
|
|
117
|
+
const byName = allApps.find((p) => p.name === projectName || p.path.includes(`/${projectName}/`));
|
|
118
|
+
if (byName) {
|
|
79
119
|
return {
|
|
80
|
-
file: path,
|
|
81
|
-
projectName:
|
|
82
|
-
content,
|
|
83
|
-
sourceRoot,
|
|
120
|
+
file: byName.path,
|
|
121
|
+
projectName: byName.name,
|
|
122
|
+
content: byName.content,
|
|
123
|
+
sourceRoot: byName.sourceRoot,
|
|
84
124
|
isNxProjectJson: true,
|
|
85
125
|
};
|
|
86
126
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
127
|
+
// Not found — throw with available list
|
|
128
|
+
const available = allApps.map((p) => ` • ${p.name}`).join('\n');
|
|
129
|
+
throw new schematics_1.SchematicsException(`Project "${projectName}" not found.\n` +
|
|
130
|
+
`Available application projects:\n${available || ' (none found)'}\n\n` +
|
|
131
|
+
`Re-run with: ng add @resolve-components/theme --project=<name>`);
|
|
132
|
+
}
|
|
133
|
+
// No project specified — filter to Angular projects only
|
|
134
|
+
const angularApps = allApps.filter((p) => isAngularProject(p.content));
|
|
135
|
+
if (angularApps.length === 1) {
|
|
136
|
+
// Exactly one Angular app → auto-pick
|
|
137
|
+
const found = angularApps[0];
|
|
138
|
+
return {
|
|
139
|
+
file: found.path,
|
|
140
|
+
projectName: found.name,
|
|
141
|
+
content: found.content,
|
|
142
|
+
sourceRoot: found.sourceRoot,
|
|
143
|
+
isNxProjectJson: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
if (angularApps.length > 1) {
|
|
147
|
+
// Multiple Angular apps — cannot guess, ask user to specify
|
|
148
|
+
const list = angularApps.map((p) => ` • ${p.name}`).join('\n');
|
|
149
|
+
throw new schematics_1.SchematicsException(`Multiple Angular applications found. Please specify which one to configure:\n\n` +
|
|
150
|
+
`${list}\n\n` +
|
|
151
|
+
`Re-run with: ng add @resolve-components/theme --project=<name>`);
|
|
152
|
+
}
|
|
153
|
+
// No Angular apps found — fall back to any application project
|
|
154
|
+
if (allApps.length === 1) {
|
|
155
|
+
const found = allApps[0];
|
|
156
|
+
return {
|
|
157
|
+
file: found.path,
|
|
158
|
+
projectName: found.name,
|
|
159
|
+
content: found.content,
|
|
160
|
+
sourceRoot: found.sourceRoot,
|
|
161
|
+
isNxProjectJson: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (allApps.length > 1) {
|
|
165
|
+
const list = allApps.map((p) => ` • ${p.name}`).join('\n');
|
|
166
|
+
throw new schematics_1.SchematicsException(`Could not auto-detect an Angular project. Found these application projects:\n\n` +
|
|
167
|
+
`${list}\n\n` +
|
|
168
|
+
`Re-run with: ng add @resolve-components/theme --project=<name>`);
|
|
90
169
|
}
|
|
91
170
|
return null;
|
|
92
171
|
}
|
|
@@ -207,6 +286,67 @@ function addGlobalStyles(options) {
|
|
|
207
286
|
};
|
|
208
287
|
}
|
|
209
288
|
// ---------------------------------------------------------------------------
|
|
289
|
+
// Rule: wrap app.html with <rc-layout>
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
function addRcLayoutToTemplate(options) {
|
|
292
|
+
return (tree, ctx) => {
|
|
293
|
+
const cfg = resolveProjectConfig(tree, options);
|
|
294
|
+
const sourceRoot = cfg?.sourceRoot ?? 'src';
|
|
295
|
+
const templatePath = `${sourceRoot}/app/app.html`;
|
|
296
|
+
if (!tree.exists(templatePath)) {
|
|
297
|
+
ctx.logger.warn(`⚠ ${templatePath} not found — wrap your root template with <rc-layout> manually.`);
|
|
298
|
+
return tree;
|
|
299
|
+
}
|
|
300
|
+
const content = tree.read(templatePath).toString('utf-8');
|
|
301
|
+
if (content.includes('<rc-layout'))
|
|
302
|
+
return tree;
|
|
303
|
+
const wrapped = `<rc-layout>\n${content.trimEnd()}\n</rc-layout>\n`;
|
|
304
|
+
tree.overwrite(templatePath, wrapped);
|
|
305
|
+
ctx.logger.info(` Wrapped ${templatePath} with <rc-layout>`);
|
|
306
|
+
return tree;
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Rule: add RcLayout to app.ts imports
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
function addRcLayoutToAppComponent(options) {
|
|
313
|
+
return (tree, ctx) => {
|
|
314
|
+
const cfg = resolveProjectConfig(tree, options);
|
|
315
|
+
const sourceRoot = cfg?.sourceRoot ?? 'src';
|
|
316
|
+
// Support both app.ts and app.component.ts
|
|
317
|
+
const appTsPath = tree.exists(`${sourceRoot}/app/app.ts`)
|
|
318
|
+
? `${sourceRoot}/app/app.ts`
|
|
319
|
+
: tree.exists(`${sourceRoot}/app/app.component.ts`)
|
|
320
|
+
? `${sourceRoot}/app/app.component.ts`
|
|
321
|
+
: null;
|
|
322
|
+
if (!appTsPath) {
|
|
323
|
+
ctx.logger.warn(`⚠ app.ts / app.component.ts not found — add RcLayout to your root component imports manually.`);
|
|
324
|
+
return tree;
|
|
325
|
+
}
|
|
326
|
+
let content = tree.read(appTsPath).toString('utf-8');
|
|
327
|
+
if (content.includes('RcLayout'))
|
|
328
|
+
return tree;
|
|
329
|
+
// Add import statement after last existing import
|
|
330
|
+
const importLine = `import { RcLayout } from '@resolve-components/theme';\n`;
|
|
331
|
+
const lastImportMatch = [...content.matchAll(/^import .*?;\n/gms)]
|
|
332
|
+
.filter((m) => !m[0].includes('\n '))
|
|
333
|
+
.pop();
|
|
334
|
+
if (lastImportMatch) {
|
|
335
|
+
const insertAt = lastImportMatch.index + lastImportMatch[0].length;
|
|
336
|
+
content =
|
|
337
|
+
content.slice(0, insertAt) + importLine + content.slice(insertAt);
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
content = importLine + content;
|
|
341
|
+
}
|
|
342
|
+
// Add RcLayout to the component's imports array
|
|
343
|
+
content = content.replace(/(imports:\s*\[)/, `$1RcLayout, `);
|
|
344
|
+
tree.overwrite(appTsPath, content);
|
|
345
|
+
ctx.logger.info(` Added RcLayout import → ${appTsPath}`);
|
|
346
|
+
return tree;
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
210
350
|
// Exported schematic factory
|
|
211
351
|
// ---------------------------------------------------------------------------
|
|
212
352
|
function ngAdd(options) {
|
|
@@ -217,6 +357,8 @@ function ngAdd(options) {
|
|
|
217
357
|
addBuildConfig(options),
|
|
218
358
|
addProviderToConfig(options),
|
|
219
359
|
addGlobalStyles(options),
|
|
360
|
+
addRcLayoutToTemplate(options),
|
|
361
|
+
addRcLayoutToAppComponent(options),
|
|
220
362
|
(_tree, ctx) => {
|
|
221
363
|
ctx.addTask(new tasks_1.NodePackageInstallTask());
|
|
222
364
|
ctx.logger.info('\n✅ @resolve-components/theme setup complete!');
|