@htlkg/astro 0.0.13 → 0.0.17

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 CHANGED
@@ -103,8 +103,8 @@ await requireAdminAccess(Astro);
103
103
  │ └── Topbar.astro
104
104
  ├── htlkg/ # Astro integration
105
105
  │ ├── index.ts # Main integration
106
- ├── config.ts # Configuration types
107
- │ └── virtual-modules.ts
106
+ └── config.ts # Configuration types
107
+ ├── runtime-config.ts # Runtime configuration storage
108
108
  ├── layouts/ # Page layouts
109
109
  │ ├── AdminLayout.astro
110
110
  │ ├── BrandLayout.astro
@@ -1,62 +1,65 @@
1
1
  // src/htlkg/index.ts
2
2
  import tailwind from "@astrojs/tailwind";
3
3
  import vue from "@astrojs/vue";
4
-
5
- // src/htlkg/virtual-modules.ts
6
- function serializePattern(pattern) {
7
- if (pattern instanceof RegExp) {
8
- return `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;
9
- }
10
- return JSON.stringify(pattern);
11
- }
12
- function serializePatterns(patterns) {
13
- if (!patterns || patterns.length === 0) return "[]";
14
- return `[${patterns.map(serializePattern).join(", ")}]`;
15
- }
16
- function createVirtualModulePlugin(authConfig, loginPageConfig, amplifyConfig) {
17
- const virtualModuleId = "virtual:htlkg-config";
18
- const resolvedVirtualModuleId = "\0" + virtualModuleId;
4
+ import inoxToolsRequestNanostores from "@inox-tools/request-nanostores";
5
+ var DEFAULT_ENV_VARS = [
6
+ "PUBLIC_COGNITO_USER_POOL_ID",
7
+ "PUBLIC_COGNITO_USER_POOL_CLIENT_ID"
8
+ ];
9
+ var VIRTUAL_MODULE_ID = "virtual:htlkg";
10
+ var RESOLVED_VIRTUAL_MODULE_ID = "\0" + VIRTUAL_MODULE_ID;
11
+ function createVirtualConfigPlugin(config) {
19
12
  return {
20
- name: "htlkg-config",
21
- enforce: "pre",
13
+ name: "htlkg-virtual-config",
22
14
  resolveId(id) {
23
- if (id === virtualModuleId) {
24
- return resolvedVirtualModuleId;
15
+ if (id === VIRTUAL_MODULE_ID) {
16
+ return RESOLVED_VIRTUAL_MODULE_ID;
25
17
  }
26
18
  },
27
19
  load(id) {
28
- if (id === resolvedVirtualModuleId) {
29
- const serializedAuthConfig = `{
30
- publicRoutes: ${serializePatterns(authConfig.publicRoutes || [])},
31
- authenticatedRoutes: ${serializePatterns(authConfig.authenticatedRoutes || [])},
32
- adminRoutes: ${serializePatterns(authConfig.adminRoutes || [])},
33
- brandRoutes: ${JSON.stringify(authConfig.brandRoutes || [])},
34
- loginUrl: ${JSON.stringify(authConfig.loginUrl || "/login")}
35
- }`;
36
- const serializedAmplifyConfig = amplifyConfig ? JSON.stringify(amplifyConfig) : "null";
37
- const serializedLoginPageConfig = loginPageConfig !== false && loginPageConfig !== null ? JSON.stringify(loginPageConfig) : "null";
38
- return `export const routeGuardConfig = ${serializedAuthConfig};
39
- export const loginPageConfig = ${serializedLoginPageConfig};
40
- export const amplifyConfig = ${serializedAmplifyConfig};`;
20
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
21
+ const serializedConfig = serializeConfig(config);
22
+ return `export const htlkgConfig = ${serializedConfig};`;
41
23
  }
42
24
  }
43
25
  };
44
26
  }
45
- var virtualModuleTypes = `
46
- declare module 'virtual:htlkg-config' {
47
- import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';
48
-
49
- export const routeGuardConfig: RouteGuardConfig;
50
- export const loginPageConfig: LoginPageConfig | null;
51
- export const amplifyConfig: Record<string, unknown> | null;
27
+ function serializeConfig(config) {
28
+ const routeGuard = config.routeGuard || {};
29
+ const routeGuardParts = [];
30
+ if (routeGuard.publicRoutes) {
31
+ routeGuardParts.push(`publicRoutes: ${serializePatterns(routeGuard.publicRoutes)}`);
32
+ }
33
+ if (routeGuard.authenticatedRoutes) {
34
+ routeGuardParts.push(`authenticatedRoutes: ${serializePatterns(routeGuard.authenticatedRoutes)}`);
35
+ }
36
+ if (routeGuard.adminRoutes) {
37
+ routeGuardParts.push(`adminRoutes: ${serializePatterns(routeGuard.adminRoutes)}`);
38
+ }
39
+ if (routeGuard.brandRoutes && routeGuard.brandRoutes.length > 0) {
40
+ const brandRoutesStr = routeGuard.brandRoutes.map(
41
+ (br) => `{ pattern: ${br.pattern.toString()}, brandIdParam: ${br.brandIdParam} }`
42
+ ).join(", ");
43
+ routeGuardParts.push(`brandRoutes: [${brandRoutesStr}]`);
44
+ }
45
+ if (routeGuard.loginUrl) {
46
+ routeGuardParts.push(`loginUrl: ${JSON.stringify(routeGuard.loginUrl)}`);
47
+ }
48
+ return `{
49
+ routeGuardConfig: { ${routeGuardParts.join(", ")} },
50
+ loginPageConfig: ${JSON.stringify(config.loginPage)},
51
+ amplifyConfig: ${JSON.stringify(config.amplify)}
52
+ }`;
53
+ }
54
+ function serializePatterns(patterns) {
55
+ const serialized = patterns.map((p) => {
56
+ if (typeof p === "string") {
57
+ return JSON.stringify(p);
58
+ }
59
+ return p.toString();
60
+ });
61
+ return `[${serialized.join(", ")}]`;
52
62
  }
53
- `;
54
-
55
- // src/htlkg/index.ts
56
- var DEFAULT_ENV_VARS = [
57
- "PUBLIC_COGNITO_USER_POOL_ID",
58
- "PUBLIC_COGNITO_USER_POOL_CLIENT_ID"
59
- ];
60
63
  function htlkg(options = {}) {
61
64
  const {
62
65
  auth = {},
@@ -80,6 +83,12 @@ function htlkg(options = {}) {
80
83
  } else {
81
84
  integrations.push(vue());
82
85
  }
86
+ integrations.push(inoxToolsRequestNanostores());
87
+ const htlkgVirtualConfig = {
88
+ routeGuard: auth,
89
+ loginPage: loginPage || null,
90
+ amplify: amplify || null
91
+ };
83
92
  integrations.push({
84
93
  name: "@htlkg/astro",
85
94
  hooks: {
@@ -95,8 +104,9 @@ function htlkg(options = {}) {
95
104
  logger.warn(`Missing env vars: ${missing.join(", ")}`);
96
105
  }
97
106
  }
98
- const virtualModulePlugin = createVirtualModulePlugin(auth, loginPage, amplify || null);
99
- const vitePlugins = [virtualModulePlugin];
107
+ const vitePlugins = [
108
+ createVirtualConfigPlugin(htlkgVirtualConfig)
109
+ ];
100
110
  const isDev = process.env.NODE_ENV !== "production";
101
111
  if (isDev) {
102
112
  try {
@@ -106,7 +116,12 @@ function htlkg(options = {}) {
106
116
  } catch {
107
117
  }
108
118
  }
109
- updateConfig({ vite: { plugins: vitePlugins } });
119
+ updateConfig({
120
+ vite: {
121
+ plugins: vitePlugins
122
+ }
123
+ });
124
+ logger.info("virtual:htlkg module configured");
110
125
  if (useFullVueSetup) {
111
126
  addMiddleware({ entrypoint: "@htlkg/astro/middleware", order: "pre" });
112
127
  logger.info("Authentication middleware injected");
@@ -132,6 +147,15 @@ function htlkg(options = {}) {
132
147
  filename: "htlkg.d.ts",
133
148
  content: `
134
149
  import type { AuthUser } from '@htlkg/core/types';
150
+ import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';
151
+
152
+ declare module 'virtual:htlkg' {
153
+ export const htlkgConfig: {
154
+ routeGuardConfig: RouteGuardConfig | null;
155
+ loginPageConfig: LoginPageConfig | null;
156
+ amplifyConfig: Record<string, unknown> | null;
157
+ };
158
+ }
135
159
 
136
160
  declare global {
137
161
  namespace App {
@@ -141,8 +165,6 @@ declare global {
141
165
  }
142
166
  }
143
167
 
144
- ${virtualModuleTypes}
145
-
146
168
  export {};
147
169
  `
148
170
  });
@@ -155,4 +177,4 @@ export {};
155
177
  export {
156
178
  htlkg
157
179
  };
158
- //# sourceMappingURL=chunk-IA7L6IR5.js.map
180
+ //# sourceMappingURL=chunk-QWEHFUIH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/htlkg/index.ts"],"sourcesContent":["/**\n * htlkg Astro Integration\n *\n * Supports static, hybrid, and full SSR output modes.\n * Uses a virtual Vite module (virtual:htlkg) to provide configuration\n * to middleware and Vue app setup.\n */\n\nimport tailwind from '@astrojs/tailwind';\nimport vue from '@astrojs/vue';\nimport inoxToolsRequestNanostores from '@inox-tools/request-nanostores';\nimport type { AstroIntegration } from 'astro';\nimport type { Plugin } from 'vite';\nimport type { HtlkgIntegrationOptions, RouteGuardConfig, LoginPageConfig } from './config.js';\n\nconst DEFAULT_ENV_VARS = [\n\t'PUBLIC_COGNITO_USER_POOL_ID',\n\t'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'\n];\n\nconst VIRTUAL_MODULE_ID = 'virtual:htlkg';\nconst RESOLVED_VIRTUAL_MODULE_ID = '\\0' + VIRTUAL_MODULE_ID;\n\n/**\n * Create Vite plugin that provides the virtual:htlkg module\n */\nfunction createVirtualConfigPlugin(config: {\n\trouteGuard: RouteGuardConfig;\n\tloginPage: LoginPageConfig | null;\n\tamplify: Record<string, unknown> | null;\n}): Plugin {\n\treturn {\n\t\tname: 'htlkg-virtual-config',\n\t\tresolveId(id) {\n\t\t\tif (id === VIRTUAL_MODULE_ID) {\n\t\t\t\treturn RESOLVED_VIRTUAL_MODULE_ID;\n\t\t\t}\n\t\t},\n\t\tload(id) {\n\t\t\tif (id === RESOLVED_VIRTUAL_MODULE_ID) {\n\t\t\t\t// Serialize the config to be loaded as a module\n\t\t\t\t// We need to handle RegExp serialization for route patterns\n\t\t\t\tconst serializedConfig = serializeConfig(config);\n\t\t\t\treturn `export const htlkgConfig = ${serializedConfig};`;\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Serialize config object to JavaScript code\n * Handles RegExp patterns which can't be JSON.stringify'd\n */\nfunction serializeConfig(config: {\n\trouteGuard: RouteGuardConfig;\n\tloginPage: LoginPageConfig | null;\n\tamplify: Record<string, unknown> | null;\n}): string {\n\tconst routeGuard = config.routeGuard || {};\n\n\t// Build route guard config with proper RegExp handling\n\tconst routeGuardParts: string[] = [];\n\n\tif (routeGuard.publicRoutes) {\n\t\trouteGuardParts.push(`publicRoutes: ${serializePatterns(routeGuard.publicRoutes)}`);\n\t}\n\tif (routeGuard.authenticatedRoutes) {\n\t\trouteGuardParts.push(`authenticatedRoutes: ${serializePatterns(routeGuard.authenticatedRoutes)}`);\n\t}\n\tif (routeGuard.adminRoutes) {\n\t\trouteGuardParts.push(`adminRoutes: ${serializePatterns(routeGuard.adminRoutes)}`);\n\t}\n\tif (routeGuard.brandRoutes && routeGuard.brandRoutes.length > 0) {\n\t\tconst brandRoutesStr = routeGuard.brandRoutes.map(br =>\n\t\t\t`{ pattern: ${br.pattern.toString()}, brandIdParam: ${br.brandIdParam} }`\n\t\t).join(', ');\n\t\trouteGuardParts.push(`brandRoutes: [${brandRoutesStr}]`);\n\t}\n\tif (routeGuard.loginUrl) {\n\t\trouteGuardParts.push(`loginUrl: ${JSON.stringify(routeGuard.loginUrl)}`);\n\t}\n\n\treturn `{\n\trouteGuardConfig: { ${routeGuardParts.join(', ')} },\n\tloginPageConfig: ${JSON.stringify(config.loginPage)},\n\tamplifyConfig: ${JSON.stringify(config.amplify)}\n}`;\n}\n\n/**\n * Serialize an array of route patterns (strings or RegExp)\n */\nfunction serializePatterns(patterns: (string | RegExp)[]): string {\n\tconst serialized = patterns.map(p => {\n\t\tif (typeof p === 'string') {\n\t\t\treturn JSON.stringify(p);\n\t\t}\n\t\t// RegExp - output as literal\n\t\treturn p.toString();\n\t});\n\treturn `[${serialized.join(', ')}]`;\n}\n\nexport function htlkg(\n\toptions: HtlkgIntegrationOptions = {},\n): AstroIntegration | AstroIntegration[] {\n\tconst {\n\t\tauth = {},\n\t\tloginPage = { path: '/login', title: 'Sign In', redirectUrl: '/admin' },\n\t\tvalidateEnv = true,\n\t\trequiredEnvVars = DEFAULT_ENV_VARS,\n\t\ttailwind: tailwindOptions,\n\t\tamplify,\n\t\tvueAppSetup = 'auto',\n\t} = options;\n\n\tconst integrations: AstroIntegration[] = [];\n\n\t// Add Tailwind integration (enabled by default)\n\tif (tailwindOptions !== false) {\n\t\tconst tailwindConfig =\n\t\t\ttypeof tailwindOptions === 'object' ? tailwindOptions : undefined;\n\t\tintegrations.push(\n\t\t\ttailwind(tailwindConfig as Parameters<typeof tailwind>[0]),\n\t\t);\n\t}\n\n\t// Determine Vue setup mode:\n\t// - 'full': Use Amplify app entrypoint (requires SSR)\n\t// - 'basic': Basic Vue without app entrypoint (works with static)\n\t// - 'auto': Default to 'basic' for compatibility with static builds\n\tconst useFullVueSetup = vueAppSetup === 'full';\n\n\t// Add Vue integration\n\tif (useFullVueSetup) {\n\t\tintegrations.push(vue({ appEntrypoint: '@htlkg/astro/vue-app-setup' }));\n\t} else {\n\t\tintegrations.push(vue());\n\t}\n\n\t// Add request nanostores integration (for SSR-safe nanostores)\n\tintegrations.push(inoxToolsRequestNanostores());\n\n\t// Prepare configuration for virtual module\n\tconst htlkgVirtualConfig = {\n\t\trouteGuard: auth,\n\t\tloginPage: loginPage || null,\n\t\tamplify: amplify || null,\n\t};\n\n\t// Add the main htlkg integration\n\tintegrations.push({\n\t\tname: '@htlkg/astro',\n\t\thooks: {\n\t\t\t'astro:config:setup': async ({ updateConfig, addMiddleware, injectRoute, logger }) => {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.info(useFullVueSetup\n\t\t\t\t\t\t? 'Vue configured with Amplify app setup'\n\t\t\t\t\t\t: 'Vue configured (basic mode)');\n\n\t\t\t\t\tif (amplify) {\n\t\t\t\t\t\tlogger.info('Amplify configuration provided');\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate env vars (only for full setup)\n\t\t\t\t\tif (validateEnv && !amplify && useFullVueSetup) {\n\t\t\t\t\t\tconst missing = requiredEnvVars.filter(v => !process.env[v]);\n\t\t\t\t\t\tif (missing.length > 0) {\n\t\t\t\t\t\t\tlogger.warn(`Missing env vars: ${missing.join(', ')}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create virtual module plugin for config\n\t\t\t\t\tconst vitePlugins: Plugin[] = [\n\t\t\t\t\t\tcreateVirtualConfigPlugin(htlkgVirtualConfig),\n\t\t\t\t\t];\n\n\t\t\t\t\t// Only load Vue DevTools during development\n\t\t\t\t\tconst isDev = process.env.NODE_ENV !== 'production';\n\t\t\t\t\tif (isDev) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst vueDevTools = (await import('vite-plugin-vue-devtools')).default;\n\t\t\t\t\t\t\tvitePlugins.push(vueDevTools());\n\t\t\t\t\t\t\tlogger.info('Vue DevTools enabled');\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// DevTools not available, skip silently\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Update Vite config with plugins\n\t\t\t\t\tupdateConfig({\n\t\t\t\t\t\tvite: {\n\t\t\t\t\t\t\tplugins: vitePlugins\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\n\t\t\t\t\tlogger.info('virtual:htlkg module configured');\n\n\t\t\t\t\t// Inject middleware (only for full setup)\n\t\t\t\t\tif (useFullVueSetup) {\n\t\t\t\t\t\taddMiddleware({ entrypoint: '@htlkg/astro/middleware', order: 'pre' });\n\t\t\t\t\t\tlogger.info('Authentication middleware injected');\n\t\t\t\t\t}\n\n\t\t\t\t\t// Inject login page (only for full setup)\n\t\t\t\t\tif (loginPage !== false && useFullVueSetup) {\n\t\t\t\t\t\tconst loginPath = loginPage.path || '/login';\n\t\t\t\t\t\tinjectRoute({\n\t\t\t\t\t\t\tpattern: loginPath,\n\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/auth/LoginPage.astro',\n\t\t\t\t\t\t\tprerender: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlogger.info(`Login page injected at ${loginPath}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tlogger.info('htlkg integration configured');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = error instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\tlogger.error(`htlkg configuration failed: ${msg}`);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t'astro:config:done': ({ injectTypes }) => {\n\t\t\t\tinjectTypes({\n\t\t\t\t\tfilename: 'htlkg.d.ts',\n\t\t\t\t\tcontent: `\nimport type { AuthUser } from '@htlkg/core/types';\nimport type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';\n\ndeclare module 'virtual:htlkg' {\n export const htlkgConfig: {\n routeGuardConfig: RouteGuardConfig | null;\n loginPageConfig: LoginPageConfig | null;\n amplifyConfig: Record<string, unknown> | null;\n };\n}\n\ndeclare global {\n namespace App {\n interface Locals {\n user: AuthUser | null;\n }\n }\n}\n\nexport {};\n`,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t});\n\n\treturn integrations;\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  htlkg
3
- } from "../chunk-IA7L6IR5.js";
3
+ } from "../chunk-QWEHFUIH.js";
4
4
  export {
5
5
  htlkg
6
6
  };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  htlkg
3
- } from "./chunk-IA7L6IR5.js";
3
+ } from "./chunk-QWEHFUIH.js";
4
4
  import {
5
5
  isAuthenticatedUser
6
6
  } from "./chunk-UBF5F2RG.js";
@@ -17,6 +17,16 @@ import {
17
17
  processListData,
18
18
  sortItems
19
19
  } from "./chunk-2GML443T.js";
20
+ import {
21
+ getClientIP,
22
+ getQueryParams,
23
+ getRequestHeaders,
24
+ getServerData,
25
+ isMobileDevice,
26
+ isServerSide,
27
+ setCacheControl,
28
+ setResponseHeaders
29
+ } from "./chunk-WNMPTDCR.js";
20
30
  import {
21
31
  chunkArray,
22
32
  filterItems,
@@ -35,16 +45,6 @@ import {
35
45
  serializeForHydration,
36
46
  shouldHydrate
37
47
  } from "./chunk-64USRLVP.js";
38
- import {
39
- getClientIP,
40
- getQueryParams,
41
- getRequestHeaders,
42
- getServerData,
43
- isMobileDevice,
44
- isServerSide,
45
- setCacheControl,
46
- setResponseHeaders
47
- } from "./chunk-WNMPTDCR.js";
48
48
 
49
49
  // src/factories/createListPage.ts
50
50
  function generateBreadcrumbs(pageId, title) {
@@ -5,11 +5,12 @@ import { sequence } from "astro:middleware";
5
5
  import { Amplify } from "aws-amplify";
6
6
  import { getUser } from "@htlkg/core/auth";
7
7
  import { globalSettings } from "@htlkg/core/amplify-astro-adapter";
8
- import { amplifyConfig } from "virtual:htlkg-config";
8
+ import { htlkgConfig } from "virtual:htlkg";
9
9
  var amplifyConfigured = false;
10
10
  function ensureAmplifyConfigured() {
11
11
  if (amplifyConfigured) return;
12
12
  try {
13
+ const { amplifyConfig } = htlkgConfig;
13
14
  if (!amplifyConfig) {
14
15
  console.warn("[htlkg Auth] No Amplify configuration provided");
15
16
  return;
@@ -44,8 +45,7 @@ var authMiddleware = async (context, next) => {
44
45
  };
45
46
 
46
47
  // src/middleware/route-guards.ts
47
- import { routeGuardConfig } from "virtual:htlkg-config";
48
- var config = routeGuardConfig;
48
+ import { htlkgConfig as htlkgConfig2 } from "virtual:htlkg";
49
49
  function matchesPattern(pathname, patterns) {
50
50
  return patterns.some((pattern) => {
51
51
  try {
@@ -68,13 +68,14 @@ function matchesPattern(pathname, patterns) {
68
68
  var routeGuard = async (context, next) => {
69
69
  const { locals, url, redirect } = context;
70
70
  const pathname = url.pathname;
71
+ const { routeGuardConfig } = htlkgConfig2;
71
72
  const {
72
73
  publicRoutes = [],
73
74
  authenticatedRoutes = [],
74
75
  adminRoutes = [],
75
76
  brandRoutes = [],
76
77
  loginUrl = "/login"
77
- } = config;
78
+ } = routeGuardConfig || {};
78
79
  try {
79
80
  if (matchesPattern(pathname, publicRoutes)) {
80
81
  return next();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/middleware/index.ts","../../src/middleware/auth.ts","../../src/middleware/route-guards.ts"],"sourcesContent":["/**\n * Middleware for @htlkg/astro\n * \n * This module exports:\n * - authMiddleware: Retrieves authenticated user and injects into locals\n * - routeGuard: Enforces access control based on route configuration\n * - onRequest: Composed middleware chain (auth + route guard)\n * - Helper functions: requireAuth, requireAdminAccess, requireBrandAccess\n */\n\nimport { sequence } from 'astro:middleware';\nimport { authMiddleware } from './auth.js';\nimport { routeGuard } from './route-guards.js';\n\n// Export individual middleware\nexport { authMiddleware } from './auth.js';\nexport {\n\trouteGuard,\n\trequireAuth,\n\trequireAdminAccess,\n\trequireBrandAccess,\n} from './route-guards.js';\n\n/**\n * Export composed middleware chain\n * Auth middleware runs first to populate locals.user\n * Route guard middleware runs second to enforce access control\n * \n * This is the default export that gets injected by the htlkg integration\n */\nexport const onRequest = sequence(authMiddleware, routeGuard);\n","/**\n * Authentication middleware for htlkg integration\n * \n * This middleware:\n * - Configures AWS Amplify for server-side auth\n * - Retrieves the authenticated user from AWS Amplify\n * - Injects the user into Astro.locals for use in pages and API routes\n * - Handles authentication errors gracefully\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport { Amplify } from 'aws-amplify';\nimport { getUser } from '@htlkg/core/auth';\nimport { globalSettings } from '@htlkg/core/amplify-astro-adapter';\nimport { amplifyConfig } from 'virtual:htlkg-config';\n\n// Track if Amplify has been configured\nlet amplifyConfigured = false;\n\n/**\n * Configure Amplify on first request\n */\nfunction ensureAmplifyConfigured(): void {\n\tif (amplifyConfigured) return;\n\n\ttry {\n\t\tif (!amplifyConfig) {\n\t\t\tconsole.warn('[htlkg Auth] No Amplify configuration provided');\n\t\t\treturn;\n\t\t}\n\n\t\t// Configure Amplify with SSR support\n\t\tAmplify.configure(amplifyConfig as any, { ssr: true });\n\t\t\n\t\t// Enable server-side auth in the adapter\n\t\tglobalSettings.enableServerSideAuth();\n\t\t\n\t\t// Check if we're on HTTPS (for secure cookies)\n\t\tconst isSSL = typeof window !== 'undefined' \n\t\t\t? window.location.protocol === 'https:' \n\t\t\t: process.env.NODE_ENV === 'production';\n\t\tglobalSettings.setIsSSLOrigin(isSSL);\n\t\t\n\t\tamplifyConfigured = true;\n\t\tconsole.info('[htlkg Auth] Amplify configured for server-side auth');\n\t} catch (error) {\n\t\tconst errorMsg = error instanceof Error ? error.message : 'Unknown error';\n\t\tconsole.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);\n\t}\n}\n\n/**\n * Auth middleware - retrieves authenticated user and injects into locals\n * \n * This middleware runs on every request and attempts to get the current\n * authenticated user from AWS Amplify. If successful, the user is injected\n * into context.locals.user. If authentication fails, locals.user is set to null.\n * \n * @example\n * // In an Astro page\n * const { user } = Astro.locals;\n * if (user) {\n * console.log('Authenticated user:', user.email);\n * }\n */\nexport const authMiddleware: MiddlewareHandler = async (context, next) => {\n\tconst { locals } = context;\n\n\t// Ensure Amplify is configured before attempting auth\n\tensureAmplifyConfigured();\n\n\ttry {\n\t\tconst user = await getUser(context);\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\t// Set user to null on any authentication error\n\t\tlocals.user = null;\n\n\t\t// Log error without exposing sensitive information\n\t\tif (error instanceof Error) {\n\t\t\t// Filter out sensitive data from error messages\n\t\t\tconst safeMessage = error.message\n\t\t\t\t.replace(/token[=:]\\s*[^\\s,}]+/gi, 'token=***')\n\t\t\t\t.replace(/key[=:]\\s*[^\\s,}]+/gi, 'key=***')\n\t\t\t\t.replace(/secret[=:]\\s*[^\\s,}]+/gi, 'secret=***')\n\t\t\t\t.replace(/password[=:]\\s*[^\\s,}]+/gi, 'password=***');\n\n\t\t\tconsole.error('[htlkg Auth] Authentication failed:', safeMessage);\n\t\t} else {\n\t\t\tconsole.error('[htlkg Auth] Authentication failed: Unknown error');\n\t\t}\n\t}\n\n\treturn next();\n};\n","/**\n * Route guard middleware for htlkg integration\n * \n * This middleware enforces access control based on route configuration:\n * - Public routes: accessible to everyone\n * - Authenticated routes: require any logged-in user\n * - Admin routes: require admin privileges\n * - Brand routes: require brand-specific access or admin privileges\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport type { RoutePattern, RouteGuardConfig } from '../htlkg/config.js';\n\n// Import configuration from virtual module\nimport { routeGuardConfig } from 'virtual:htlkg-config';\n\n// Type assertion for the imported config\nconst config = routeGuardConfig as RouteGuardConfig;\n\n/**\n * Helper: Check if pathname matches any of the provided patterns\n */\nfunction matchesPattern(pathname: string, patterns: RoutePattern[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\ttry {\n\t\t\tif (typeof pattern === 'string') {\n\t\t\t\t// Special case: \"/\" only matches exactly\n\t\t\t\tif (pattern === '/') {\n\t\t\t\t\treturn pathname === '/';\n\t\t\t\t}\n\t\t\t\t// String pattern: exact match or starts with (for sub-routes)\n\t\t\t\treturn pathname === pattern || pathname.startsWith(pattern + '/');\n\t\t\t}\n\t\t\t// RegExp pattern: test against pathname\n\t\t\treturn pattern.test(pathname);\n\t\t} catch (error) {\n\t\t\t// Log pattern matching errors but don't block the request\n\t\t\tconsole.error(\n\t\t\t\t'[htlkg] Error matching route pattern:',\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * Route guard middleware - protects routes based on configuration\n * \n * This middleware runs after authMiddleware and enforces access control\n * based on the route configuration provided to the htlkg integration.\n * \n * @example\n * // In astro.config.mjs\n * htlkg({\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * brandRoutes: [\n * { pattern: /^\\/brands\\/(\\d+)/, brandIdParam: 1 }\n * ],\n * loginUrl: '/login'\n * }\n * })\n */\nexport const routeGuard: MiddlewareHandler = async (context, next) => {\n\tconst { locals, url, redirect } = context;\n\tconst pathname = url.pathname;\n\n\tconst {\n\t\tpublicRoutes = [],\n\t\tauthenticatedRoutes = [],\n\t\tadminRoutes = [],\n\t\tbrandRoutes = [],\n\t\tloginUrl = '/login',\n\t} = config;\n\n\ttry {\n\t\t// Public routes - no auth required\n\t\tif (matchesPattern(pathname, publicRoutes)) {\n\t\t\treturn next();\n\t\t}\n\n\t\tconst user = locals.user;\n\n\t\t// Admin routes - require admin role\n\t\tif (matchesPattern(pathname, adminRoutes)) {\n\t\t\tif (!user || !user.isAdmin) {\n\t\t\t\treturn redirect(`${loginUrl}?error=admin_required`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Brand routes - require brand access or admin\n\t\tfor (const brandRoute of brandRoutes) {\n\t\t\ttry {\n\t\t\t\tconst match = pathname.match(brandRoute.pattern);\n\t\t\t\tif (match) {\n\t\t\t\t\tconst brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);\n\n\t\t\t\t\tif (Number.isNaN(brandId)) {\n\t\t\t\t\t\tconsole.warn(`[htlkg] Invalid brandId extracted from ${pathname}`);\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=invalid_brand`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!user || (!user.isAdmin && !user.brandIds.includes(brandId))) {\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t\t\t}\n\t\t\t\t\treturn next();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[htlkg] Error processing brand route for ${pathname}:`,\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t\t);\n\t\t\t\t// Fail-safe: deny access on error\n\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t}\n\t\t}\n\n\t\t// Authenticated routes - require any logged-in user\n\t\tif (matchesPattern(pathname, authenticatedRoutes)) {\n\t\t\tif (!user) {\n\t\t\t\tconst returnUrl = encodeURIComponent(pathname + url.search);\n\t\t\t\treturn redirect(`${loginUrl}?redirect=${returnUrl}`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Default: allow access\n\t\treturn next();\n\t} catch (error) {\n\t\t// Catch-all error handler for route guard\n\t\tconsole.error(\n\t\t\t'[htlkg] Unexpected error in route guard:',\n\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t);\n\t\t// Fail-safe: deny access and redirect to login\n\t\treturn redirect(`${loginUrl}?error=server_error`);\n\t}\n};\n\n/**\n * Helper functions for programmatic route protection in pages\n * These can be used in Astro pages for more fine-grained control\n */\n\n/**\n * Require authentication for a page\n * Redirects to login if user is not authenticated\n */\nexport async function requireAuth(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\tconst currentUrl = context.url.pathname + context.url.search;\n\t\tconst encodedReturnUrl = encodeURIComponent(currentUrl);\n\t\treturn context.redirect(`${loginUrl}?redirect=${encodedReturnUrl}`);\n\t}\n\treturn user;\n}\n\n/**\n * Require admin access for a page\n * Redirects to login if user is not an admin\n */\nexport async function requireAdminAccess(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin) {\n\t\treturn context.redirect(`${loginUrl}?error=admin_required`);\n\t}\n\treturn user;\n}\n\n/**\n * Require brand access for a page\n * Redirects to login if user doesn't have access to the specified brand\n */\nexport async function requireBrandAccess(\n\tcontext: any,\n\tbrandId: number,\n\tloginUrl = '/login'\n) {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin && !user.brandIds.includes(brandId)) {\n\t\treturn context.redirect(`${loginUrl}?error=access_denied`);\n\t}\n\treturn user;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/middleware/index.ts","../../src/middleware/auth.ts","../../src/middleware/route-guards.ts"],"sourcesContent":["/**\n * Middleware for @htlkg/astro\n * \n * This module exports:\n * - authMiddleware: Retrieves authenticated user and injects into locals\n * - routeGuard: Enforces access control based on route configuration\n * - onRequest: Composed middleware chain (auth + route guard)\n * - Helper functions: requireAuth, requireAdminAccess, requireBrandAccess\n */\n\nimport { sequence } from 'astro:middleware';\nimport { authMiddleware } from './auth.js';\nimport { routeGuard } from './route-guards.js';\n\n// Export individual middleware\nexport { authMiddleware } from './auth.js';\nexport {\n\trouteGuard,\n\trequireAuth,\n\trequireAdminAccess,\n\trequireBrandAccess,\n} from './route-guards.js';\n\n/**\n * Export composed middleware chain\n * Auth middleware runs first to populate locals.user\n * Route guard middleware runs second to enforce access control\n * \n * This is the default export that gets injected by the htlkg integration\n */\nexport const onRequest = sequence(authMiddleware, routeGuard);\n","/**\n * Authentication middleware for htlkg integration\n *\n * This middleware:\n * - Configures AWS Amplify for server-side auth\n * - Retrieves the authenticated user from AWS Amplify\n * - Injects the user into Astro.locals for use in pages and API routes\n * - Handles authentication errors gracefully\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport { Amplify } from 'aws-amplify';\nimport { getUser } from '@htlkg/core/auth';\nimport { globalSettings } from '@htlkg/core/amplify-astro-adapter';\nimport { htlkgConfig } from 'virtual:htlkg';\n\n// Track if Amplify has been configured\nlet amplifyConfigured = false;\n\n/**\n * Configure Amplify on first request\n */\nfunction ensureAmplifyConfigured(): void {\n\tif (amplifyConfigured) return;\n\n\ttry {\n\t\tconst { amplifyConfig } = htlkgConfig;\n\t\tif (!amplifyConfig) {\n\t\t\tconsole.warn('[htlkg Auth] No Amplify configuration provided');\n\t\t\treturn;\n\t\t}\n\n\t\t// Configure Amplify with SSR support\n\t\tAmplify.configure(amplifyConfig as any, { ssr: true });\n\t\t\n\t\t// Enable server-side auth in the adapter\n\t\tglobalSettings.enableServerSideAuth();\n\t\t\n\t\t// Check if we're on HTTPS (for secure cookies)\n\t\tconst isSSL = typeof window !== 'undefined' \n\t\t\t? window.location.protocol === 'https:' \n\t\t\t: process.env.NODE_ENV === 'production';\n\t\tglobalSettings.setIsSSLOrigin(isSSL);\n\t\t\n\t\tamplifyConfigured = true;\n\t\tconsole.info('[htlkg Auth] Amplify configured for server-side auth');\n\t} catch (error) {\n\t\tconst errorMsg = error instanceof Error ? error.message : 'Unknown error';\n\t\tconsole.error(`[htlkg Auth] Failed to configure Amplify: ${errorMsg}`);\n\t}\n}\n\n/**\n * Auth middleware - retrieves authenticated user and injects into locals\n * \n * This middleware runs on every request and attempts to get the current\n * authenticated user from AWS Amplify. If successful, the user is injected\n * into context.locals.user. If authentication fails, locals.user is set to null.\n * \n * @example\n * // In an Astro page\n * const { user } = Astro.locals;\n * if (user) {\n * console.log('Authenticated user:', user.email);\n * }\n */\nexport const authMiddleware: MiddlewareHandler = async (context, next) => {\n\tconst { locals } = context;\n\n\t// Ensure Amplify is configured before attempting auth\n\tensureAmplifyConfigured();\n\n\ttry {\n\t\tconst user = await getUser(context);\n\t\tlocals.user = user;\n\t} catch (error) {\n\t\t// Set user to null on any authentication error\n\t\tlocals.user = null;\n\n\t\t// Log error without exposing sensitive information\n\t\tif (error instanceof Error) {\n\t\t\t// Filter out sensitive data from error messages\n\t\t\tconst safeMessage = error.message\n\t\t\t\t.replace(/token[=:]\\s*[^\\s,}]+/gi, 'token=***')\n\t\t\t\t.replace(/key[=:]\\s*[^\\s,}]+/gi, 'key=***')\n\t\t\t\t.replace(/secret[=:]\\s*[^\\s,}]+/gi, 'secret=***')\n\t\t\t\t.replace(/password[=:]\\s*[^\\s,}]+/gi, 'password=***');\n\n\t\t\tconsole.error('[htlkg Auth] Authentication failed:', safeMessage);\n\t\t} else {\n\t\t\tconsole.error('[htlkg Auth] Authentication failed: Unknown error');\n\t\t}\n\t}\n\n\treturn next();\n};\n","/**\n * Route guard middleware for htlkg integration\n *\n * This middleware enforces access control based on route configuration:\n * - Public routes: accessible to everyone\n * - Authenticated routes: require any logged-in user\n * - Admin routes: require admin privileges\n * - Brand routes: require brand-specific access or admin privileges\n */\n\nimport type { MiddlewareHandler } from 'astro';\nimport type { RoutePattern } from '../htlkg/config.js';\nimport { htlkgConfig } from 'virtual:htlkg';\n\n/**\n * Helper: Check if pathname matches any of the provided patterns\n */\nfunction matchesPattern(pathname: string, patterns: RoutePattern[]): boolean {\n\treturn patterns.some((pattern) => {\n\t\ttry {\n\t\t\tif (typeof pattern === 'string') {\n\t\t\t\t// Special case: \"/\" only matches exactly\n\t\t\t\tif (pattern === '/') {\n\t\t\t\t\treturn pathname === '/';\n\t\t\t\t}\n\t\t\t\t// String pattern: exact match or starts with (for sub-routes)\n\t\t\t\treturn pathname === pattern || pathname.startsWith(pattern + '/');\n\t\t\t}\n\t\t\t// RegExp pattern: test against pathname\n\t\t\treturn pattern.test(pathname);\n\t\t} catch (error) {\n\t\t\t// Log pattern matching errors but don't block the request\n\t\t\tconsole.error(\n\t\t\t\t'[htlkg] Error matching route pattern:',\n\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t});\n}\n\n/**\n * Route guard middleware - protects routes based on configuration\n * \n * This middleware runs after authMiddleware and enforces access control\n * based on the route configuration provided to the htlkg integration.\n * \n * @example\n * // In astro.config.mjs\n * htlkg({\n * auth: {\n * publicRoutes: ['/login', '/'],\n * adminRoutes: [/^\\/admin/],\n * brandRoutes: [\n * { pattern: /^\\/brands\\/(\\d+)/, brandIdParam: 1 }\n * ],\n * loginUrl: '/login'\n * }\n * })\n */\nexport const routeGuard: MiddlewareHandler = async (context, next) => {\n\tconst { locals, url, redirect } = context;\n\tconst pathname = url.pathname;\n\n\tconst { routeGuardConfig } = htlkgConfig;\n\tconst {\n\t\tpublicRoutes = [],\n\t\tauthenticatedRoutes = [],\n\t\tadminRoutes = [],\n\t\tbrandRoutes = [],\n\t\tloginUrl = '/login',\n\t} = routeGuardConfig || {};\n\n\ttry {\n\t\t// Public routes - no auth required\n\t\tif (matchesPattern(pathname, publicRoutes)) {\n\t\t\treturn next();\n\t\t}\n\n\t\tconst user = locals.user;\n\n\t\t// Admin routes - require admin role\n\t\tif (matchesPattern(pathname, adminRoutes)) {\n\t\t\tif (!user || !user.isAdmin) {\n\t\t\t\treturn redirect(`${loginUrl}?error=admin_required`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Brand routes - require brand access or admin\n\t\tfor (const brandRoute of brandRoutes) {\n\t\t\ttry {\n\t\t\t\tconst match = pathname.match(brandRoute.pattern);\n\t\t\t\tif (match) {\n\t\t\t\t\tconst brandId = Number.parseInt(match[brandRoute.brandIdParam], 10);\n\n\t\t\t\t\tif (Number.isNaN(brandId)) {\n\t\t\t\t\t\tconsole.warn(`[htlkg] Invalid brandId extracted from ${pathname}`);\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=invalid_brand`);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!user || (!user.isAdmin && !user.brandIds.includes(brandId))) {\n\t\t\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t\t\t}\n\t\t\t\t\treturn next();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[htlkg] Error processing brand route for ${pathname}:`,\n\t\t\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t\t\t);\n\t\t\t\t// Fail-safe: deny access on error\n\t\t\t\treturn redirect(`${loginUrl}?error=access_denied`);\n\t\t\t}\n\t\t}\n\n\t\t// Authenticated routes - require any logged-in user\n\t\tif (matchesPattern(pathname, authenticatedRoutes)) {\n\t\t\tif (!user) {\n\t\t\t\tconst returnUrl = encodeURIComponent(pathname + url.search);\n\t\t\t\treturn redirect(`${loginUrl}?redirect=${returnUrl}`);\n\t\t\t}\n\t\t\treturn next();\n\t\t}\n\n\t\t// Default: allow access\n\t\treturn next();\n\t} catch (error) {\n\t\t// Catch-all error handler for route guard\n\t\tconsole.error(\n\t\t\t'[htlkg] Unexpected error in route guard:',\n\t\t\terror instanceof Error ? error.message : 'Unknown error',\n\t\t);\n\t\t// Fail-safe: deny access and redirect to login\n\t\treturn redirect(`${loginUrl}?error=server_error`);\n\t}\n};\n\n/**\n * Helper functions for programmatic route protection in pages\n * These can be used in Astro pages for more fine-grained control\n */\n\n/**\n * Require authentication for a page\n * Redirects to login if user is not authenticated\n */\nexport async function requireAuth(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\tconst currentUrl = context.url.pathname + context.url.search;\n\t\tconst encodedReturnUrl = encodeURIComponent(currentUrl);\n\t\treturn context.redirect(`${loginUrl}?redirect=${encodedReturnUrl}`);\n\t}\n\treturn user;\n}\n\n/**\n * Require admin access for a page\n * Redirects to login if user is not an admin\n */\nexport async function requireAdminAccess(context: any, loginUrl = '/login') {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin) {\n\t\treturn context.redirect(`${loginUrl}?error=admin_required`);\n\t}\n\treturn user;\n}\n\n/**\n * Require brand access for a page\n * Redirects to login if user doesn't have access to the specified brand\n */\nexport async function requireBrandAccess(\n\tcontext: any,\n\tbrandId: number,\n\tloginUrl = '/login'\n) {\n\tconst user = context.locals.user;\n\tif (!user) {\n\t\treturn context.redirect(`${loginUrl}?error=not_authenticated`);\n\t}\n\tif (!user.isAdmin && !user.brandIds.includes(brandId)) {\n\t\treturn context.redirect(`${loginUrl}?error=access_denied`);\n\t}\n\treturn user;\n}\n"],"mappings":"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","names":["htlkgConfig"]}
@@ -0,0 +1,10 @@
1
+ // src/runtime-config.ts
2
+ import {
3
+ htlkgConfig,
4
+ htlkgConfig as htlkgConfig2
5
+ } from "virtual:htlkg";
6
+ export {
7
+ htlkgConfig2 as getHtlkgConfig,
8
+ htlkgConfig
9
+ };
10
+ //# sourceMappingURL=runtime-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/runtime-config.ts"],"sourcesContent":["/**\n * Runtime Configuration for @htlkg/astro\n *\n * @deprecated This module is deprecated. Import from 'virtual:htlkg' instead.\n *\n * @example\n * // OLD (deprecated)\n * import { getHtlkgConfig } from '@htlkg/astro/runtime-config';\n * const config = getHtlkgConfig();\n *\n * // NEW\n * import { htlkgConfig } from 'virtual:htlkg';\n * const { routeGuardConfig, loginPageConfig, amplifyConfig } = htlkgConfig;\n */\n\nexport {\n\thtlkgConfig,\n\thtlkgConfig as getHtlkgConfig,\n} from 'virtual:htlkg';\n"],"mappings":";AAeA;AAAA,EACC;AAAA,EACe,eAAfA;AAAA,OACM;","names":["htlkgConfig"]}
@@ -10,6 +10,16 @@ import {
10
10
  parseListParams,
11
11
  processListData
12
12
  } from "../chunk-2GML443T.js";
13
+ import {
14
+ getClientIP,
15
+ getQueryParams,
16
+ getRequestHeaders,
17
+ getServerData,
18
+ isMobileDevice,
19
+ isServerSide,
20
+ setCacheControl,
21
+ setResponseHeaders
22
+ } from "../chunk-WNMPTDCR.js";
13
23
  import {
14
24
  chunkArray,
15
25
  filterItems,
@@ -28,16 +38,6 @@ import {
28
38
  serializeForHydration,
29
39
  shouldHydrate
30
40
  } from "../chunk-64USRLVP.js";
31
- import {
32
- getClientIP,
33
- getQueryParams,
34
- getRequestHeaders,
35
- getServerData,
36
- isMobileDevice,
37
- isServerSide,
38
- setCacheControl,
39
- setResponseHeaders
40
- } from "../chunk-WNMPTDCR.js";
41
41
  export {
42
42
  applyClientFilters,
43
43
  buildGraphQLFilter,
@@ -1,6 +1,6 @@
1
1
  // src/vue-app-setup.ts
2
- import { amplifyConfig } from "virtual:htlkg-config";
3
2
  import { Amplify } from "aws-amplify";
3
+ import { htlkgConfig } from "virtual:htlkg";
4
4
  function setupVueApp(app) {
5
5
  if (import.meta.env.DEV && typeof window !== "undefined") {
6
6
  try {
@@ -12,6 +12,7 @@ function setupVueApp(app) {
12
12
  }
13
13
  }
14
14
  try {
15
+ const { amplifyConfig } = htlkgConfig;
15
16
  if (!amplifyConfig) {
16
17
  console.warn("[htlkg] No Amplify configuration provided");
17
18
  return;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/vue-app-setup.ts"],"sourcesContent":["/**\n * Vue App Setup for htlkg Integration\n * \n * This file is automatically loaded by the Vue integration when vueAppSetup is enabled.\n * It configures AWS Amplify for client-side authentication in all Vue components.\n * It also sets up nanostores devtools integration for Vue DevTools.\n */\n\n/// <reference types=\"vite/client\" />\n\nimport { amplifyConfig } from \"virtual:htlkg-config\";\nimport type { App } from \"vue\";\nimport type { ResourcesConfig } from \"aws-amplify\";\nimport { Amplify } from \"aws-amplify\";\n\n/**\n * Setup function called by Astro's Vue integration\n * Configures Amplify for client-side authentication and nanostores devtools\n */\nexport default function setupVueApp(app: App): void {\n\t// Setup nanostores devtools in development\n\t// The devtools plugin will automatically detect stores used in components\n\tif (import.meta.env.DEV && typeof window !== 'undefined') {\n\t\ttry {\n\t\t\t// Dynamically import devtools plugin (only in browser)\n\t\t\timport('@nanostores/vue/devtools').then(({ devtools }) => {\n\t\t\t\t// Install devtools plugin - it will detect stores automatically\n\t\t\t\tapp.use(devtools, {});\n\t\t\t}).catch(() => {\n\t\t\t\t// Silently ignore - devtools are optional\n\t\t\t});\n\t\t} catch {\n\t\t\t// Silently ignore - devtools are optional\n\t\t}\n\t}\n\t\n\t// Setup Amplify\n\ttry {\n\t\tif (!amplifyConfig) {\n\t\t\tconsole.warn(\"[htlkg] No Amplify configuration provided\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if this is a full amplify_outputs.json config\n\t\tif (\n\t\t\t\"auth\" in amplifyConfig ||\n\t\t\t\"data\" in amplifyConfig ||\n\t\t\t\"storage\" in amplifyConfig\n\t\t) {\n\t\t\tAmplify.configure(amplifyConfig as ResourcesConfig, { ssr: true });\n\t\t} else {\n\t\t\t// Legacy individual config properties\n\t\t\tconst { userPoolId, userPoolClientId, region } = amplifyConfig as {\n\t\t\t\tuserPoolId?: string;\n\t\t\t\tuserPoolClientId?: string;\n\t\t\t\tregion?: string;\n\t\t\t};\n\n\t\t\tif (userPoolId && userPoolClientId) {\n\t\t\t\tconst config: ResourcesConfig = {\n\t\t\t\t\tAuth: {\n\t\t\t\t\t\tCognito: {\n\t\t\t\t\t\t\tuserPoolId,\n\t\t\t\t\t\t\tuserPoolClientId,\n\t\t\t\t\t\t\t...(region && { region }),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tAmplify.configure(config, { ssr: true });\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[htlkg] Missing required Amplify configuration (userPoolId, userPoolClientId)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"[htlkg] Failed to setup Vue app:\", error);\n\t\t// Don't throw - allow app to continue even if Amplify setup fails\n\t}\n}\n"],"mappings":";AAUA,SAAS,qBAAqB;AAG9B,SAAS,eAAe;AAMT,SAAR,YAA6B,KAAgB;AAGnD,MAAI,YAAY,IAAI,OAAO,OAAO,WAAW,aAAa;AACzD,QAAI;AAEH,aAAO,0BAA0B,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM;AAEzD,YAAI,IAAI,UAAU,CAAC,CAAC;AAAA,MACrB,CAAC,EAAE,MAAM,MAAM;AAAA,MAEf,CAAC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACD;AAGA,MAAI;AACH,QAAI,CAAC,eAAe;AACnB,cAAQ,KAAK,2CAA2C;AACxD;AAAA,IACD;AAGA,QACC,UAAU,iBACV,UAAU,iBACV,aAAa,eACZ;AACD,cAAQ,UAAU,eAAkC,EAAE,KAAK,KAAK,CAAC;AAAA,IAClE,OAAO;AAEN,YAAM,EAAE,YAAY,kBAAkB,OAAO,IAAI;AAMjD,UAAI,cAAc,kBAAkB;AACnC,cAAM,SAA0B;AAAA,UAC/B,MAAM;AAAA,YACL,SAAS;AAAA,cACR;AAAA,cACA;AAAA,cACA,GAAI,UAAU,EAAE,OAAO;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AAEA,gBAAQ,UAAU,QAAQ,EAAE,KAAK,KAAK,CAAC;AAAA,MACxC,OAAO;AACN,gBAAQ;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AACf,YAAQ,MAAM,oCAAoC,KAAK;AAAA,EAExD;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/vue-app-setup.ts"],"sourcesContent":["/**\n * Vue App Setup for htlkg Integration\n *\n * This file is automatically loaded by the Vue integration when vueAppSetup is enabled.\n * It configures AWS Amplify for client-side authentication in all Vue components.\n * It also sets up nanostores devtools integration for Vue DevTools.\n */\n\n/// <reference types=\"vite/client\" />\n\nimport type { App } from \"vue\";\nimport type { ResourcesConfig } from \"aws-amplify\";\nimport { Amplify } from \"aws-amplify\";\nimport { htlkgConfig } from \"virtual:htlkg\";\n\n/**\n * Setup function called by Astro's Vue integration\n * Configures Amplify for client-side authentication and nanostores devtools\n */\nexport default function setupVueApp(app: App): void {\n\t// Setup nanostores devtools in development\n\t// The devtools plugin will automatically detect stores used in components\n\tif (import.meta.env.DEV && typeof window !== 'undefined') {\n\t\ttry {\n\t\t\t// Dynamically import devtools plugin (only in browser)\n\t\t\timport('@nanostores/vue/devtools').then(({ devtools }) => {\n\t\t\t\t// Install devtools plugin - it will detect stores automatically\n\t\t\t\tapp.use(devtools, {});\n\t\t\t}).catch(() => {\n\t\t\t\t// Silently ignore - devtools are optional\n\t\t\t});\n\t\t} catch {\n\t\t\t// Silently ignore - devtools are optional\n\t\t}\n\t}\n\n\t// Setup Amplify\n\ttry {\n\t\tconst { amplifyConfig } = htlkgConfig;\n\t\tif (!amplifyConfig) {\n\t\t\tconsole.warn(\"[htlkg] No Amplify configuration provided\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if this is a full amplify_outputs.json config\n\t\tif (\n\t\t\t\"auth\" in amplifyConfig ||\n\t\t\t\"data\" in amplifyConfig ||\n\t\t\t\"storage\" in amplifyConfig\n\t\t) {\n\t\t\tAmplify.configure(amplifyConfig as ResourcesConfig, { ssr: true });\n\t\t} else {\n\t\t\t// Legacy individual config properties\n\t\t\tconst { userPoolId, userPoolClientId, region } = amplifyConfig as {\n\t\t\t\tuserPoolId?: string;\n\t\t\t\tuserPoolClientId?: string;\n\t\t\t\tregion?: string;\n\t\t\t};\n\n\t\t\tif (userPoolId && userPoolClientId) {\n\t\t\t\tconst config: ResourcesConfig = {\n\t\t\t\t\tAuth: {\n\t\t\t\t\t\tCognito: {\n\t\t\t\t\t\t\tuserPoolId,\n\t\t\t\t\t\t\tuserPoolClientId,\n\t\t\t\t\t\t\t...(region && { region }),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t};\n\n\t\t\t\tAmplify.configure(config, { ssr: true });\n\t\t\t} else {\n\t\t\t\tconsole.error(\n\t\t\t\t\t\"[htlkg] Missing required Amplify configuration (userPoolId, userPoolClientId)\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"[htlkg] Failed to setup Vue app:\", error);\n\t\t// Don't throw - allow app to continue even if Amplify setup fails\n\t}\n}\n"],"mappings":";AAYA,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAMb,SAAR,YAA6B,KAAgB;AAGnD,MAAI,YAAY,IAAI,OAAO,OAAO,WAAW,aAAa;AACzD,QAAI;AAEH,aAAO,0BAA0B,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM;AAEzD,YAAI,IAAI,UAAU,CAAC,CAAC;AAAA,MACrB,CAAC,EAAE,MAAM,MAAM;AAAA,MAEf,CAAC;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACD;AAGA,MAAI;AACH,UAAM,EAAE,cAAc,IAAI;AAC1B,QAAI,CAAC,eAAe;AACnB,cAAQ,KAAK,2CAA2C;AACxD;AAAA,IACD;AAGA,QACC,UAAU,iBACV,UAAU,iBACV,aAAa,eACZ;AACD,cAAQ,UAAU,eAAkC,EAAE,KAAK,KAAK,CAAC;AAAA,IAClE,OAAO;AAEN,YAAM,EAAE,YAAY,kBAAkB,OAAO,IAAI;AAMjD,UAAI,cAAc,kBAAkB;AACnC,cAAM,SAA0B;AAAA,UAC/B,MAAM;AAAA,YACL,SAAS;AAAA,cACR;AAAA,cACA;AAAA,cACA,GAAI,UAAU,EAAE,OAAO;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AAEA,gBAAQ,UAAU,QAAQ,EAAE,KAAK,KAAK,CAAC;AAAA,MACxC,OAAO;AACN,gBAAQ;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD,SAAS,OAAO;AACf,YAAQ,MAAM,oCAAoC,KAAK;AAAA,EAExD;AACD;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@htlkg/astro",
3
- "version": "0.0.13",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -23,6 +23,10 @@
23
23
  "import": "./dist/vue-app-setup.js",
24
24
  "types": "./src/vue-app-setup.ts"
25
25
  },
26
+ "./runtime-config": {
27
+ "import": "./dist/runtime-config.js",
28
+ "types": "./src/runtime-config.ts"
29
+ },
26
30
  "./auth/LoginPage.astro": "./src/auth/LoginPage.astro",
27
31
  "./layouts": "./src/layouts/index.ts",
28
32
  "./layouts/*.astro": "./src/layouts/*.astro",
@@ -54,52 +58,43 @@
54
58
  "dist",
55
59
  "src"
56
60
  ],
57
- "scripts": {
58
- "build": "tsup",
59
- "dev": "tsup --watch",
60
- "test": "vitest run",
61
- "test:watch": "vitest",
62
- "prepublishOnly": "pnpm build",
63
- "version:patch": "pnpm version patch --no-git-tag-version",
64
- "version:minor": "pnpm version minor --no-git-tag-version",
65
- "version:major": "pnpm version major --no-git-tag-version"
66
- },
67
61
  "dependencies": {
68
- "@htlkg/core": "^0.0.9",
69
- "@htlkg/components": "^0.0.9",
70
- "@hotelinking/ui": "^16.49.16"
71
- },
72
- "peerDependencies": {
73
- "@astrojs/tailwind": "^6.0.0",
62
+ "@hotelinking/ui": "^16.49.16",
63
+ "@astrojs/tailwind": "^6.0.2",
74
64
  "@astrojs/vue": "^5.0.0",
75
65
  "@inox-tools/request-nanostores": "^0.6.0",
76
66
  "@nanostores/logger": "^1.0.0",
77
- "@nanostores/vue": "^0.10.0 || ^1.0.0",
78
- "@vue/devtools-api": "^6.0.0 || ^7.0.0 || ^8.0.0",
67
+ "@nanostores/vue": "^1.0.1",
68
+ "@vue/devtools-api": "^8.0.5",
69
+ "nanostores": "^0.11.3",
70
+ "tailwindcss": "^3.4.18",
71
+ "vue": "^3.5.22",
72
+ "vite-plugin-vue-devtools": "^7.6.10",
73
+ "@htlkg/components": "0.0.11",
74
+ "@htlkg/core": "0.0.10"
75
+ },
76
+ "peerDependencies": {
79
77
  "astro": "^5.0.0",
80
- "aws-amplify": "^6.0.0",
81
- "nanostores": "^0.11.0 || ^1.0.0",
82
- "tailwindcss": "^3.0.0",
83
- "vue": "^3.5.0"
78
+ "aws-amplify": "^6.0.0"
84
79
  },
85
80
  "devDependencies": {
86
- "@astrojs/tailwind": "^6.0.2",
87
- "@astrojs/vue": "^5.0.0",
88
- "@inox-tools/request-nanostores": "^0.6.0",
89
- "@nanostores/logger": "^1.0.0",
90
- "@nanostores/vue": "^0.10.0",
91
- "@vue/devtools-api": "^6.6.4",
92
81
  "astro": "^5.14.7",
93
82
  "aws-amplify": "^6.11.3",
94
- "nanostores": "^0.11.3",
95
- "tailwindcss": "^3.4.18",
96
83
  "tsup": "^8.0.0",
97
84
  "typescript": "^5.9.2",
98
- "vite-plugin-vue-devtools": "^7.6.10",
99
- "vitest": "^3.2.4",
100
- "vue": "^3.5.22"
85
+ "vite": "^6.0.0",
86
+ "vitest": "^3.2.4"
101
87
  },
102
88
  "publishConfig": {
103
89
  "access": "public"
90
+ },
91
+ "scripts": {
92
+ "build": "tsup",
93
+ "dev": "tsup --watch",
94
+ "test": "vitest run",
95
+ "test:watch": "vitest",
96
+ "version:patch": "pnpm version patch --no-git-tag-version",
97
+ "version:minor": "pnpm version minor --no-git-tag-version",
98
+ "version:major": "pnpm version major --no-git-tag-version"
104
99
  }
105
- }
100
+ }
@@ -16,18 +16,14 @@
16
16
 
17
17
  import DefaultLayout from "../layouts/DefaultLayout.astro";
18
18
  import LoginForm from "./LoginForm.vue";
19
+ import { htlkgConfig } from "virtual:htlkg";
19
20
 
20
21
  export const prerender = false;
21
22
 
22
- // Lazy load configuration to avoid build-time evaluation
23
- let loginPageConfig;
24
- try {
25
- const config = await import("virtual:htlkg-config");
26
- loginPageConfig = config.loginPageConfig;
27
- } catch (e) {
28
- // During build analysis, virtual modules may not be available
29
- loginPageConfig = null;
30
- }
23
+ // Get login page configuration from virtual module
24
+ const { loginPageConfig } = htlkgConfig;
25
+
26
+ console.log(loginPageConfig);
31
27
 
32
28
  const {
33
29
  logo = "https://images.hotelinking.com/ui/WiFiBot.svg",
@@ -32,32 +32,31 @@ export default defineConfig({
32
32
  - Route guard configuration
33
33
  - Login page injection
34
34
  - Brand route handling
35
- - Virtual module configuration
35
+ - Runtime configuration
36
36
 
37
37
  ## Options
38
38
 
39
39
  ```typescript
40
40
  interface HtlkgIntegrationOptions {
41
41
  auth?: {
42
- enabled: boolean;
43
- loginPage?: string;
44
42
  publicRoutes?: RoutePattern[];
45
- protectedRoutes?: RoutePattern[];
46
- };
47
- brandRoutes?: {
48
- enabled: boolean;
49
- pattern?: string;
43
+ authenticatedRoutes?: RoutePattern[];
44
+ adminRoutes?: RoutePattern[];
45
+ brandRoutes?: BrandRouteConfig[];
46
+ loginUrl?: string;
50
47
  };
48
+ loginPage?: {
49
+ path?: string;
50
+ logo?: string;
51
+ title?: string;
52
+ redirectUrl?: string;
53
+ } | false;
54
+ amplify?: Record<string, unknown>;
55
+ vueAppSetup?: 'auto' | 'basic' | 'full';
51
56
  }
52
57
  ```
53
58
 
54
- ## Virtual Modules
55
-
56
- The integration provides virtual modules for configuration:
59
+ ## Configuration
57
60
 
58
- ```typescript
59
- // Access config in your code
60
- import config from 'virtual:htlkg-config';
61
-
62
- console.log(config.auth.loginPage);
63
- ```
61
+ Configuration is passed to the integration and made available to middleware
62
+ and pages at runtime via the internal `runtime-config` module.
@@ -1,20 +1,106 @@
1
1
  /**
2
2
  * htlkg Astro Integration
3
- *
3
+ *
4
4
  * Supports static, hybrid, and full SSR output modes.
5
+ * Uses a virtual Vite module (virtual:htlkg) to provide configuration
6
+ * to middleware and Vue app setup.
5
7
  */
6
8
 
7
9
  import tailwind from '@astrojs/tailwind';
8
10
  import vue from '@astrojs/vue';
11
+ import inoxToolsRequestNanostores from '@inox-tools/request-nanostores';
9
12
  import type { AstroIntegration } from 'astro';
10
- import type { HtlkgIntegrationOptions } from './config.js';
11
- import { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';
13
+ import type { Plugin } from 'vite';
14
+ import type { HtlkgIntegrationOptions, RouteGuardConfig, LoginPageConfig } from './config.js';
12
15
 
13
16
  const DEFAULT_ENV_VARS = [
14
17
  'PUBLIC_COGNITO_USER_POOL_ID',
15
18
  'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'
16
19
  ];
17
20
 
21
+ const VIRTUAL_MODULE_ID = 'virtual:htlkg';
22
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
23
+
24
+ /**
25
+ * Create Vite plugin that provides the virtual:htlkg module
26
+ */
27
+ function createVirtualConfigPlugin(config: {
28
+ routeGuard: RouteGuardConfig;
29
+ loginPage: LoginPageConfig | null;
30
+ amplify: Record<string, unknown> | null;
31
+ }): Plugin {
32
+ return {
33
+ name: 'htlkg-virtual-config',
34
+ resolveId(id) {
35
+ if (id === VIRTUAL_MODULE_ID) {
36
+ return RESOLVED_VIRTUAL_MODULE_ID;
37
+ }
38
+ },
39
+ load(id) {
40
+ if (id === RESOLVED_VIRTUAL_MODULE_ID) {
41
+ // Serialize the config to be loaded as a module
42
+ // We need to handle RegExp serialization for route patterns
43
+ const serializedConfig = serializeConfig(config);
44
+ return `export const htlkgConfig = ${serializedConfig};`;
45
+ }
46
+ },
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Serialize config object to JavaScript code
52
+ * Handles RegExp patterns which can't be JSON.stringify'd
53
+ */
54
+ function serializeConfig(config: {
55
+ routeGuard: RouteGuardConfig;
56
+ loginPage: LoginPageConfig | null;
57
+ amplify: Record<string, unknown> | null;
58
+ }): string {
59
+ const routeGuard = config.routeGuard || {};
60
+
61
+ // Build route guard config with proper RegExp handling
62
+ const routeGuardParts: string[] = [];
63
+
64
+ if (routeGuard.publicRoutes) {
65
+ routeGuardParts.push(`publicRoutes: ${serializePatterns(routeGuard.publicRoutes)}`);
66
+ }
67
+ if (routeGuard.authenticatedRoutes) {
68
+ routeGuardParts.push(`authenticatedRoutes: ${serializePatterns(routeGuard.authenticatedRoutes)}`);
69
+ }
70
+ if (routeGuard.adminRoutes) {
71
+ routeGuardParts.push(`adminRoutes: ${serializePatterns(routeGuard.adminRoutes)}`);
72
+ }
73
+ if (routeGuard.brandRoutes && routeGuard.brandRoutes.length > 0) {
74
+ const brandRoutesStr = routeGuard.brandRoutes.map(br =>
75
+ `{ pattern: ${br.pattern.toString()}, brandIdParam: ${br.brandIdParam} }`
76
+ ).join(', ');
77
+ routeGuardParts.push(`brandRoutes: [${brandRoutesStr}]`);
78
+ }
79
+ if (routeGuard.loginUrl) {
80
+ routeGuardParts.push(`loginUrl: ${JSON.stringify(routeGuard.loginUrl)}`);
81
+ }
82
+
83
+ return `{
84
+ routeGuardConfig: { ${routeGuardParts.join(', ')} },
85
+ loginPageConfig: ${JSON.stringify(config.loginPage)},
86
+ amplifyConfig: ${JSON.stringify(config.amplify)}
87
+ }`;
88
+ }
89
+
90
+ /**
91
+ * Serialize an array of route patterns (strings or RegExp)
92
+ */
93
+ function serializePatterns(patterns: (string | RegExp)[]): string {
94
+ const serialized = patterns.map(p => {
95
+ if (typeof p === 'string') {
96
+ return JSON.stringify(p);
97
+ }
98
+ // RegExp - output as literal
99
+ return p.toString();
100
+ });
101
+ return `[${serialized.join(', ')}]`;
102
+ }
103
+
18
104
  export function htlkg(
19
105
  options: HtlkgIntegrationOptions = {},
20
106
  ): AstroIntegration | AstroIntegration[] {
@@ -52,14 +138,24 @@ export function htlkg(
52
138
  integrations.push(vue());
53
139
  }
54
140
 
141
+ // Add request nanostores integration (for SSR-safe nanostores)
142
+ integrations.push(inoxToolsRequestNanostores());
143
+
144
+ // Prepare configuration for virtual module
145
+ const htlkgVirtualConfig = {
146
+ routeGuard: auth,
147
+ loginPage: loginPage || null,
148
+ amplify: amplify || null,
149
+ };
150
+
55
151
  // Add the main htlkg integration
56
152
  integrations.push({
57
153
  name: '@htlkg/astro',
58
154
  hooks: {
59
155
  'astro:config:setup': async ({ updateConfig, addMiddleware, injectRoute, logger }) => {
60
156
  try {
61
- logger.info(useFullVueSetup
62
- ? 'Vue configured with Amplify app setup'
157
+ logger.info(useFullVueSetup
158
+ ? 'Vue configured with Amplify app setup'
63
159
  : 'Vue configured (basic mode)');
64
160
 
65
161
  if (amplify) {
@@ -74,11 +170,12 @@ export function htlkg(
74
170
  }
75
171
  }
76
172
 
77
- // Create Vite virtual module plugin
78
- const virtualModulePlugin = createVirtualModulePlugin(auth, loginPage, amplify || null);
79
- const vitePlugins: any[] = [virtualModulePlugin];
173
+ // Create virtual module plugin for config
174
+ const vitePlugins: Plugin[] = [
175
+ createVirtualConfigPlugin(htlkgVirtualConfig),
176
+ ];
80
177
 
81
- // Only load Vue DevTools during development (dynamic import to avoid bundling in production)
178
+ // Only load Vue DevTools during development
82
179
  const isDev = process.env.NODE_ENV !== 'production';
83
180
  if (isDev) {
84
181
  try {
@@ -90,7 +187,14 @@ export function htlkg(
90
187
  }
91
188
  }
92
189
 
93
- updateConfig({ vite: { plugins: vitePlugins } });
190
+ // Update Vite config with plugins
191
+ updateConfig({
192
+ vite: {
193
+ plugins: vitePlugins
194
+ }
195
+ });
196
+
197
+ logger.info('virtual:htlkg module configured');
94
198
 
95
199
  // Inject middleware (only for full setup)
96
200
  if (useFullVueSetup) {
@@ -121,6 +225,15 @@ export function htlkg(
121
225
  filename: 'htlkg.d.ts',
122
226
  content: `
123
227
  import type { AuthUser } from '@htlkg/core/types';
228
+ import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';
229
+
230
+ declare module 'virtual:htlkg' {
231
+ export const htlkgConfig: {
232
+ routeGuardConfig: RouteGuardConfig | null;
233
+ loginPageConfig: LoginPageConfig | null;
234
+ amplifyConfig: Record<string, unknown> | null;
235
+ };
236
+ }
124
237
 
125
238
  declare global {
126
239
  namespace App {
@@ -130,8 +243,6 @@ declare global {
130
243
  }
131
244
  }
132
245
 
133
- ${virtualModuleTypes}
134
-
135
246
  export {};
136
247
  `,
137
248
  });
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Authentication middleware for htlkg integration
3
- *
3
+ *
4
4
  * This middleware:
5
5
  * - Configures AWS Amplify for server-side auth
6
6
  * - Retrieves the authenticated user from AWS Amplify
@@ -12,7 +12,7 @@ import type { MiddlewareHandler } from 'astro';
12
12
  import { Amplify } from 'aws-amplify';
13
13
  import { getUser } from '@htlkg/core/auth';
14
14
  import { globalSettings } from '@htlkg/core/amplify-astro-adapter';
15
- import { amplifyConfig } from 'virtual:htlkg-config';
15
+ import { htlkgConfig } from 'virtual:htlkg';
16
16
 
17
17
  // Track if Amplify has been configured
18
18
  let amplifyConfigured = false;
@@ -24,6 +24,7 @@ function ensureAmplifyConfigured(): void {
24
24
  if (amplifyConfigured) return;
25
25
 
26
26
  try {
27
+ const { amplifyConfig } = htlkgConfig;
27
28
  if (!amplifyConfig) {
28
29
  console.warn('[htlkg Auth] No Amplify configuration provided');
29
30
  return;
@@ -1,18 +1,12 @@
1
1
  /**
2
2
  * Unit tests for route guard helper functions
3
3
  *
4
- * Note: The routeGuard middleware itself cannot be tested in isolation because it
5
- * depends on the virtual:htlkg-config module which is generated at build time by Vite.
6
- * These tests focus on the exported helper functions that don't depend on the virtual module.
4
+ * These tests focus on the exported helper functions logic.
5
+ * The tests recreate the function logic to ensure isolation.
7
6
  */
8
7
 
9
8
  import { describe, it, expect, vi } from 'vitest';
10
9
 
11
- // Since the module imports from 'virtual:htlkg-config' at the top level,
12
- // we need to test the helper functions by recreating their logic here.
13
- // The actual helper functions (requireAuth, requireAdminAccess, requireBrandAccess)
14
- // have their implementation logic tested below.
15
-
16
10
  describe('Route Guard Helper Functions Logic', () => {
17
11
  /**
18
12
  * requireAuth implementation logic test
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Route guard middleware for htlkg integration
3
- *
3
+ *
4
4
  * This middleware enforces access control based on route configuration:
5
5
  * - Public routes: accessible to everyone
6
6
  * - Authenticated routes: require any logged-in user
@@ -9,13 +9,8 @@
9
9
  */
10
10
 
11
11
  import type { MiddlewareHandler } from 'astro';
12
- import type { RoutePattern, RouteGuardConfig } from '../htlkg/config.js';
13
-
14
- // Import configuration from virtual module
15
- import { routeGuardConfig } from 'virtual:htlkg-config';
16
-
17
- // Type assertion for the imported config
18
- const config = routeGuardConfig as RouteGuardConfig;
12
+ import type { RoutePattern } from '../htlkg/config.js';
13
+ import { htlkgConfig } from 'virtual:htlkg';
19
14
 
20
15
  /**
21
16
  * Helper: Check if pathname matches any of the provided patterns
@@ -67,13 +62,14 @@ export const routeGuard: MiddlewareHandler = async (context, next) => {
67
62
  const { locals, url, redirect } = context;
68
63
  const pathname = url.pathname;
69
64
 
65
+ const { routeGuardConfig } = htlkgConfig;
70
66
  const {
71
67
  publicRoutes = [],
72
68
  authenticatedRoutes = [],
73
69
  adminRoutes = [],
74
70
  brandRoutes = [],
75
71
  loginUrl = '/login',
76
- } = config;
72
+ } = routeGuardConfig || {};
77
73
 
78
74
  try {
79
75
  // Public routes - no auth required
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Runtime Configuration for @htlkg/astro
3
+ *
4
+ * @deprecated This module is deprecated. Import from 'virtual:htlkg' instead.
5
+ *
6
+ * @example
7
+ * // OLD (deprecated)
8
+ * import { getHtlkgConfig } from '@htlkg/astro/runtime-config';
9
+ * const config = getHtlkgConfig();
10
+ *
11
+ * // NEW
12
+ * import { htlkgConfig } from 'virtual:htlkg';
13
+ * const { routeGuardConfig, loginPageConfig, amplifyConfig } = htlkgConfig;
14
+ */
15
+
16
+ export {
17
+ htlkgConfig,
18
+ htlkgConfig as getHtlkgConfig,
19
+ } from 'virtual:htlkg';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Vue App Setup for htlkg Integration
3
- *
3
+ *
4
4
  * This file is automatically loaded by the Vue integration when vueAppSetup is enabled.
5
5
  * It configures AWS Amplify for client-side authentication in all Vue components.
6
6
  * It also sets up nanostores devtools integration for Vue DevTools.
@@ -8,10 +8,10 @@
8
8
 
9
9
  /// <reference types="vite/client" />
10
10
 
11
- import { amplifyConfig } from "virtual:htlkg-config";
12
11
  import type { App } from "vue";
13
12
  import type { ResourcesConfig } from "aws-amplify";
14
13
  import { Amplify } from "aws-amplify";
14
+ import { htlkgConfig } from "virtual:htlkg";
15
15
 
16
16
  /**
17
17
  * Setup function called by Astro's Vue integration
@@ -33,9 +33,10 @@ export default function setupVueApp(app: App): void {
33
33
  // Silently ignore - devtools are optional
34
34
  }
35
35
  }
36
-
36
+
37
37
  // Setup Amplify
38
38
  try {
39
+ const { amplifyConfig } = htlkgConfig;
39
40
  if (!amplifyConfig) {
40
41
  console.warn("[htlkg] No Amplify configuration provided");
41
42
  return;
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/htlkg/index.ts","../src/htlkg/virtual-modules.ts"],"sourcesContent":["/**\n * htlkg Astro Integration\n * \n * Supports static, hybrid, and full SSR output modes.\n */\n\nimport tailwind from '@astrojs/tailwind';\nimport vue from '@astrojs/vue';\nimport type { AstroIntegration } from 'astro';\nimport type { HtlkgIntegrationOptions } from './config.js';\nimport { createVirtualModulePlugin, virtualModuleTypes } from './virtual-modules.js';\n\nconst DEFAULT_ENV_VARS = [\n\t'PUBLIC_COGNITO_USER_POOL_ID',\n\t'PUBLIC_COGNITO_USER_POOL_CLIENT_ID'\n];\n\nexport function htlkg(\n\toptions: HtlkgIntegrationOptions = {},\n): AstroIntegration | AstroIntegration[] {\n\tconst {\n\t\tauth = {},\n\t\tloginPage = { path: '/login', title: 'Sign In', redirectUrl: '/admin' },\n\t\tvalidateEnv = true,\n\t\trequiredEnvVars = DEFAULT_ENV_VARS,\n\t\ttailwind: tailwindOptions,\n\t\tamplify,\n\t\tvueAppSetup = 'auto',\n\t} = options;\n\n\tconst integrations: AstroIntegration[] = [];\n\n\t// Add Tailwind integration (enabled by default)\n\tif (tailwindOptions !== false) {\n\t\tconst tailwindConfig =\n\t\t\ttypeof tailwindOptions === 'object' ? tailwindOptions : undefined;\n\t\tintegrations.push(\n\t\t\ttailwind(tailwindConfig as Parameters<typeof tailwind>[0]),\n\t\t);\n\t}\n\n\t// Determine Vue setup mode:\n\t// - 'full': Use Amplify app entrypoint (requires SSR)\n\t// - 'basic': Basic Vue without app entrypoint (works with static)\n\t// - 'auto': Default to 'basic' for compatibility with static builds\n\tconst useFullVueSetup = vueAppSetup === 'full';\n\n\t// Add Vue integration\n\tif (useFullVueSetup) {\n\t\tintegrations.push(vue({ appEntrypoint: '@htlkg/astro/vue-app-setup' }));\n\t} else {\n\t\tintegrations.push(vue());\n\t}\n\n\t// Add the main htlkg integration\n\tintegrations.push({\n\t\tname: '@htlkg/astro',\n\t\thooks: {\n\t\t\t'astro:config:setup': async ({ updateConfig, addMiddleware, injectRoute, logger }) => {\n\t\t\t\ttry {\n\t\t\t\t\tlogger.info(useFullVueSetup \n\t\t\t\t\t\t? 'Vue configured with Amplify app setup' \n\t\t\t\t\t\t: 'Vue configured (basic mode)');\n\n\t\t\t\t\tif (amplify) {\n\t\t\t\t\t\tlogger.info('Amplify configuration provided');\n\t\t\t\t\t}\n\n\t\t\t\t\t// Validate env vars (only for full setup)\n\t\t\t\t\tif (validateEnv && !amplify && useFullVueSetup) {\n\t\t\t\t\t\tconst missing = requiredEnvVars.filter(v => !process.env[v]);\n\t\t\t\t\t\tif (missing.length > 0) {\n\t\t\t\t\t\t\tlogger.warn(`Missing env vars: ${missing.join(', ')}`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Create Vite virtual module plugin\n\t\t\t\t\tconst virtualModulePlugin = createVirtualModulePlugin(auth, loginPage, amplify || null);\n\t\t\t\t\tconst vitePlugins: any[] = [virtualModulePlugin];\n\n\t\t\t\t\t// Only load Vue DevTools during development (dynamic import to avoid bundling in production)\n\t\t\t\t\tconst isDev = process.env.NODE_ENV !== 'production';\n\t\t\t\t\tif (isDev) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst vueDevTools = (await import('vite-plugin-vue-devtools')).default;\n\t\t\t\t\t\t\tvitePlugins.push(vueDevTools());\n\t\t\t\t\t\t\tlogger.info('Vue DevTools enabled');\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// DevTools not available, skip silently\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tupdateConfig({ vite: { plugins: vitePlugins } });\n\n\t\t\t\t\t// Inject middleware (only for full setup)\n\t\t\t\t\tif (useFullVueSetup) {\n\t\t\t\t\t\taddMiddleware({ entrypoint: '@htlkg/astro/middleware', order: 'pre' });\n\t\t\t\t\t\tlogger.info('Authentication middleware injected');\n\t\t\t\t\t}\n\n\t\t\t\t\t// Inject login page (only for full setup)\n\t\t\t\t\tif (loginPage !== false && useFullVueSetup) {\n\t\t\t\t\t\tconst loginPath = loginPage.path || '/login';\n\t\t\t\t\t\tinjectRoute({\n\t\t\t\t\t\t\tpattern: loginPath,\n\t\t\t\t\t\t\tentrypoint: '@htlkg/astro/auth/LoginPage.astro',\n\t\t\t\t\t\t\tprerender: false,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlogger.info(`Login page injected at ${loginPath}`);\n\t\t\t\t\t}\n\n\t\t\t\t\tlogger.info('htlkg integration configured');\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst msg = error instanceof Error ? error.message : 'Unknown error';\n\t\t\t\t\tlogger.error(`htlkg configuration failed: ${msg}`);\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t},\n\t\t\t'astro:config:done': ({ injectTypes }) => {\n\t\t\t\tinjectTypes({\n\t\t\t\t\tfilename: 'htlkg.d.ts',\n\t\t\t\t\tcontent: `\nimport type { AuthUser } from '@htlkg/core/types';\n\ndeclare global {\n namespace App {\n interface Locals {\n user: AuthUser | null;\n }\n }\n}\n\n${virtualModuleTypes}\n\nexport {};\n`,\n\t\t\t\t});\n\t\t\t},\n\t\t},\n\t});\n\n\treturn integrations;\n}\n","/**\n * Virtual module setup for htlkg integration\n * Creates Vite virtual modules to pass configuration to middleware and pages\n */\n\nimport type { RouteGuardConfig, LoginPageConfig, RoutePattern } from './config.js';\n\n/**\n * Serialize a route pattern (RegExp or string) to JavaScript code\n */\nfunction serializePattern(pattern: RegExp | string): string {\n\tif (pattern instanceof RegExp) {\n\t\treturn `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;\n\t}\n\treturn JSON.stringify(pattern);\n}\n\n/**\n * Serialize an array of route patterns to JavaScript code\n */\nfunction serializePatterns(patterns: RoutePattern[]): string {\n\tif (!patterns || patterns.length === 0) return '[]';\n\treturn `[${patterns.map(serializePattern).join(', ')}]`;\n}\n\n/**\n * Create the virtual module plugin for Vite\n * This plugin provides configuration to middleware and pages at runtime\n */\nexport function createVirtualModulePlugin(\n\tauthConfig: RouteGuardConfig,\n\tloginPageConfig: LoginPageConfig | false | null,\n\tamplifyConfig: Record<string, unknown> | null\n) {\n\tconst virtualModuleId = 'virtual:htlkg-config';\n\tconst resolvedVirtualModuleId = '\\0' + virtualModuleId;\n\n\treturn {\n\t\tname: 'htlkg-config',\n\t\tenforce: 'pre' as const,\n\t\tresolveId(id: string) {\n\t\t\tif (id === virtualModuleId) {\n\t\t\t\treturn resolvedVirtualModuleId;\n\t\t\t}\n\t\t},\n\t\tload(id: string) {\n\t\t\tif (id === resolvedVirtualModuleId) {\n\t\t\t\t// Serialize auth configuration with RegExp support\n\t\t\t\tconst serializedAuthConfig = `{\n\t\t\t\t\tpublicRoutes: ${serializePatterns(authConfig.publicRoutes || [])},\n\t\t\t\t\tauthenticatedRoutes: ${serializePatterns(authConfig.authenticatedRoutes || [])},\n\t\t\t\t\tadminRoutes: ${serializePatterns(authConfig.adminRoutes || [])},\n\t\t\t\t\tbrandRoutes: ${JSON.stringify(authConfig.brandRoutes || [])},\n\t\t\t\t\tloginUrl: ${JSON.stringify(authConfig.loginUrl || '/login')}\n\t\t\t\t}`;\n\n\t\t\t\tconst serializedAmplifyConfig = amplifyConfig\n\t\t\t\t\t? JSON.stringify(amplifyConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\tconst serializedLoginPageConfig = loginPageConfig !== false && loginPageConfig !== null\n\t\t\t\t\t? JSON.stringify(loginPageConfig)\n\t\t\t\t\t: 'null';\n\n\t\t\t\treturn `export const routeGuardConfig = ${serializedAuthConfig};\nexport const loginPageConfig = ${serializedLoginPageConfig};\nexport const amplifyConfig = ${serializedAmplifyConfig};`;\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Type definitions for the virtual module\n * This should be injected into the project's type definitions\n */\nexport const virtualModuleTypes = `\ndeclare module 'virtual:htlkg-config' {\n import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';\n \n export const routeGuardConfig: RouteGuardConfig;\n export const loginPageConfig: LoginPageConfig | null;\n export const amplifyConfig: Record<string, unknown> | null;\n}\n`;\n"],"mappings":"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","names":[]}
@@ -1,14 +0,0 @@
1
- /**
2
- * Mock for virtual:htlkg-config module used in tests
3
- */
4
-
5
- export const routeGuardConfig = {
6
- publicRoutes: [],
7
- authenticatedRoutes: [],
8
- adminRoutes: [],
9
- brandRoutes: [],
10
- loginUrl: '/login',
11
- };
12
-
13
- export const loginPageConfig = null;
14
- export const amplifyConfig = null;
@@ -1,158 +0,0 @@
1
- /**
2
- * Unit tests for virtual module setup
3
- */
4
-
5
- import { describe, it, expect } from 'vitest';
6
- import { createVirtualModulePlugin } from './virtual-modules.js';
7
-
8
- describe('Virtual Module Plugin', () => {
9
- it('should create a valid Vite plugin', () => {
10
- const authConfig = {
11
- publicRoutes: ['/login', '/'],
12
- authenticatedRoutes: ['/dashboard'],
13
- adminRoutes: [/^\/admin/],
14
- brandRoutes: [],
15
- loginUrl: '/login',
16
- };
17
-
18
- const plugin = createVirtualModulePlugin(authConfig, null, null);
19
-
20
- expect(plugin).toBeDefined();
21
- expect(plugin.name).toBe('htlkg-config');
22
- expect(typeof plugin.resolveId).toBe('function');
23
- expect(typeof plugin.load).toBe('function');
24
- });
25
-
26
- it('should resolve virtual module ID', () => {
27
- const plugin = createVirtualModulePlugin({}, null, null);
28
-
29
- const resolved = plugin.resolveId('virtual:htlkg-config');
30
- expect(resolved).toBe('\0virtual:htlkg-config');
31
- });
32
-
33
- it('should not resolve non-virtual module IDs', () => {
34
- const plugin = createVirtualModulePlugin({}, null, null);
35
-
36
- const resolved = plugin.resolveId('some-other-module');
37
- expect(resolved).toBeUndefined();
38
- });
39
-
40
- it('should generate module code with string patterns', () => {
41
- const authConfig = {
42
- publicRoutes: ['/login', '/register'],
43
- authenticatedRoutes: ['/dashboard'],
44
- adminRoutes: [],
45
- brandRoutes: [],
46
- loginUrl: '/login',
47
- };
48
-
49
- const plugin = createVirtualModulePlugin(authConfig, null, null);
50
- const code = plugin.load('\0virtual:htlkg-config');
51
-
52
- expect(code).toContain('publicRoutes: ["/login", "/register"]');
53
- expect(code).toContain('authenticatedRoutes: ["/dashboard"]');
54
- expect(code).toContain('loginUrl: "/login"');
55
- });
56
-
57
- it('should generate module code with RegExp patterns', () => {
58
- const authConfig = {
59
- publicRoutes: [],
60
- authenticatedRoutes: [],
61
- adminRoutes: [/^\/admin/],
62
- brandRoutes: [],
63
- loginUrl: '/login',
64
- };
65
-
66
- const plugin = createVirtualModulePlugin(authConfig, null, null);
67
- const code = plugin.load('\0virtual:htlkg-config');
68
-
69
- expect(code).toContain('new RegExp');
70
- // The pattern is serialized as a string, so we check for the pattern source
71
- expect(code).toContain('adminRoutes: [new RegExp(');
72
- });
73
-
74
- it('should generate module code with mixed patterns', () => {
75
- const authConfig = {
76
- publicRoutes: ['/login', /^\/public/],
77
- authenticatedRoutes: ['/dashboard', /^\/app/],
78
- adminRoutes: [],
79
- brandRoutes: [],
80
- loginUrl: '/login',
81
- };
82
-
83
- const plugin = createVirtualModulePlugin(authConfig, null, null);
84
- const code = plugin.load('\0virtual:htlkg-config');
85
-
86
- expect(code).toContain('"/login"');
87
- expect(code).toContain('new RegExp');
88
- expect(code).toContain('"/dashboard"');
89
- });
90
-
91
- it('should handle empty route arrays', () => {
92
- const authConfig = {
93
- publicRoutes: [],
94
- authenticatedRoutes: [],
95
- adminRoutes: [],
96
- brandRoutes: [],
97
- loginUrl: '/login',
98
- };
99
-
100
- const plugin = createVirtualModulePlugin(authConfig, null, null);
101
- const code = plugin.load('\0virtual:htlkg-config');
102
-
103
- expect(code).toContain('publicRoutes: []');
104
- expect(code).toContain('authenticatedRoutes: []');
105
- expect(code).toContain('adminRoutes: []');
106
- });
107
-
108
- it('should serialize amplify config when provided', () => {
109
- const amplifyConfig = {
110
- userPoolId: 'us-east-1_abc123',
111
- userPoolClientId: 'abc123def456',
112
- region: 'us-east-1',
113
- };
114
-
115
- const plugin = createVirtualModulePlugin({}, null, amplifyConfig);
116
- const code = plugin.load('\0virtual:htlkg-config');
117
-
118
- expect(code).toContain('amplifyConfig');
119
- expect(code).toContain('userPoolId');
120
- expect(code).toContain('us-east-1_abc123');
121
- });
122
-
123
- it('should set amplify config to null when not provided', () => {
124
- const plugin = createVirtualModulePlugin({}, null, null);
125
- const code = plugin.load('\0virtual:htlkg-config');
126
-
127
- expect(code).toContain('amplifyConfig = null');
128
- });
129
-
130
- it('should serialize login page config when provided', () => {
131
- const loginPageConfig = {
132
- path: '/custom-login',
133
- title: 'Custom Login',
134
- redirectUrl: '/dashboard',
135
- };
136
-
137
- const plugin = createVirtualModulePlugin({}, loginPageConfig, null);
138
- const code = plugin.load('\0virtual:htlkg-config');
139
-
140
- expect(code).toContain('loginPageConfig');
141
- expect(code).toContain('custom-login');
142
- expect(code).toContain('Custom Login');
143
- });
144
-
145
- it('should set login page config to null when disabled', () => {
146
- const plugin = createVirtualModulePlugin({}, false, null);
147
- const code = plugin.load('\0virtual:htlkg-config');
148
-
149
- expect(code).toContain('loginPageConfig = null');
150
- });
151
-
152
- it('should not load non-virtual module IDs', () => {
153
- const plugin = createVirtualModulePlugin({}, null, null);
154
- const code = plugin.load('some-other-module');
155
-
156
- expect(code).toBeUndefined();
157
- });
158
- });
@@ -1,85 +0,0 @@
1
- /**
2
- * Virtual module setup for htlkg integration
3
- * Creates Vite virtual modules to pass configuration to middleware and pages
4
- */
5
-
6
- import type { RouteGuardConfig, LoginPageConfig, RoutePattern } from './config.js';
7
-
8
- /**
9
- * Serialize a route pattern (RegExp or string) to JavaScript code
10
- */
11
- function serializePattern(pattern: RegExp | string): string {
12
- if (pattern instanceof RegExp) {
13
- return `new RegExp(${JSON.stringify(pattern.source)}, ${JSON.stringify(pattern.flags)})`;
14
- }
15
- return JSON.stringify(pattern);
16
- }
17
-
18
- /**
19
- * Serialize an array of route patterns to JavaScript code
20
- */
21
- function serializePatterns(patterns: RoutePattern[]): string {
22
- if (!patterns || patterns.length === 0) return '[]';
23
- return `[${patterns.map(serializePattern).join(', ')}]`;
24
- }
25
-
26
- /**
27
- * Create the virtual module plugin for Vite
28
- * This plugin provides configuration to middleware and pages at runtime
29
- */
30
- export function createVirtualModulePlugin(
31
- authConfig: RouteGuardConfig,
32
- loginPageConfig: LoginPageConfig | false | null,
33
- amplifyConfig: Record<string, unknown> | null
34
- ) {
35
- const virtualModuleId = 'virtual:htlkg-config';
36
- const resolvedVirtualModuleId = '\0' + virtualModuleId;
37
-
38
- return {
39
- name: 'htlkg-config',
40
- enforce: 'pre' as const,
41
- resolveId(id: string) {
42
- if (id === virtualModuleId) {
43
- return resolvedVirtualModuleId;
44
- }
45
- },
46
- load(id: string) {
47
- if (id === resolvedVirtualModuleId) {
48
- // Serialize auth configuration with RegExp support
49
- const serializedAuthConfig = `{
50
- publicRoutes: ${serializePatterns(authConfig.publicRoutes || [])},
51
- authenticatedRoutes: ${serializePatterns(authConfig.authenticatedRoutes || [])},
52
- adminRoutes: ${serializePatterns(authConfig.adminRoutes || [])},
53
- brandRoutes: ${JSON.stringify(authConfig.brandRoutes || [])},
54
- loginUrl: ${JSON.stringify(authConfig.loginUrl || '/login')}
55
- }`;
56
-
57
- const serializedAmplifyConfig = amplifyConfig
58
- ? JSON.stringify(amplifyConfig)
59
- : 'null';
60
-
61
- const serializedLoginPageConfig = loginPageConfig !== false && loginPageConfig !== null
62
- ? JSON.stringify(loginPageConfig)
63
- : 'null';
64
-
65
- return `export const routeGuardConfig = ${serializedAuthConfig};
66
- export const loginPageConfig = ${serializedLoginPageConfig};
67
- export const amplifyConfig = ${serializedAmplifyConfig};`;
68
- }
69
- },
70
- };
71
- }
72
-
73
- /**
74
- * Type definitions for the virtual module
75
- * This should be injected into the project's type definitions
76
- */
77
- export const virtualModuleTypes = `
78
- declare module 'virtual:htlkg-config' {
79
- import type { RouteGuardConfig, LoginPageConfig } from '@htlkg/astro/htlkg/config';
80
-
81
- export const routeGuardConfig: RouteGuardConfig;
82
- export const loginPageConfig: LoginPageConfig | null;
83
- export const amplifyConfig: Record<string, unknown> | null;
84
- }
85
- `;