@htlkg/astro 0.0.1
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/README.md +265 -0
- package/dist/chunk-33R4URZV.js +59 -0
- package/dist/chunk-33R4URZV.js.map +1 -0
- package/dist/chunk-64USRLVP.js +85 -0
- package/dist/chunk-64USRLVP.js.map +1 -0
- package/dist/chunk-WLOFOVCL.js +210 -0
- package/dist/chunk-WLOFOVCL.js.map +1 -0
- package/dist/chunk-WNMPTDCR.js +73 -0
- package/dist/chunk-WNMPTDCR.js.map +1 -0
- package/dist/chunk-Z2ZAL7KX.js +9 -0
- package/dist/chunk-Z2ZAL7KX.js.map +1 -0
- package/dist/chunk-ZQ4XMJH7.js +1 -0
- package/dist/chunk-ZQ4XMJH7.js.map +1 -0
- package/dist/htlkg/config.js +7 -0
- package/dist/htlkg/config.js.map +1 -0
- package/dist/htlkg/index.js +7 -0
- package/dist/htlkg/index.js.map +1 -0
- package/dist/index.js +64 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.js +168 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/utils/hydration.js +21 -0
- package/dist/utils/hydration.js.map +1 -0
- package/dist/utils/index.js +56 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/ssr.js +21 -0
- package/dist/utils/ssr.js.map +1 -0
- package/dist/utils/static.js +19 -0
- package/dist/utils/static.js.map +1 -0
- package/package.json +53 -0
- package/src/__mocks__/astro-middleware.ts +19 -0
- package/src/__mocks__/virtual-htlkg-config.ts +14 -0
- package/src/auth/LoginForm.vue +482 -0
- package/src/auth/LoginPage.astro +70 -0
- package/src/components/PageHeader.astro +145 -0
- package/src/components/Sidebar.astro +157 -0
- package/src/components/Topbar.astro +167 -0
- package/src/components/index.ts +9 -0
- package/src/htlkg/config.test.ts +165 -0
- package/src/htlkg/config.ts +242 -0
- package/src/htlkg/index.ts +245 -0
- package/src/htlkg/virtual-modules.test.ts +158 -0
- package/src/htlkg/virtual-modules.ts +81 -0
- package/src/index.ts +37 -0
- package/src/layouts/AdminLayout.astro +184 -0
- package/src/layouts/AuthLayout.astro +164 -0
- package/src/layouts/BrandLayout.astro +309 -0
- package/src/layouts/DefaultLayout.astro +25 -0
- package/src/layouts/PublicLayout.astro +153 -0
- package/src/layouts/index.ts +10 -0
- package/src/middleware/auth.ts +53 -0
- package/src/middleware/index.ts +31 -0
- package/src/middleware/route-guards.test.ts +182 -0
- package/src/middleware/route-guards.ts +218 -0
- package/src/patterns/admin/DetailPage.astro +195 -0
- package/src/patterns/admin/FormPage.astro +203 -0
- package/src/patterns/admin/ListPage.astro +178 -0
- package/src/patterns/admin/index.ts +9 -0
- package/src/patterns/brand/ConfigPage.astro +128 -0
- package/src/patterns/brand/PortalPage.astro +161 -0
- package/src/patterns/brand/index.ts +8 -0
- package/src/patterns/index.ts +8 -0
- package/src/utils/hydration.test.ts +154 -0
- package/src/utils/hydration.ts +151 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/ssr.test.ts +235 -0
- package/src/utils/ssr.ts +139 -0
- package/src/utils/static.test.ts +144 -0
- package/src/utils/static.ts +144 -0
- package/src/vue-app-setup.ts +88 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
generateStaticPaths,
|
|
4
|
+
generatePaginatedPaths,
|
|
5
|
+
chunkArray,
|
|
6
|
+
sortItems,
|
|
7
|
+
filterItems,
|
|
8
|
+
groupItems,
|
|
9
|
+
} from './static';
|
|
10
|
+
|
|
11
|
+
describe('Static Generation Utilities', () => {
|
|
12
|
+
describe('generateStaticPaths', () => {
|
|
13
|
+
it('should generate paths from items', () => {
|
|
14
|
+
const items = [
|
|
15
|
+
{ id: '1', name: 'Item 1' },
|
|
16
|
+
{ id: '2', name: 'Item 2' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const paths = generateStaticPaths(items, (item) => ({
|
|
20
|
+
params: { id: item.id },
|
|
21
|
+
props: { item },
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
expect(paths).toHaveLength(2);
|
|
25
|
+
expect(paths[0]).toEqual({
|
|
26
|
+
params: { id: '1' },
|
|
27
|
+
props: { item: items[0] },
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('generatePaginatedPaths', () => {
|
|
33
|
+
it('should generate paginated paths', () => {
|
|
34
|
+
const items = Array.from({ length: 25 }, (_, i) => ({ id: i + 1 }));
|
|
35
|
+
|
|
36
|
+
const paths = generatePaginatedPaths(items, 10, (page, pageItems) => ({
|
|
37
|
+
params: { page: page.toString() },
|
|
38
|
+
props: { items: pageItems, page },
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
expect(paths).toHaveLength(3);
|
|
42
|
+
expect(paths[0].props?.items).toHaveLength(10);
|
|
43
|
+
expect(paths[1].props?.items).toHaveLength(10);
|
|
44
|
+
expect(paths[2].props?.items).toHaveLength(5);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('chunkArray', () => {
|
|
49
|
+
it('should chunk array into smaller arrays', () => {
|
|
50
|
+
const items = [1, 2, 3, 4, 5, 6, 7];
|
|
51
|
+
const chunks = chunkArray(items, 3);
|
|
52
|
+
|
|
53
|
+
expect(chunks).toEqual([
|
|
54
|
+
[1, 2, 3],
|
|
55
|
+
[4, 5, 6],
|
|
56
|
+
[7],
|
|
57
|
+
]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should handle empty array', () => {
|
|
61
|
+
const chunks = chunkArray([], 3);
|
|
62
|
+
expect(chunks).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('sortItems', () => {
|
|
67
|
+
it('should sort items ascending', () => {
|
|
68
|
+
const items = [
|
|
69
|
+
{ id: 3, name: 'C' },
|
|
70
|
+
{ id: 1, name: 'A' },
|
|
71
|
+
{ id: 2, name: 'B' },
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const sorted = sortItems(items, 'id', 'asc');
|
|
75
|
+
|
|
76
|
+
expect(sorted.map(i => i.id)).toEqual([1, 2, 3]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should sort items descending', () => {
|
|
80
|
+
const items = [
|
|
81
|
+
{ id: 1, name: 'A' },
|
|
82
|
+
{ id: 3, name: 'C' },
|
|
83
|
+
{ id: 2, name: 'B' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const sorted = sortItems(items, 'id', 'desc');
|
|
87
|
+
|
|
88
|
+
expect(sorted.map(i => i.id)).toEqual([3, 2, 1]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not mutate original array', () => {
|
|
92
|
+
const items = [
|
|
93
|
+
{ id: 3, name: 'C' },
|
|
94
|
+
{ id: 1, name: 'A' },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
sortItems(items, 'id', 'asc');
|
|
98
|
+
|
|
99
|
+
expect(items[0].id).toBe(3);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('filterItems', () => {
|
|
104
|
+
it('should filter items by predicate', () => {
|
|
105
|
+
const items = [
|
|
106
|
+
{ id: 1, active: true },
|
|
107
|
+
{ id: 2, active: false },
|
|
108
|
+
{ id: 3, active: true },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const filtered = filterItems(items, (item) => item.active);
|
|
112
|
+
|
|
113
|
+
expect(filtered).toHaveLength(2);
|
|
114
|
+
expect(filtered.map(i => i.id)).toEqual([1, 3]);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('groupItems', () => {
|
|
119
|
+
it('should group items by key', () => {
|
|
120
|
+
const items = [
|
|
121
|
+
{ id: 1, category: 'A' },
|
|
122
|
+
{ id: 2, category: 'B' },
|
|
123
|
+
{ id: 3, category: 'A' },
|
|
124
|
+
];
|
|
125
|
+
|
|
126
|
+
const grouped = groupItems(items, 'category');
|
|
127
|
+
|
|
128
|
+
expect(grouped).toEqual({
|
|
129
|
+
A: [
|
|
130
|
+
{ id: 1, category: 'A' },
|
|
131
|
+
{ id: 3, category: 'A' },
|
|
132
|
+
],
|
|
133
|
+
B: [
|
|
134
|
+
{ id: 2, category: 'B' },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle empty array', () => {
|
|
140
|
+
const grouped = groupItems([], 'category' as any);
|
|
141
|
+
expect(grouped).toEqual({});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Helper functions for static site generation in Astro.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate static paths from array of items
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* export async function getStaticPaths() {
|
|
13
|
+
* const brands = await getBrands();
|
|
14
|
+
* return generateStaticPaths(brands, (brand) => ({
|
|
15
|
+
* params: { brandId: brand.id },
|
|
16
|
+
* props: { brand }
|
|
17
|
+
* }));
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export function generateStaticPaths<T, P = any>(
|
|
22
|
+
items: T[],
|
|
23
|
+
mapper: (item: T) => { params: Record<string, string | number>; props?: P }
|
|
24
|
+
): Array<{ params: Record<string, string | number>; props?: P }> {
|
|
25
|
+
return items.map(mapper);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Generate paginated static paths
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* export async function getStaticPaths() {
|
|
34
|
+
* const items = await getItems();
|
|
35
|
+
* return generatePaginatedPaths(items, 10, (page, items) => ({
|
|
36
|
+
* params: { page: page.toString() },
|
|
37
|
+
* props: { items, page }
|
|
38
|
+
* }));
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function generatePaginatedPaths<T, P = any>(
|
|
43
|
+
items: T[],
|
|
44
|
+
pageSize: number,
|
|
45
|
+
mapper: (
|
|
46
|
+
page: number,
|
|
47
|
+
pageItems: T[],
|
|
48
|
+
totalPages: number
|
|
49
|
+
) => { params: Record<string, string | number>; props?: P }
|
|
50
|
+
): Array<{ params: Record<string, string | number>; props?: P }> {
|
|
51
|
+
const totalPages = Math.ceil(items.length / pageSize);
|
|
52
|
+
const paths: Array<{ params: Record<string, string | number>; props?: P }> = [];
|
|
53
|
+
|
|
54
|
+
for (let page = 1; page <= totalPages; page++) {
|
|
55
|
+
const start = (page - 1) * pageSize;
|
|
56
|
+
const end = start + pageSize;
|
|
57
|
+
const pageItems = items.slice(start, end);
|
|
58
|
+
|
|
59
|
+
paths.push(mapper(page, pageItems, totalPages));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return paths;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate nested static paths
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* export async function getStaticPaths() {
|
|
71
|
+
* const brands = await getBrands();
|
|
72
|
+
* return generateNestedPaths(brands, async (brand) => {
|
|
73
|
+
* const products = await getProducts(brand.id);
|
|
74
|
+
* return products.map(product => ({
|
|
75
|
+
* params: { brandId: brand.id, productId: product.id },
|
|
76
|
+
* props: { brand, product }
|
|
77
|
+
* }));
|
|
78
|
+
* });
|
|
79
|
+
* }
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
export async function generateNestedPaths<T, P = any>(
|
|
83
|
+
items: T[],
|
|
84
|
+
mapper: (item: T) => Promise<Array<{ params: Record<string, string | number>; props?: P }>>
|
|
85
|
+
): Promise<Array<{ params: Record<string, string | number>; props?: P }>> {
|
|
86
|
+
const nestedPaths = await Promise.all(items.map(mapper));
|
|
87
|
+
return nestedPaths.flat();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Chunk array into smaller arrays
|
|
92
|
+
*/
|
|
93
|
+
export function chunkArray<T>(array: T[], size: number): T[][] {
|
|
94
|
+
const chunks: T[][] = [];
|
|
95
|
+
for (let i = 0; i < array.length; i += size) {
|
|
96
|
+
chunks.push(array.slice(i, i + size));
|
|
97
|
+
}
|
|
98
|
+
return chunks;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Sort items for static generation
|
|
103
|
+
*/
|
|
104
|
+
export function sortItems<T>(
|
|
105
|
+
items: T[],
|
|
106
|
+
key: keyof T,
|
|
107
|
+
order: 'asc' | 'desc' = 'asc'
|
|
108
|
+
): T[] {
|
|
109
|
+
return [...items].sort((a, b) => {
|
|
110
|
+
const aVal = a[key];
|
|
111
|
+
const bVal = b[key];
|
|
112
|
+
|
|
113
|
+
if (aVal < bVal) return order === 'asc' ? -1 : 1;
|
|
114
|
+
if (aVal > bVal) return order === 'asc' ? 1 : -1;
|
|
115
|
+
return 0;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Filter items for static generation
|
|
121
|
+
*/
|
|
122
|
+
export function filterItems<T>(
|
|
123
|
+
items: T[],
|
|
124
|
+
predicate: (item: T) => boolean
|
|
125
|
+
): T[] {
|
|
126
|
+
return items.filter(predicate);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Group items by key
|
|
131
|
+
*/
|
|
132
|
+
export function groupItems<T>(
|
|
133
|
+
items: T[],
|
|
134
|
+
key: keyof T
|
|
135
|
+
): Record<string, T[]> {
|
|
136
|
+
return items.reduce((groups, item) => {
|
|
137
|
+
const groupKey = String(item[key]);
|
|
138
|
+
if (!groups[groupKey]) {
|
|
139
|
+
groups[groupKey] = [];
|
|
140
|
+
}
|
|
141
|
+
groups[groupKey].push(item);
|
|
142
|
+
return groups;
|
|
143
|
+
}, {} as Record<string, T[]>);
|
|
144
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vue App Setup for htlkg Integration
|
|
3
|
+
*
|
|
4
|
+
* This file is automatically loaded by the Vue integration when vueAppSetup is enabled.
|
|
5
|
+
* It configures AWS Amplify for client-side authentication in all Vue components.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/// <reference types="vite/client" />
|
|
9
|
+
|
|
10
|
+
console.log("[htlkg Vue Setup] MODULE LOADED - vue-app-setup.ts is being imported");
|
|
11
|
+
|
|
12
|
+
import { amplifyConfig } from "virtual:htlkg-config";
|
|
13
|
+
import type { App } from "vue";
|
|
14
|
+
import type { ResourcesConfig } from "aws-amplify";
|
|
15
|
+
import { Amplify } from "aws-amplify";
|
|
16
|
+
|
|
17
|
+
console.log("[htlkg Vue Setup] MODULE LEVEL - After imports, amplifyConfig:", amplifyConfig);
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Setup function called by Astro's Vue integration
|
|
21
|
+
* Configures Amplify for client-side authentication
|
|
22
|
+
*/
|
|
23
|
+
export default function setupVueApp(app: App): void {
|
|
24
|
+
console.log("[htlkg Vue Setup] Starting Vue app setup...");
|
|
25
|
+
console.log("[htlkg Vue Setup] amplifyConfig:", amplifyConfig ? "present" : "null/undefined");
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (!amplifyConfig) {
|
|
29
|
+
console.warn("[htlkg Vue Setup] No Amplify configuration provided");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("[htlkg Vue Setup] amplifyConfig keys:", Object.keys(amplifyConfig));
|
|
34
|
+
|
|
35
|
+
// Check if this is a full amplify_outputs.json config
|
|
36
|
+
if (
|
|
37
|
+
"auth" in amplifyConfig ||
|
|
38
|
+
"data" in amplifyConfig ||
|
|
39
|
+
"storage" in amplifyConfig
|
|
40
|
+
) {
|
|
41
|
+
console.log("[htlkg Vue Setup] Detected amplify_outputs.json format");
|
|
42
|
+
Amplify.configure(amplifyConfig as ResourcesConfig, { ssr: true });
|
|
43
|
+
console.info(
|
|
44
|
+
"[htlkg Vue Setup] ✅ Configured Amplify with amplify_outputs.json",
|
|
45
|
+
);
|
|
46
|
+
console.log("[htlkg Vue Setup] Amplify config:", Amplify.getConfig());
|
|
47
|
+
} else {
|
|
48
|
+
// Legacy individual config properties
|
|
49
|
+
console.log("[htlkg Vue Setup] Detected legacy config format");
|
|
50
|
+
const { userPoolId, userPoolClientId, region } = amplifyConfig as {
|
|
51
|
+
userPoolId?: string;
|
|
52
|
+
userPoolClientId?: string;
|
|
53
|
+
region?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
console.log("[htlkg Vue Setup] userPoolId:", userPoolId ? "present" : "missing");
|
|
57
|
+
console.log("[htlkg Vue Setup] userPoolClientId:", userPoolClientId ? "present" : "missing");
|
|
58
|
+
console.log("[htlkg Vue Setup] region:", region || "not specified");
|
|
59
|
+
|
|
60
|
+
if (userPoolId && userPoolClientId) {
|
|
61
|
+
const config: ResourcesConfig = {
|
|
62
|
+
Auth: {
|
|
63
|
+
Cognito: {
|
|
64
|
+
userPoolId,
|
|
65
|
+
userPoolClientId,
|
|
66
|
+
...(region && { region }),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
console.log("[htlkg Vue Setup] Configuring Amplify with:", JSON.stringify(config, null, 2));
|
|
72
|
+
Amplify.configure(config, { ssr: true });
|
|
73
|
+
console.info(
|
|
74
|
+
"[htlkg Vue Setup] ✅ Configured Amplify with legacy config properties",
|
|
75
|
+
);
|
|
76
|
+
console.log("[htlkg Vue Setup] Amplify config:", Amplify.getConfig());
|
|
77
|
+
} else {
|
|
78
|
+
console.error(
|
|
79
|
+
"[htlkg Vue Setup] ❌ Missing required Amplify configuration (userPoolId, userPoolClientId)",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error("[htlkg Vue Setup] ❌ Failed to setup Vue app:", error);
|
|
85
|
+
console.error("[htlkg Vue Setup] Error details:", error instanceof Error ? error.message : error);
|
|
86
|
+
// Don't throw - allow app to continue even if Amplify setup fails
|
|
87
|
+
}
|
|
88
|
+
}
|