@gradial/aci 0.1.20-preview.2 → 0.1.20-preview.3
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 +8 -10
- package/bin/aci +0 -0
- package/dist/astro/index.d.ts +2 -0
- package/dist/astro/index.js +4 -0
- package/dist/content/index.d.ts +1 -0
- package/dist/content/index.js +1 -0
- package/dist/content/provider.d.ts +19 -0
- package/dist/content/provider.js +7 -0
- package/dist/content/resolve-slots.d.ts +9 -0
- package/dist/content/resolve-slots.js +24 -0
- package/dist/content/types.d.ts +1 -1
- package/dist/content/validation.js +0 -1
- package/dist/define-component.js +0 -6
- package/dist/define-layout.d.ts +2 -0
- package/dist/define-layout.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/asset-route.d.ts +2 -0
- package/dist/next/asset-route.js +10 -2
- package/dist/next/config.js +1 -1
- package/dist/next/middleware.d.ts +6 -2
- package/dist/next/middleware.js +7 -2
- package/dist/next/page.d.ts +16 -15
- package/dist/next/page.js +27 -9
- package/dist/next/render.d.ts +5 -0
- package/dist/next/render.js +15 -0
- package/dist/next/root-layout.d.ts +4 -0
- package/dist/next/root-layout.js +5 -0
- package/dist/next/server.d.ts +3 -2
- package/dist/next/server.js +12 -2
- package/dist/providers/file.d.ts +2 -0
- package/dist/providers/file.js +61 -1
- package/dist/providers/s3.d.ts +1 -0
- package/dist/providers/s3.js +14 -1
- package/dist/registry.d.ts +15 -0
- package/dist/registry.js +10 -0
- package/dist/sveltekit/index.d.ts +2 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +15 -2
- package/dist/types/component.d.ts +0 -9
- package/dist/types/config.d.ts +0 -1
- package/dist/types/page.d.ts +1 -2
- package/dist/types/render-mode.d.ts +0 -1
- package/package.json +19 -13
- package/src/cli/compile-registry.mjs +132 -12
- package/src/types/component.ts +0 -10
- package/src/types/config.ts +0 -1
- package/src/types/page.ts +1 -2
- package/src/types/render-mode.ts +0 -8
- package/dist/content/cache.test.d.ts +0 -1
- package/dist/content/cache.test.js +0 -86
- package/dist/next/preview-banner.d.ts +0 -4
- package/dist/next/preview-banner.js +0 -51
- package/dist/next/preview.d.ts +0 -7
- package/dist/next/preview.js +0 -37
- package/src/cli/verify-renderer.mjs +0 -73
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from '@jest/globals';
|
|
2
|
-
import { getCachedSiteConfig, getCachedManifest, clearProviderCache, getCacheStats, resetCacheStats } from './cache.js';
|
|
3
|
-
describe('Content Cache', () => {
|
|
4
|
-
let mockProvider;
|
|
5
|
-
let getSiteConfigCallCount = 0;
|
|
6
|
-
let manifestCallCount = 0;
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
getSiteConfigCallCount = 0;
|
|
9
|
-
manifestCallCount = 0;
|
|
10
|
-
resetCacheStats();
|
|
11
|
-
mockProvider = {
|
|
12
|
-
getSiteConfig: async () => {
|
|
13
|
-
getSiteConfigCallCount++;
|
|
14
|
-
return { title: 'Test Site' };
|
|
15
|
-
},
|
|
16
|
-
manifest: async () => {
|
|
17
|
-
manifestCallCount++;
|
|
18
|
-
return {
|
|
19
|
-
routes: {},
|
|
20
|
-
fragments: {},
|
|
21
|
-
siteConfigRef: 'site.json'
|
|
22
|
-
};
|
|
23
|
-
},
|
|
24
|
-
getPage: async () => ({}),
|
|
25
|
-
getFragment: async () => ({}),
|
|
26
|
-
listRoutes: async () => []
|
|
27
|
-
};
|
|
28
|
-
});
|
|
29
|
-
it('should cache siteConfig across multiple calls', async () => {
|
|
30
|
-
const result1 = await getCachedSiteConfig(mockProvider);
|
|
31
|
-
const result2 = await getCachedSiteConfig(mockProvider);
|
|
32
|
-
expect(getSiteConfigCallCount).toBe(1); // Only called once
|
|
33
|
-
expect(result1).toEqual(result2);
|
|
34
|
-
const stats = getCacheStats();
|
|
35
|
-
expect(stats.siteConfig.hits).toBe(1);
|
|
36
|
-
expect(stats.siteConfig.misses).toBe(1);
|
|
37
|
-
});
|
|
38
|
-
it('should cache manifest across multiple calls', async () => {
|
|
39
|
-
const result1 = await getCachedManifest(mockProvider);
|
|
40
|
-
const result2 = await getCachedManifest(mockProvider);
|
|
41
|
-
expect(manifestCallCount).toBe(1); // Only called once
|
|
42
|
-
expect(result1).toEqual(result2);
|
|
43
|
-
const stats = getCacheStats();
|
|
44
|
-
expect(stats.manifest.hits).toBe(1);
|
|
45
|
-
expect(stats.manifest.misses).toBe(1);
|
|
46
|
-
});
|
|
47
|
-
it('should clear cache for a provider', async () => {
|
|
48
|
-
await getCachedSiteConfig(mockProvider);
|
|
49
|
-
expect(getSiteConfigCallCount).toBe(1);
|
|
50
|
-
clearProviderCache(mockProvider);
|
|
51
|
-
await getCachedSiteConfig(mockProvider);
|
|
52
|
-
expect(getSiteConfigCallCount).toBe(2); // Called again after clear
|
|
53
|
-
const stats = getCacheStats();
|
|
54
|
-
expect(stats.siteConfig.misses).toBe(2);
|
|
55
|
-
});
|
|
56
|
-
it('should not cache rejected promises', async () => {
|
|
57
|
-
const errorProvider = {
|
|
58
|
-
getSiteConfig: async () => {
|
|
59
|
-
getSiteConfigCallCount++;
|
|
60
|
-
throw new Error('S3 timeout');
|
|
61
|
-
},
|
|
62
|
-
getPage: async () => ({}),
|
|
63
|
-
getFragment: async () => ({}),
|
|
64
|
-
listRoutes: async () => []
|
|
65
|
-
};
|
|
66
|
-
await expect(getCachedSiteConfig(errorProvider)).rejects.toThrow('S3 timeout');
|
|
67
|
-
expect(getSiteConfigCallCount).toBe(1);
|
|
68
|
-
// Second call should retry (not cached)
|
|
69
|
-
await expect(getCachedSiteConfig(errorProvider)).rejects.toThrow('S3 timeout');
|
|
70
|
-
expect(getSiteConfigCallCount).toBe(2);
|
|
71
|
-
});
|
|
72
|
-
it('should cache separately per provider instance', async () => {
|
|
73
|
-
const provider2 = {
|
|
74
|
-
getSiteConfig: async () => ({ title: 'Site 2' }),
|
|
75
|
-
getPage: async () => ({}),
|
|
76
|
-
getFragment: async () => ({}),
|
|
77
|
-
listRoutes: async () => []
|
|
78
|
-
};
|
|
79
|
-
const result1 = await getCachedSiteConfig(mockProvider);
|
|
80
|
-
const result2 = await getCachedSiteConfig(provider2);
|
|
81
|
-
expect(result1).toEqual({ title: 'Test Site' });
|
|
82
|
-
expect(result2).toEqual({ title: 'Site 2' });
|
|
83
|
-
const stats = getCacheStats();
|
|
84
|
-
expect(stats.siteConfig.misses).toBe(2); // Both are cache misses
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function PreviewBanner({ releaseId }) {
|
|
3
|
-
if (!releaseId)
|
|
4
|
-
return null;
|
|
5
|
-
const shortId = releaseId.length > 20
|
|
6
|
-
? `${releaseId.slice(0, 8)}\u2026${releaseId.slice(-6)}`
|
|
7
|
-
: releaseId;
|
|
8
|
-
return (_jsxs("div", { role: "alert", style: {
|
|
9
|
-
position: 'fixed',
|
|
10
|
-
top: 0,
|
|
11
|
-
left: 0,
|
|
12
|
-
right: 0,
|
|
13
|
-
zIndex: 9999,
|
|
14
|
-
display: 'flex',
|
|
15
|
-
alignItems: 'center',
|
|
16
|
-
justifyContent: 'center',
|
|
17
|
-
gap: '12px',
|
|
18
|
-
padding: '8px 16px',
|
|
19
|
-
backgroundColor: '#1a1a2e',
|
|
20
|
-
color: '#fff',
|
|
21
|
-
fontSize: '13px',
|
|
22
|
-
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
23
|
-
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
|
|
24
|
-
}, children: [_jsx("span", { "aria-hidden": "true", style: {
|
|
25
|
-
display: 'inline-flex',
|
|
26
|
-
alignItems: 'center',
|
|
27
|
-
justifyContent: 'center',
|
|
28
|
-
width: '18px',
|
|
29
|
-
height: '18px',
|
|
30
|
-
borderRadius: '50%',
|
|
31
|
-
backgroundColor: '#f59e0b',
|
|
32
|
-
color: '#1a1a2e',
|
|
33
|
-
fontWeight: 700,
|
|
34
|
-
fontSize: '11px',
|
|
35
|
-
flexShrink: 0,
|
|
36
|
-
}, children: "P" }), _jsx("span", { style: { fontWeight: 500 }, children: "Release Preview" }), _jsx("span", { style: {
|
|
37
|
-
fontFamily: 'monospace',
|
|
38
|
-
opacity: 0.7,
|
|
39
|
-
fontSize: '12px',
|
|
40
|
-
}, title: releaseId, children: shortId }), _jsx("a", { href: "?leave_preview=1", style: {
|
|
41
|
-
marginLeft: 'auto',
|
|
42
|
-
padding: '4px 12px',
|
|
43
|
-
borderRadius: '6px',
|
|
44
|
-
backgroundColor: '#dc2626',
|
|
45
|
-
color: '#fff',
|
|
46
|
-
textDecoration: 'none',
|
|
47
|
-
fontSize: '12px',
|
|
48
|
-
fontWeight: 600,
|
|
49
|
-
whiteSpace: 'nowrap',
|
|
50
|
-
}, children: "Exit Preview" })] }));
|
|
51
|
-
}
|
package/dist/next/preview.d.ts
DELETED
package/dist/next/preview.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export async function validatePreviewToken(token, signKey) {
|
|
2
|
-
try {
|
|
3
|
-
const parts = token.split('.');
|
|
4
|
-
if (parts.length !== 3)
|
|
5
|
-
return null;
|
|
6
|
-
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
7
|
-
const signature = base64URLToBytes(parts[2]);
|
|
8
|
-
const key = await crypto.subtle.importKey('raw', new TextEncoder().encode(signKey), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
9
|
-
const expected = new Uint8Array(await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(signingInput)));
|
|
10
|
-
if (!constantTimeEqual(signature, expected))
|
|
11
|
-
return null;
|
|
12
|
-
const payload = new TextDecoder().decode(base64URLToBytes(parts[1]));
|
|
13
|
-
return JSON.parse(payload);
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return null;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function base64URLToBytes(value) {
|
|
20
|
-
const base64 = value.replace(/-/g, '+').replace(/_/g, '/');
|
|
21
|
-
const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, '=');
|
|
22
|
-
const binary = atob(padded);
|
|
23
|
-
const bytes = new Uint8Array(binary.length);
|
|
24
|
-
for (let i = 0; i < binary.length; i++) {
|
|
25
|
-
bytes[i] = binary.charCodeAt(i);
|
|
26
|
-
}
|
|
27
|
-
return bytes;
|
|
28
|
-
}
|
|
29
|
-
function constantTimeEqual(left, right) {
|
|
30
|
-
if (left.length !== right.length)
|
|
31
|
-
return false;
|
|
32
|
-
let diff = 0;
|
|
33
|
-
for (let i = 0; i < left.length; i++) {
|
|
34
|
-
diff |= left[i] ^ right[i];
|
|
35
|
-
}
|
|
36
|
-
return diff === 0;
|
|
37
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
-
|
|
6
|
-
const args = parseArgs(process.argv.slice(2));
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
if (!args.renderer) throw new Error('missing --renderer');
|
|
10
|
-
await verifyRenderer(path.resolve(args.renderer));
|
|
11
|
-
console.log(JSON.stringify({ success: true, errors: [] }, null, 2));
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error(JSON.stringify({ success: false, errors: [String(error?.message ?? error)] }, null, 2));
|
|
14
|
-
process.exitCode = 1;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async function verifyRenderer(rendererPath) {
|
|
18
|
-
try {
|
|
19
|
-
const mod = await import(pathToFileURL(rendererPath).href);
|
|
20
|
-
const renderer = mod.default ?? mod.renderer;
|
|
21
|
-
if (!renderer || typeof renderer.renderPage !== 'function') {
|
|
22
|
-
throw new Error('renderer must export a GradialRenderer with renderPage');
|
|
23
|
-
}
|
|
24
|
-
return;
|
|
25
|
-
} catch (error) {
|
|
26
|
-
if (!shouldUseStaticFallback(error)) {
|
|
27
|
-
throw error;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const source = fs.readFileSync(rendererPath, 'utf8');
|
|
32
|
-
if (!staticallyExportsRenderPage(source)) {
|
|
33
|
-
throw new Error('renderer must export a GradialRenderer with renderPage');
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function shouldUseStaticFallback(error) {
|
|
38
|
-
const message = String(error?.message ?? error);
|
|
39
|
-
return message.includes('Unknown file extension ".astro"');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function staticallyExportsRenderPage(source) {
|
|
43
|
-
if (!/\brenderPage\s*[:=]\s*(async\s*)?(\([^)]*\)|[A-Za-z_$][\w$]*)?\s*=>|\brenderPage\s*[:=]\s*(async\s*)?function\b|\basync\s+renderPage\s*\(|\brenderPage\s*\(/m.test(source)) {
|
|
44
|
-
return false;
|
|
45
|
-
}
|
|
46
|
-
if (/export\s+default\s+{[\s\S]*\brenderPage\b[\s\S]*}/m.test(source)) {
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
const defaultName = source.match(/export\s+default\s+([A-Za-z_$][\w$]*)\s*;?/m)?.[1];
|
|
50
|
-
const namedExport = source.match(/export\s*{\s*([A-Za-z_$][\w$]*)\s+as\s+renderer\s*}/m)?.[1] ?? source.match(/export\s*{\s*([A-Za-z_$][\w$]*)\s*}/m)?.[1];
|
|
51
|
-
const rendererName = defaultName ?? namedExport;
|
|
52
|
-
if (!rendererName) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
const declaration = new RegExp(`(?:const|let|var)\\s+${rendererName}\\b[\\s\\S]*?\\{[\\s\\S]*?\\brenderPage\\b`, 'm');
|
|
56
|
-
return declaration.test(source);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function parseArgs(argv) {
|
|
60
|
-
const out = {};
|
|
61
|
-
for (let i = 0; i < argv.length; i++) {
|
|
62
|
-
const arg = argv[i];
|
|
63
|
-
if (!arg.startsWith('--')) continue;
|
|
64
|
-
const body = arg.slice(2);
|
|
65
|
-
const equals = body.indexOf('=');
|
|
66
|
-
if (equals >= 0) {
|
|
67
|
-
out[body.slice(0, equals)] = body.slice(equals + 1);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
out[body] = argv[++i];
|
|
71
|
-
}
|
|
72
|
-
return out;
|
|
73
|
-
}
|