@koehler8/cms 1.0.0-beta.9 → 1.1.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/CHANGELOG.md +1522 -0
  2. package/README.md +108 -28
  3. package/bin/cms-create-extension.js +7 -0
  4. package/bin/cms-create-site.js +7 -0
  5. package/bin/cms-create-theme.js +7 -0
  6. package/bin/cms-ssg-build.js +13 -0
  7. package/bin/cms-validate-extensions.js +4 -0
  8. package/bin/cms-validate-themes.js +11 -0
  9. package/extensions/manifest.schema.json +1 -1
  10. package/package.json +33 -17
  11. package/scripts/create-extension.mjs +11 -0
  12. package/scripts/create-site.mjs +11 -0
  13. package/scripts/create-theme.mjs +11 -0
  14. package/scripts/generate-public-assets.js +70 -36
  15. package/scripts/image-variants/cache.js +40 -0
  16. package/scripts/image-variants/classifier.js +53 -0
  17. package/scripts/image-variants/config.js +54 -0
  18. package/scripts/image-variants/index.js +18 -0
  19. package/scripts/image-variants/manifest.js +76 -0
  20. package/scripts/image-variants/planner.js +95 -0
  21. package/scripts/image-variants/reconcile.js +194 -0
  22. package/scripts/image-variants/renderer.js +51 -0
  23. package/scripts/lib/extension-manifests.mjs +70 -0
  24. package/scripts/scaffold.mjs +242 -0
  25. package/scripts/ssg-build/findEmptyRenders.mjs +42 -0
  26. package/scripts/ssg-build/index.mjs +253 -0
  27. package/scripts/validate-extensions.mjs +50 -382
  28. package/scripts/validate-themes.mjs +37 -5
  29. package/src/components/About.vue +4 -2
  30. package/src/components/AboutValue.vue +5 -4
  31. package/src/components/ComingSoon.vue +12 -1
  32. package/src/components/ComingSoonModal.vue +33 -61
  33. package/src/components/Contact.vue +216 -120
  34. package/src/components/DraftGate.vue +211 -0
  35. package/src/components/Footer.vue +15 -2
  36. package/src/components/FooterMinimal.vue +13 -12
  37. package/src/components/Header.vue +365 -10
  38. package/src/components/Hero.vue +17 -8
  39. package/src/components/Home.vue +82 -66
  40. package/src/components/Intro.vue +4 -2
  41. package/src/components/IntroGate.vue +30 -73
  42. package/src/components/NotFound.vue +100 -0
  43. package/src/components/Plan.vue +10 -4
  44. package/src/components/Portfolio.vue +29 -3
  45. package/src/components/Principles.vue +3 -1
  46. package/src/components/Spacer.vue +23 -0
  47. package/src/components/Spacer15.vue +3 -4
  48. package/src/components/Spacer30.vue +3 -4
  49. package/src/components/Spacer40.vue +3 -4
  50. package/src/components/Spacer60.vue +3 -4
  51. package/src/components/StickyCTA.vue +23 -1
  52. package/src/components/Team.vue +3 -1
  53. package/src/composables/useComponentResolver.js +49 -1
  54. package/src/composables/useDraftGate.js +93 -0
  55. package/src/composables/useFocusTrap.js +85 -0
  56. package/src/composables/usePageConfig.js +136 -36
  57. package/src/composables/usePageMeta.js +178 -5
  58. package/src/composables/usePromoBackgroundStyles.js +30 -14
  59. package/src/extensions/extensionLoader.js +34 -24
  60. package/src/main.js +69 -6
  61. package/src/router/index.js +98 -37
  62. package/src/styles/base.css +11 -2
  63. package/src/themes/buildCssVarMap.js +34 -0
  64. package/src/utils/appInfo.js +8 -5
  65. package/src/utils/breadcrumbs.js +121 -0
  66. package/src/utils/canonicalUrl.js +53 -0
  67. package/src/utils/componentRegistry.js +98 -16
  68. package/src/utils/cookieConsent.js +38 -24
  69. package/src/utils/draftMode.js +65 -0
  70. package/src/utils/imageSources.js +9 -1
  71. package/src/utils/jsonLd.js +66 -0
  72. package/src/utils/loadConfig.js +106 -6
  73. package/src/utils/notFound.js +52 -0
  74. package/src/utils/robotsGenerator.js +95 -0
  75. package/src/utils/sha256.js +20 -0
  76. package/src/utils/sitemapGenerator.js +119 -0
  77. package/src/utils/socialMeta.js +100 -0
  78. package/src/utils/ssgRoutes.js +40 -0
  79. package/src/utils/trackingContext.js +16 -22
  80. package/src/utils/webAppManifest.js +85 -0
  81. package/src/utils/webStorage.js +82 -0
  82. package/templates/index.html +17 -24
  83. package/templates/scaffolds/extension/README.md +53 -0
  84. package/templates/scaffolds/extension/extension.config.json +10 -0
  85. package/templates/scaffolds/extension/index.js +37 -0
  86. package/templates/scaffolds/extension/package.json +22 -0
  87. package/templates/scaffolds/site/.env.example +7 -0
  88. package/templates/scaffolds/site/.nvmrc +1 -0
  89. package/templates/scaffolds/site/CLAUDE.md +121 -0
  90. package/templates/scaffolds/site/README.md +25 -0
  91. package/templates/scaffolds/site/amplify.yml +22 -0
  92. package/templates/scaffolds/site/package.json +26 -0
  93. package/templates/scaffolds/site/site/assets/img/.gitkeep +0 -0
  94. package/templates/scaffolds/site/site/components/.gitkeep +0 -0
  95. package/templates/scaffolds/site/site/content/content.config.json +3 -0
  96. package/templates/scaffolds/site/site/content/en/pages/home.json +8 -0
  97. package/templates/scaffolds/site/site/content/en/shared.json +5 -0
  98. package/templates/scaffolds/site/site/content/en/site.json +10 -0
  99. package/templates/scaffolds/site/site/style.css +4 -0
  100. package/templates/scaffolds/site/vite.config.js +15 -0
  101. package/templates/scaffolds/theme/README.md +50 -0
  102. package/templates/scaffolds/theme/index.js +8 -0
  103. package/templates/scaffolds/theme/package.json +21 -0
  104. package/templates/scaffolds/theme/theme.config.js +228 -0
  105. package/templates/scaffolds/theme/theme.css +14 -0
  106. package/themes/base/theme.config.js +100 -57
  107. package/vite-plugin.js +549 -32
  108. package/public/robots.txt +0 -5
  109. package/scripts/check-overflow.mjs +0 -33
  110. package/scripts/migrate-to-content-dirs.mjs +0 -223
  111. package/scripts/patch-lru-cache-tla.js +0 -164
package/public/robots.txt DELETED
@@ -1,5 +0,0 @@
1
- User-agent: *
2
- Disallow: /admin
3
- Disallow: /privacy
4
- Disallow: /terms
5
- Disallow: /cookies
@@ -1,33 +0,0 @@
1
- import { chromium } from 'playwright';
2
- import path from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __filename = fileURLToPath(import.meta.url);
6
- const __dirname = path.dirname(__filename);
7
- const distIndex = path.resolve(__dirname, '../dist/index.html');
8
-
9
- const browser = await chromium.launch();
10
- const page = await browser.newPage({ viewport: { width: 412, height: 915 } });
11
- await page.goto(`file://${distIndex}`);
12
- await page.waitForTimeout(2000);
13
- const result = await page.evaluate(() => ({
14
- scrollWidth: document.documentElement.scrollWidth,
15
- clientWidth: document.documentElement.clientWidth,
16
- bodyScrollWidth: document.body.scrollWidth,
17
- bodyClientWidth: document.body.clientWidth,
18
- overflowElements: Array.from(document.querySelectorAll('*')).map(el => {
19
- const rect = el.getBoundingClientRect();
20
- const docWidth = document.documentElement.clientWidth;
21
- const overflowX = rect.right > docWidth + 1;
22
- return overflowX ? {
23
- tag: el.tagName.toLowerCase(),
24
- classes: el.className,
25
- rectRight: rect.right,
26
- rectLeft: rect.left,
27
- width: rect.width,
28
- text: el.textContent?.trim().slice(0, 120)
29
- } : null;
30
- }).filter(Boolean)
31
- }));
32
- console.log(JSON.stringify(result, null, 2));
33
- await browser.close();
@@ -1,223 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Migration script: converts site/config/ structure to site/content/ structure.
4
- *
5
- * Old layout:
6
- * site/config/site.json (nested)
7
- * site/config/shared.json (nested)
8
- * site/config/pages/*.json (nested)
9
- * site/config/i18n/{locale}.json (flat dot-notation)
10
- *
11
- * New layout:
12
- * site/content/content.config.json ({ "baseLocale": "en" })
13
- * site/content/en/site.json (flat dot-notation, sorted)
14
- * site/content/en/shared.json (flat dot-notation, sorted)
15
- * site/content/en/pages/*.json (flat dot-notation, sorted)
16
- * site/content/{locale}/site.json (flat dot-notation, sorted, overrides only)
17
- * site/content/{locale}/shared.json
18
- * site/content/{locale}/pages/*.json
19
- *
20
- * Usage:
21
- * node scripts/migrate-to-content-dirs.mjs /path/to/site-repo
22
- * node scripts/migrate-to-content-dirs.mjs /path/to/site-repo --commit
23
- *
24
- * Without --commit, runs in dry-run mode (prints what it would do).
25
- */
26
-
27
- import fs from 'node:fs';
28
- import path from 'node:path';
29
-
30
- // ---- Flatten utility ----
31
-
32
- const ARRAY_INDEX_RE = /\[(\d+)]/g;
33
-
34
- function flattenObject(obj, prefix = '') {
35
- const result = {};
36
- if (!obj || typeof obj !== 'object') return result;
37
-
38
- for (const [key, value] of Object.entries(obj)) {
39
- if (key.startsWith('$')) continue;
40
- const fullKey = prefix ? `${prefix}.${key}` : key;
41
-
42
- if (Array.isArray(value)) {
43
- value.forEach((item, idx) => {
44
- if (item && typeof item === 'object' && !Array.isArray(item)) {
45
- Object.assign(result, flattenObject(item, `${fullKey}[${idx}]`));
46
- } else {
47
- result[`${fullKey}[${idx}]`] = item;
48
- }
49
- });
50
- } else if (value && typeof value === 'object') {
51
- Object.assign(result, flattenObject(value, fullKey));
52
- } else {
53
- result[fullKey] = value;
54
- }
55
- }
56
- return result;
57
- }
58
-
59
- function sortedJson(obj) {
60
- const sorted = {};
61
- for (const key of Object.keys(obj).sort()) {
62
- sorted[key] = obj[key];
63
- }
64
- return JSON.stringify(sorted, null, 2) + '\n';
65
- }
66
-
67
- // ---- Main ----
68
-
69
- const siteRepoDir = process.argv[2];
70
- const commit = process.argv.includes('--commit');
71
-
72
- if (!siteRepoDir) {
73
- console.error('Usage: node scripts/migrate-to-content-dirs.mjs /path/to/site-repo [--commit]');
74
- process.exit(1);
75
- }
76
-
77
- const siteDir = path.join(siteRepoDir, 'site');
78
- const configDir = path.join(siteDir, 'config');
79
- const contentDir = path.join(siteDir, 'content');
80
-
81
- if (!fs.existsSync(configDir)) {
82
- console.error(`Config directory not found: ${configDir}`);
83
- process.exit(1);
84
- }
85
-
86
- const actions = [];
87
-
88
- function plan(action, detail) {
89
- actions.push({ action, detail });
90
- if (!commit) console.log(` [dry-run] ${action}: ${detail}`);
91
- }
92
-
93
- // 1. Flatten and move English base files
94
- const enDir = path.join(contentDir, 'en');
95
- const enPagesDir = path.join(enDir, 'pages');
96
-
97
- // site.json
98
- const siteJsonPath = path.join(configDir, 'site.json');
99
- if (fs.existsSync(siteJsonPath)) {
100
- const nested = JSON.parse(fs.readFileSync(siteJsonPath, 'utf-8'));
101
- const flat = flattenObject(nested);
102
- plan('write', `content/en/site.json (${Object.keys(flat).length} keys)`);
103
- if (commit) {
104
- fs.mkdirSync(enDir, { recursive: true });
105
- fs.writeFileSync(path.join(enDir, 'site.json'), sortedJson(flat));
106
- }
107
- }
108
-
109
- // shared.json
110
- const sharedJsonPath = path.join(configDir, 'shared.json');
111
- if (fs.existsSync(sharedJsonPath)) {
112
- const nested = JSON.parse(fs.readFileSync(sharedJsonPath, 'utf-8'));
113
- const flat = flattenObject(nested);
114
- plan('write', `content/en/shared.json (${Object.keys(flat).length} keys)`);
115
- if (commit) {
116
- fs.mkdirSync(enDir, { recursive: true });
117
- fs.writeFileSync(path.join(enDir, 'shared.json'), sortedJson(flat));
118
- }
119
- }
120
-
121
- // pages/*.json
122
- const pagesDir = path.join(configDir, 'pages');
123
- if (fs.existsSync(pagesDir)) {
124
- for (const file of fs.readdirSync(pagesDir)) {
125
- if (!file.endsWith('.json')) continue;
126
- const nested = JSON.parse(fs.readFileSync(path.join(pagesDir, file), 'utf-8'));
127
- const flat = flattenObject(nested);
128
- plan('write', `content/en/pages/${file} (${Object.keys(flat).length} keys)`);
129
- if (commit) {
130
- fs.mkdirSync(enPagesDir, { recursive: true });
131
- fs.writeFileSync(path.join(enPagesDir, file), sortedJson(flat));
132
- }
133
- }
134
- }
135
-
136
- // 2. Split i18n flat files into locale directories
137
- const i18nDir = path.join(configDir, 'i18n');
138
- if (fs.existsSync(i18nDir)) {
139
- // Read page filenames from config/pages/ to know valid page scopes
140
- const pageFiles = fs.existsSync(pagesDir)
141
- ? fs.readdirSync(pagesDir).filter((f) => f.endsWith('.json')).map((f) => f.replace('.json', ''))
142
- : [];
143
-
144
- for (const file of fs.readdirSync(i18nDir)) {
145
- if (!file.endsWith('.json')) continue;
146
- const locale = file.replace('.json', '');
147
- const localeDir = path.join(contentDir, locale);
148
- const localePagesDir = path.join(localeDir, 'pages');
149
-
150
- const raw = JSON.parse(fs.readFileSync(path.join(i18nDir, file), 'utf-8'));
151
-
152
- // Buckets: site.*, shared.*, pages.{pageId}.*
153
- const siteBucket = {};
154
- const sharedBucket = {};
155
- const pageBuckets = {};
156
-
157
- for (const [key, value] of Object.entries(raw)) {
158
- if (key.startsWith('$')) continue;
159
-
160
- if (key.startsWith('site.')) {
161
- siteBucket[key.slice('site.'.length)] = value;
162
- } else if (key.startsWith('shared.')) {
163
- sharedBucket[key.slice('shared.'.length)] = value;
164
- } else if (key.startsWith('pages.')) {
165
- const rest = key.slice('pages.'.length);
166
- const dotIdx = rest.indexOf('.');
167
- if (dotIdx === -1) continue;
168
- const pageId = rest.slice(0, dotIdx);
169
- const pageKey = rest.slice(dotIdx + 1);
170
- if (!pageBuckets[pageId]) pageBuckets[pageId] = {};
171
- pageBuckets[pageId][pageKey] = value;
172
- }
173
- }
174
-
175
- if (Object.keys(siteBucket).length > 0) {
176
- plan('write', `content/${locale}/site.json (${Object.keys(siteBucket).length} keys)`);
177
- if (commit) {
178
- fs.mkdirSync(localeDir, { recursive: true });
179
- fs.writeFileSync(path.join(localeDir, 'site.json'), sortedJson(siteBucket));
180
- }
181
- }
182
-
183
- if (Object.keys(sharedBucket).length > 0) {
184
- plan('write', `content/${locale}/shared.json (${Object.keys(sharedBucket).length} keys)`);
185
- if (commit) {
186
- fs.mkdirSync(localeDir, { recursive: true });
187
- fs.writeFileSync(path.join(localeDir, 'shared.json'), sortedJson(sharedBucket));
188
- }
189
- }
190
-
191
- for (const [pageId, pageData] of Object.entries(pageBuckets)) {
192
- if (Object.keys(pageData).length === 0) continue;
193
- plan('write', `content/${locale}/pages/${pageId}.json (${Object.keys(pageData).length} keys)`);
194
- if (commit) {
195
- fs.mkdirSync(localePagesDir, { recursive: true });
196
- fs.writeFileSync(path.join(localePagesDir, `${pageId}.json`), sortedJson(pageData));
197
- }
198
- }
199
- }
200
- }
201
-
202
- // 3. Create content.config.json
203
- plan('write', 'content/content.config.json');
204
- if (commit) {
205
- fs.mkdirSync(contentDir, { recursive: true });
206
- fs.writeFileSync(
207
- path.join(contentDir, 'content.config.json'),
208
- JSON.stringify({ baseLocale: 'en' }, null, 2) + '\n'
209
- );
210
- }
211
-
212
- // 4. Remove old config directory
213
- if (commit) {
214
- fs.rmSync(configDir, { recursive: true, force: true });
215
- plan('remove', 'config/ directory');
216
- } else {
217
- plan('remove', 'config/ directory (would be removed)');
218
- }
219
-
220
- console.log(`\n${commit ? 'Migration complete' : 'Dry run complete'}. ${actions.length} actions.`);
221
- if (!commit) {
222
- console.log('Run with --commit to apply changes.');
223
- }
@@ -1,164 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Patches all nested lru-cache copies in node_modules to remove top-level
4
- * await, which breaks Node 20's sync CJS loader when cssstyle (via jsdom, via
5
- * vite-ssg) tries to `require()` @asamuzakjp/css-color.
6
- *
7
- * The fix:
8
- * 1. Overwrite `dist/esm/diagnostics-channel.js` with a TLA-free no-op that
9
- * uses createRequire() for node:diagnostics_channel.
10
- * 2. Rewrite each nested lru-cache `package.json` to point the `import`
11
- * conditional at `dist/esm/index.js` (non-minified), which imports from
12
- * our patched `diagnostics-channel.js` instead of inlining TLA.
13
- *
14
- * Safe to run repeatedly; idempotent.
15
- */
16
-
17
- import fs from 'node:fs';
18
- import path from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
-
23
- // Prefer INIT_CWD (set by npm during postinstall to the consumer's project
24
- // root). Fall back to the parent of scripts/ (package root) or CWD.
25
- const candidateRoots = [
26
- process.env.INIT_CWD,
27
- path.resolve(__dirname, '..'),
28
- process.cwd(),
29
- ].filter(Boolean);
30
-
31
- let repoRoot;
32
- let nodeModules;
33
- for (const root of candidateRoots) {
34
- const nm = path.join(root, 'node_modules');
35
- if (fs.existsSync(nm)) {
36
- repoRoot = root;
37
- nodeModules = nm;
38
- break;
39
- }
40
- }
41
-
42
- if (!nodeModules) {
43
- console.log('[patch-lru-cache-tla] node_modules not found; skipping');
44
- process.exit(0);
45
- }
46
-
47
- const REPLACEMENT = `// Patched by scripts/patch-lru-cache-tla.js — removes top-level await.
48
- // Uses createRequire to load node:diagnostics_channel synchronously from ESM.
49
- import { createRequire } from 'node:module';
50
- const dummy = { hasSubscribers: false };
51
- let _metrics = dummy;
52
- let _tracing = dummy;
53
- try {
54
- const require = createRequire(import.meta.url);
55
- const dc = require('node:diagnostics_channel');
56
- _metrics = dc.channel('lru-cache:metrics');
57
- _tracing = dc.tracingChannel('lru-cache');
58
- } catch {
59
- /* non-node runtime: keep dummy */
60
- }
61
- export const metrics = _metrics;
62
- export const tracing = _tracing;
63
- `;
64
-
65
- function findLruCacheDirs(root) {
66
- const found = [];
67
- function walk(dir) {
68
- let entries;
69
- try {
70
- entries = fs.readdirSync(dir, { withFileTypes: true });
71
- } catch {
72
- return;
73
- }
74
- for (const entry of entries) {
75
- if (!entry.isDirectory()) continue;
76
- const full = path.join(dir, entry.name);
77
- if (entry.name === 'lru-cache' && fs.existsSync(path.join(full, 'package.json'))) {
78
- found.push(full);
79
- // don't recurse into lru-cache's own node_modules
80
- continue;
81
- }
82
- if (entry.name === 'node_modules' || entry.name.startsWith('@')) {
83
- walk(full);
84
- } else if (entry.name === 'node_modules') {
85
- walk(full);
86
- } else {
87
- // Only recurse into scoped/package dirs that could contain nested node_modules
88
- const nested = path.join(full, 'node_modules');
89
- if (fs.existsSync(nested)) walk(nested);
90
- }
91
- }
92
- }
93
- walk(root);
94
- return found;
95
- }
96
-
97
- function patchLruCache(lruDir) {
98
- const diagFile = path.join(lruDir, 'dist', 'esm', 'diagnostics-channel.js');
99
- const pkgFile = path.join(lruDir, 'package.json');
100
- const indexJs = path.join(lruDir, 'dist', 'esm', 'index.js');
101
-
102
- if (!fs.existsSync(diagFile) || !fs.existsSync(pkgFile) || !fs.existsSync(indexJs)) {
103
- return false;
104
- }
105
-
106
- let patched = false;
107
-
108
- // 1. Overwrite diagnostics-channel.js if it still contains TLA
109
- const current = fs.readFileSync(diagFile, 'utf-8');
110
- if (current.includes('await import')) {
111
- fs.writeFileSync(diagFile, REPLACEMENT, 'utf-8');
112
- patched = true;
113
- }
114
-
115
- // 2. Rewrite package.json exports to use non-minified index.js
116
- const pkg = JSON.parse(fs.readFileSync(pkgFile, 'utf-8'));
117
- let pkgChanged = false;
118
-
119
- const rewriteExports = (exports) => {
120
- if (!exports || typeof exports !== 'object') return;
121
- for (const key of Object.keys(exports)) {
122
- const value = exports[key];
123
- if (value && typeof value === 'object') {
124
- if (value.import && typeof value.import === 'object' && value.import.default) {
125
- if (value.import.default.includes('index.min.js')) {
126
- value.import.default = value.import.default.replace('index.min.js', 'index.js');
127
- pkgChanged = true;
128
- }
129
- } else if (typeof value.import === 'string' && value.import.includes('index.min.js')) {
130
- value.import = value.import.replace('index.min.js', 'index.js');
131
- pkgChanged = true;
132
- }
133
- rewriteExports(value);
134
- }
135
- }
136
- };
137
-
138
- rewriteExports(pkg.exports);
139
-
140
- if (pkg.module && pkg.module.includes('index.min.js')) {
141
- pkg.module = pkg.module.replace('index.min.js', 'index.js');
142
- pkgChanged = true;
143
- }
144
-
145
- if (pkgChanged) {
146
- fs.writeFileSync(pkgFile, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
147
- patched = true;
148
- }
149
-
150
- return patched;
151
- }
152
-
153
- const lruDirs = findLruCacheDirs(nodeModules);
154
- let patchedCount = 0;
155
- for (const dir of lruDirs) {
156
- if (patchLruCache(dir)) {
157
- patchedCount++;
158
- console.log(`[patch-lru-cache-tla] patched ${path.relative(repoRoot, dir)}`);
159
- }
160
- }
161
-
162
- console.log(
163
- `[patch-lru-cache-tla] processed ${lruDirs.length} lru-cache dirs, patched ${patchedCount}`
164
- );