@schandlergarcia/sf-web-components 2.0.0 → 2.2.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.
- package/.a4drules/features/command-center-dashboard-rule.md +2 -2
- package/.a4drules/features/pre-code-checklist.md +2 -2
- package/.a4drules/skills/command-center-builder/SKILL.md +6 -6
- package/.a4drules/skills/command-center-builder/getting-started.md +30 -75
- package/.a4drules/skills/command-center-builder/improved-build-process.md +9 -45
- package/.a4drules/skills/command-center-guide/SKILL.md +5 -9
- package/.a4drules/skills/component-library/card-components.md +1 -1
- package/.a4drules/validation-requirements.md +3 -8
- package/CHANGELOG.md +21 -0
- package/CLAUDE.md +23 -0
- package/brands/engine/PARTNER_HUB_PRD.md +584 -0
- package/brands/engine/agentApiConfig.ts +36 -0
- package/brands/engine/brand.css +40 -0
- package/brands/engine/engine-command-center-prd.md +575 -0
- package/brands/engine/engine-live-data.js +135 -0
- package/brands/engine/engine-sample-data.js +378 -0
- package/brands/engine/engine_logo.png +0 -0
- package/brands/engine/global.css +234 -0
- package/brands/engine/partner-hub-sample-data.js +191 -0
- package/brands/engine/schema.graphql +292 -0
- package/brands/engine/useEngineLiveData.ts +49 -0
- package/brands/engine/useEvaAgent.ts +288 -0
- package/package.json +6 -2
- package/scripts/apply-brand.mjs +179 -0
- package/scripts/postinstall.mjs +21 -0
- package/scripts/reset-command-center.sh +28 -7
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* apply-brand.mjs — Apply a brand theme to the project.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/apply-brand.mjs engine # Apply Engine brand
|
|
8
|
+
* node scripts/apply-brand.mjs --list # List available brands
|
|
9
|
+
* node scripts/apply-brand.mjs --reset # Remove brand, restore neutral theme
|
|
10
|
+
*
|
|
11
|
+
* When run from a consuming project (via npm run brand:engine), the script
|
|
12
|
+
* detects the package location automatically.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
|
|
22
|
+
const arg = process.argv[2];
|
|
23
|
+
|
|
24
|
+
if (!arg) {
|
|
25
|
+
console.error('Usage: node scripts/apply-brand.mjs <brand-name|--list|--reset>');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cwd = process.cwd();
|
|
30
|
+
const PACKAGE_NAME = '@schandlergarcia/sf-web-components';
|
|
31
|
+
|
|
32
|
+
function findPackageRoot() {
|
|
33
|
+
const fromNodeModules = path.join(cwd, 'node_modules', PACKAGE_NAME);
|
|
34
|
+
if (fs.existsSync(fromNodeModules)) return fromNodeModules;
|
|
35
|
+
|
|
36
|
+
const fromScript = path.resolve(__dirname, '..');
|
|
37
|
+
if (fs.existsSync(path.join(fromScript, 'brands'))) return fromScript;
|
|
38
|
+
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const packageRoot = findPackageRoot();
|
|
43
|
+
if (!packageRoot) {
|
|
44
|
+
console.error('Could not find package root. Run from the project directory.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const brandsDir = path.join(packageRoot, 'brands');
|
|
49
|
+
|
|
50
|
+
if (arg === '--list') {
|
|
51
|
+
if (!fs.existsSync(brandsDir)) {
|
|
52
|
+
console.log('No brands available.');
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
const brands = fs.readdirSync(brandsDir).filter(d =>
|
|
56
|
+
fs.statSync(path.join(brandsDir, d)).isDirectory()
|
|
57
|
+
);
|
|
58
|
+
console.log(`Available brands: ${brands.join(', ') || '(none)'}`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (arg === '--reset') {
|
|
63
|
+
const neutralCSS = path.join(packageRoot, 'src/styles/global.css');
|
|
64
|
+
const targetCSS = path.join(cwd, 'src/styles/global.css');
|
|
65
|
+
if (fs.existsSync(neutralCSS)) {
|
|
66
|
+
fs.copyFileSync(neutralCSS, targetCSS);
|
|
67
|
+
console.log(' ✓ Restored neutral theme (global.css)');
|
|
68
|
+
}
|
|
69
|
+
const brandMarker = path.join(cwd, '.brand');
|
|
70
|
+
if (fs.existsSync(brandMarker)) fs.unlinkSync(brandMarker);
|
|
71
|
+
console.log('\n✅ Brand reset to neutral.\n');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const brandName = arg;
|
|
76
|
+
const brandDir = path.join(brandsDir, brandName);
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(brandDir)) {
|
|
79
|
+
console.error(`Brand "${brandName}" not found in ${brandsDir}`);
|
|
80
|
+
const available = fs.existsSync(brandsDir)
|
|
81
|
+
? fs.readdirSync(brandsDir).filter(d => fs.statSync(path.join(brandsDir, d)).isDirectory())
|
|
82
|
+
: [];
|
|
83
|
+
if (available.length) console.error(`Available: ${available.join(', ')}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(`\n🎨 Applying "${brandName}" brand...\n`);
|
|
88
|
+
|
|
89
|
+
let installed = 0;
|
|
90
|
+
|
|
91
|
+
// 1. global.css → src/styles/global.css (always overwrite)
|
|
92
|
+
const brandCSS = path.join(brandDir, 'global.css');
|
|
93
|
+
const targetCSS = path.join(cwd, 'src/styles/global.css');
|
|
94
|
+
if (fs.existsSync(brandCSS)) {
|
|
95
|
+
fs.mkdirSync(path.dirname(targetCSS), { recursive: true });
|
|
96
|
+
fs.copyFileSync(brandCSS, targetCSS);
|
|
97
|
+
console.log(' ✓ Brand theme applied (global.css)');
|
|
98
|
+
installed++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 2. Sample data files → src/data/
|
|
102
|
+
const dataFiles = [
|
|
103
|
+
{ src: 'engine-sample-data.js', dst: 'src/data/engine-sample-data.js' },
|
|
104
|
+
{ src: 'engine-live-data.js', dst: 'src/data/engine-live-data.js' },
|
|
105
|
+
{ src: 'partner-hub-sample-data.js', dst: 'src/data/partner-hub-sample-data.js' },
|
|
106
|
+
];
|
|
107
|
+
for (const { src, dst } of dataFiles) {
|
|
108
|
+
const srcPath = path.join(brandDir, src);
|
|
109
|
+
const dstPath = path.join(cwd, dst);
|
|
110
|
+
if (fs.existsSync(srcPath)) {
|
|
111
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true });
|
|
112
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
113
|
+
console.log(` ✓ Installed ${dst}`);
|
|
114
|
+
installed++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 3. Hooks → src/hooks/
|
|
119
|
+
const hooks = [
|
|
120
|
+
{ src: 'useEngineLiveData.ts', dst: 'src/hooks/useEngineLiveData.ts' },
|
|
121
|
+
{ src: 'useEvaAgent.ts', dst: 'src/hooks/useEvaAgent.ts' },
|
|
122
|
+
];
|
|
123
|
+
for (const { src, dst } of hooks) {
|
|
124
|
+
const srcPath = path.join(brandDir, src);
|
|
125
|
+
const dstPath = path.join(cwd, dst);
|
|
126
|
+
if (fs.existsSync(srcPath)) {
|
|
127
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true });
|
|
128
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
129
|
+
console.log(` ✓ Installed ${dst}`);
|
|
130
|
+
installed++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 4. Config files → src/config/
|
|
135
|
+
const configs = [
|
|
136
|
+
{ src: 'agentApiConfig.ts', dst: 'src/config/agentApi.ts' },
|
|
137
|
+
];
|
|
138
|
+
for (const { src, dst } of configs) {
|
|
139
|
+
const srcPath = path.join(brandDir, src);
|
|
140
|
+
const dstPath = path.join(cwd, dst);
|
|
141
|
+
if (fs.existsSync(srcPath)) {
|
|
142
|
+
fs.mkdirSync(path.dirname(dstPath), { recursive: true });
|
|
143
|
+
fs.copyFileSync(srcPath, dstPath);
|
|
144
|
+
console.log(` ✓ Installed ${dst}`);
|
|
145
|
+
installed++;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 5. PRDs → project root
|
|
150
|
+
const prds = fs.readdirSync(brandDir).filter(f => f.endsWith('_PRD.md') || f.endsWith('-prd.md'));
|
|
151
|
+
for (const prdFile of prds) {
|
|
152
|
+
fs.copyFileSync(path.join(brandDir, prdFile), path.join(cwd, prdFile));
|
|
153
|
+
console.log(` ✓ Installed ${prdFile}`);
|
|
154
|
+
installed++;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// 6. Schema → project root
|
|
158
|
+
const schema = path.join(brandDir, 'schema.graphql');
|
|
159
|
+
if (fs.existsSync(schema)) {
|
|
160
|
+
fs.copyFileSync(schema, path.join(cwd, 'schema.graphql'));
|
|
161
|
+
console.log(' ✓ Installed schema.graphql');
|
|
162
|
+
installed++;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 7. Logo → src/assets/images/
|
|
166
|
+
const logo = path.join(brandDir, 'engine_logo.png');
|
|
167
|
+
if (fs.existsSync(logo)) {
|
|
168
|
+
const logoTarget = path.join(cwd, 'src/assets/images/engine_logo.png');
|
|
169
|
+
fs.mkdirSync(path.dirname(logoTarget), { recursive: true });
|
|
170
|
+
fs.copyFileSync(logo, logoTarget);
|
|
171
|
+
console.log(' ✓ Installed engine_logo.png');
|
|
172
|
+
installed++;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 8. Write .brand marker so reset script knows which brand is active
|
|
176
|
+
fs.writeFileSync(path.join(cwd, '.brand'), brandName + '\n', 'utf-8');
|
|
177
|
+
|
|
178
|
+
console.log(`\n✅ "${brandName}" brand applied (${installed} files installed).`);
|
|
179
|
+
console.log(' Run "npm run brand:reset" to revert to neutral theme.\n');
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -335,6 +335,26 @@ if (fs.existsSync(packageJsonPath)) {
|
|
|
335
335
|
scriptsAdded.push('validate:dashboard');
|
|
336
336
|
}
|
|
337
337
|
|
|
338
|
+
// Add manual setup script (re-runs postinstall if it was missed)
|
|
339
|
+
if (!packageJson.scripts['setup']) {
|
|
340
|
+
packageJson.scripts['setup'] = 'node node_modules/@schandlergarcia/sf-web-components/scripts/postinstall.mjs';
|
|
341
|
+
scriptsAdded.push('setup');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Add brand scripts
|
|
345
|
+
if (!packageJson.scripts['brand:engine']) {
|
|
346
|
+
packageJson.scripts['brand:engine'] = 'node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs engine';
|
|
347
|
+
scriptsAdded.push('brand:engine');
|
|
348
|
+
}
|
|
349
|
+
if (!packageJson.scripts['brand:reset']) {
|
|
350
|
+
packageJson.scripts['brand:reset'] = 'node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs --reset';
|
|
351
|
+
scriptsAdded.push('brand:reset');
|
|
352
|
+
}
|
|
353
|
+
if (!packageJson.scripts['brand:list']) {
|
|
354
|
+
packageJson.scripts['brand:list'] = 'node node_modules/@schandlergarcia/sf-web-components/scripts/apply-brand.mjs --list';
|
|
355
|
+
scriptsAdded.push('brand:list');
|
|
356
|
+
}
|
|
357
|
+
|
|
338
358
|
if (scriptsAdded.length > 0) {
|
|
339
359
|
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf-8');
|
|
340
360
|
scriptsAdded.forEach(script => {
|
|
@@ -442,6 +462,7 @@ console.log(` - Installed ${templatesInstalled} page templates`);
|
|
|
442
462
|
console.log(` - Installed ${scriptsInstalled} utility scripts`);
|
|
443
463
|
console.log(` - Installed CommandCenter.tsx for dashboard management`);
|
|
444
464
|
console.log(` - Added "npm run reset:command-center" script`);
|
|
465
|
+
console.log(` - Added "npm run setup" script (re-run this setup anytime)`);
|
|
445
466
|
console.log(` - Installed AI assistant rules (.a4drules)`);
|
|
446
467
|
if (migratedFiles > 0) {
|
|
447
468
|
console.log(` - Migrated ${migratedFiles} dashboard files to correct location`);
|
|
@@ -398,24 +398,45 @@ echo "→ Cleaning caches…"
|
|
|
398
398
|
rm -rf node_modules/.vite 2>/dev/null && echo " ✓ Cleared Vite cache" || true
|
|
399
399
|
echo ""
|
|
400
400
|
|
|
401
|
-
# ── 9. Restore
|
|
401
|
+
# ── 9. Restore global.css (brand-aware) ──────────────────────────────────────
|
|
402
402
|
|
|
403
403
|
mkdir -p src/styles
|
|
404
404
|
|
|
405
405
|
GLOBAL_CSS="src/styles/global.css"
|
|
406
406
|
echo "→ Restoring ${GLOBAL_CSS}..."
|
|
407
407
|
|
|
408
|
-
#
|
|
408
|
+
# Check if a brand is active
|
|
409
|
+
ACTIVE_BRAND=""
|
|
410
|
+
if [ -f ".brand" ]; then
|
|
411
|
+
ACTIVE_BRAND="$(cat .brand | tr -d '[:space:]')"
|
|
412
|
+
fi
|
|
413
|
+
|
|
409
414
|
PACKAGE_CSS=""
|
|
410
|
-
if [ -
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
415
|
+
if [ -n "$ACTIVE_BRAND" ]; then
|
|
416
|
+
# Use the brand's global.css
|
|
417
|
+
if [ -f "node_modules/@schandlergarcia/sf-web-components/brands/$ACTIVE_BRAND/global.css" ]; then
|
|
418
|
+
PACKAGE_CSS="node_modules/@schandlergarcia/sf-web-components/brands/$ACTIVE_BRAND/global.css"
|
|
419
|
+
elif [ -f "$SCRIPT_DIR/../brands/$ACTIVE_BRAND/global.css" ]; then
|
|
420
|
+
PACKAGE_CSS="$SCRIPT_DIR/../brands/$ACTIVE_BRAND/global.css"
|
|
421
|
+
fi
|
|
422
|
+
fi
|
|
423
|
+
|
|
424
|
+
# Fall back to neutral if no brand or brand CSS not found
|
|
425
|
+
if [ -z "$PACKAGE_CSS" ]; then
|
|
426
|
+
if [ -f "node_modules/@schandlergarcia/sf-web-components/src/styles/global.css" ]; then
|
|
427
|
+
PACKAGE_CSS="node_modules/@schandlergarcia/sf-web-components/src/styles/global.css"
|
|
428
|
+
elif [ -f "$SCRIPT_DIR/../src/styles/global.css" ]; then
|
|
429
|
+
PACKAGE_CSS="$SCRIPT_DIR/../src/styles/global.css"
|
|
430
|
+
fi
|
|
414
431
|
fi
|
|
415
432
|
|
|
416
433
|
if [ -n "$PACKAGE_CSS" ] && [ -f "$PACKAGE_CSS" ]; then
|
|
417
434
|
cp "$PACKAGE_CSS" "$GLOBAL_CSS"
|
|
418
|
-
|
|
435
|
+
if [ -n "$ACTIVE_BRAND" ]; then
|
|
436
|
+
echo " ✓ Theme restored (brand: $ACTIVE_BRAND)"
|
|
437
|
+
else
|
|
438
|
+
echo " ✓ Theme restored (neutral)"
|
|
439
|
+
fi
|
|
419
440
|
else
|
|
420
441
|
echo " ⚠ Skipped global.css (package CSS not found)"
|
|
421
442
|
fi
|