Skip to content

Commit

Permalink
1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
watany-dev committed May 16, 2024
1 parent 15e66b8 commit b9d97c2
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 71 deletions.
76 changes: 70 additions & 6 deletions deno_dist/helper/ssg/ssg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,66 @@ export type BeforeRequestHook = (req: Request) => Request | false | Promise<Requ
export type AfterResponseHook = (res: Response) => Response | false | Promise<Response | false>
export type AfterGenerateHook = (result: ToSSGResult) => void | Promise<void>

export const combineBeforeRequestHooks = (
hooks: BeforeRequestHook | BeforeRequestHook[]
): BeforeRequestHook => {
if (!Array.isArray(hooks)) {
return hooks
}
return async (req: Request): Promise<Request | false> => {
let currentReq = req
for (const hook of hooks) {
const result = await hook(currentReq)
if (result === false) {
return false
}
if (result instanceof Request) {
currentReq = result
}
}
return currentReq
}
}

export const combineAfterResponseHooks = (
hooks: AfterResponseHook | AfterResponseHook[]
): AfterResponseHook => {
if (!Array.isArray(hooks)) {
return hooks
}
return async (res: Response): Promise<Response | false> => {
let currentRes = res
for (const hook of hooks) {
const result = await hook(currentRes)
if (result === false) {
return false
}
if (result instanceof Response) {
currentRes = result
}
}
return currentRes
}
}

export const combineAfterGenerateHooks = (
hooks: AfterGenerateHook | AfterGenerateHook[]
): AfterGenerateHook => {
if (!Array.isArray(hooks)) {
return hooks
}
return async (result: ToSSGResult): Promise<void> => {
for (const hook of hooks) {
await hook(result)
}
}
}

export interface ToSSGOptions {
dir?: string
beforeRequestHook?: BeforeRequestHook
afterResponseHook?: AfterResponseHook
afterGenerateHook?: AfterGenerateHook
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
concurrency?: number
extensionMap?: Record<string, string>
}
Expand Down Expand Up @@ -286,10 +341,16 @@ export const toSSG: ToSSGInterface = async (app, fs, options) => {
const outputDir = options?.dir ?? './static'
const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY

const combinedBeforeRequestHook = combineBeforeRequestHooks(
options?.beforeRequestHook || ((req) => req)
)
const combinedAfterResponseHook = combineAfterResponseHooks(
options?.afterResponseHook || ((req) => req)
)
const getInfoGen = fetchRoutesContent(
app,
options?.beforeRequestHook,
options?.afterResponseHook,
combinedBeforeRequestHook,
combinedAfterResponseHook,
concurrency
)
for (const getInfo of getInfoGen) {
Expand Down Expand Up @@ -319,6 +380,9 @@ export const toSSG: ToSSGInterface = async (app, fs, options) => {
const errorObj = error instanceof Error ? error : new Error(String(error))
result = { success: false, files: [], error: errorObj }
}
await options?.afterGenerateHook?.(result)
if (options?.afterGenerateHook) {
const conbinedAfterGenerateHooks = combineAfterGenerateHooks(options?.afterGenerateHook)
await conbinedAfterGenerateHooks(result)
}
return result
}
124 changes: 64 additions & 60 deletions src/helper/ssg/ssg.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import type {
AfterResponseHook,
AfterGenerateHook,
FileSystemModule,
ToSSGResult
ToSSGResult,
} from './ssg'

const resolveRoutesContent = async (res: ReturnType<typeof fetchRoutesContent>) => {
Expand Down Expand Up @@ -638,23 +638,22 @@ describe('Request hooks - filterPathsBeforeRequestHook and denyPathsBeforeReques
return async (req: Request): Promise<Request | false> => {
const paths = Array.isArray(allowedPaths) ? allowedPaths : [allowedPaths]
const pathname = new URL(req.url, baseURL).pathname
if (paths.some(path => pathname === path || pathname.startsWith(`${path}/`))) {

if (paths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
return req
}

return false
}
}


const denyPathsBeforeRequestHook = (deniedPaths: string | string[]): BeforeRequestHook => {
const denyPathsBeforeRequestHook = (deniedPaths: string | string[]): BeforeRequestHook => {
const baseURL = 'http://localhost'
return async (req: Request): Promise<Request | false> => {
const paths = Array.isArray(deniedPaths) ? deniedPaths : [deniedPaths]
const pathname = new URL(req.url, baseURL).pathname
if (!paths.some(path => pathname === path || pathname.startsWith(`${path}/`))) {

if (!paths.some((path) => pathname === path || pathname.startsWith(`${path}/`))) {
return req
}
return false
Expand All @@ -676,113 +675,118 @@ describe('Request hooks - filterPathsBeforeRequestHook and denyPathsBeforeReques
it('should only process requests for allowed paths with filterPathsBeforeRequestHook', async () => {
const allowedPathsHook = filterPathsBeforeRequestHook(['/allowed-path'])

const result = await toSSG(app, fsMock, { dir: './static', beforeRequestHook: allowedPathsHook })
const result = await toSSG(app, fsMock, {
dir: './static',
beforeRequestHook: allowedPathsHook,
})

expect(result.files.some(file => file.includes('allowed-path.html'))).toBe(true)
expect(result.files.some(file => file.includes('other-path.html'))).toBe(false)
expect(result.files.some((file) => file.includes('allowed-path.html'))).toBe(true)
expect(result.files.some((file) => file.includes('other-path.html'))).toBe(false)
})

it('should deny requests for specified paths with denyPathsBeforeRequestHook', async () => {
const deniedPathsHook = denyPathsBeforeRequestHook(['/denied-path'])

const result = await toSSG(app, fsMock, { dir: './static', beforeRequestHook: deniedPathsHook })

expect(result.files.some(file => file.includes('denied-path.html'))).toBe(false)

expect(result.files.some(file => file.includes('allowed-path.html'))).toBe(true)
expect(result.files.some(file => file.includes('other-path.html'))).toBe(true)
expect(result.files.some((file) => file.includes('denied-path.html'))).toBe(false)

expect(result.files.some((file) => file.includes('allowed-path.html'))).toBe(true)
expect(result.files.some((file) => file.includes('other-path.html'))).toBe(true)
})
})

describe('Combined Response hooks - modify response content', () => {
let app: Hono;
let fsMock: FileSystemModule;
let app: Hono
let fsMock: FileSystemModule

const prependContentAfterResponseHook = (prefix: string): AfterResponseHook => {
return async (res: Response): Promise<Response> => {
const originalText = await res.text();
return new Response(`${prefix}${originalText}`, { ...res });
};
};
const originalText = await res.text()
return new Response(`${prefix}${originalText}`, { ...res })
}
}

const appendContentAfterResponseHook = (suffix: string): AfterResponseHook => {
return async (res: Response): Promise<Response> => {
const originalText = await res.text();
return new Response(`${originalText}${suffix}`, { ...res });
};
};
const originalText = await res.text()
return new Response(`${originalText}${suffix}`, { ...res })
}
}

beforeEach(() => {
app = new Hono();
app.get('/content-path', (c) => c.text('Original Content'));
app = new Hono()
app.get('/content-path', (c) => c.text('Original Content'))

fsMock = {
writeFile: vi.fn(() => Promise.resolve()),
mkdir: vi.fn(() => Promise.resolve()),
};
});
}
})

it('should modify response content with combined AfterResponseHooks', async () => {
const prefixHook = prependContentAfterResponseHook('Prefix-');
const suffixHook = appendContentAfterResponseHook('-Suffix');
const prefixHook = prependContentAfterResponseHook('Prefix-')
const suffixHook = appendContentAfterResponseHook('-Suffix')

const combinedHook = [prefixHook, suffixHook];
const combinedHook = [prefixHook, suffixHook]

await toSSG(app, fsMock, {
dir: './static',
afterResponseHook: combinedHook
});
afterResponseHook: combinedHook,
})

// Assert that the response content is modified by both hooks
// This assumes you have a way to inspect the content of saved files or you need to mock/stub the Response text method correctly.
expect(fsMock.writeFile).toHaveBeenCalledWith('static/content-path.txt', 'Prefix-Original Content-Suffix')
});
});
expect(fsMock.writeFile).toHaveBeenCalledWith(
'static/content-path.txt',
'Prefix-Original Content-Suffix'
)
})
})

describe('Combined Generate hooks - AfterGenerateHook', () => {
let app: Hono;
let fsMock: FileSystemModule;
let app: Hono
let fsMock: FileSystemModule

const logResultAfterGenerateHook = (): AfterGenerateHook => {
return async (result: ToSSGResult): Promise<void> => {
console.log('Generation completed with status:', result.success); // Log the generation success
};
};
console.log('Generation completed with status:', result.success) // Log the generation success
}
}

const appendFilesAfterGenerateHook = (additionalFiles: string[]): AfterGenerateHook => {
return async (result: ToSSGResult): Promise<void> => {
result.files = result.files.concat(additionalFiles); // Append additional files to the result
};
};
result.files = result.files.concat(additionalFiles) // Append additional files to the result
}
}

beforeEach(() => {
app = new Hono();
app.get('/path', (c) => c.text('Page Content'));
app = new Hono()
app.get('/path', (c) => c.text('Page Content'))

fsMock = {
writeFile: vi.fn(() => Promise.resolve()),
mkdir: vi.fn(() => Promise.resolve()),
};
});
}
})

it('should execute combined AfterGenerateHooks affecting the result', async () => {
const logHook = logResultAfterGenerateHook();
const appendHook = appendFilesAfterGenerateHook(['/extra/file1.html', '/extra/file2.html']);
const logHook = logResultAfterGenerateHook()
const appendHook = appendFilesAfterGenerateHook(['/extra/file1.html', '/extra/file2.html'])

const combinedHook = [logHook, appendHook];
const combinedHook = [logHook, appendHook]

const consoleSpy = vi.spyOn(console, 'log');
const consoleSpy = vi.spyOn(console, 'log')
const result = await toSSG(app, fsMock, {
dir: './static',
afterGenerateHook: combinedHook
});
afterGenerateHook: combinedHook,
})

// Check that the log function was called correctly
expect(consoleSpy).toHaveBeenCalledWith('Generation completed with status:', true);
expect(consoleSpy).toHaveBeenCalledWith('Generation completed with status:', true)

// Check that additional files were appended to the result
expect(result.files).toContain('/extra/file1.html');
expect(result.files).toContain('/extra/file2.html');
});
});
expect(result.files).toContain('/extra/file1.html')
expect(result.files).toContain('/extra/file2.html')
})
})
13 changes: 8 additions & 5 deletions src/helper/ssg/ssg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,10 @@ export const combineAfterGenerateHooks = (
}
}


export interface ToSSGOptions {
dir?: string
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
beforeRequestHook?: BeforeRequestHook | BeforeRequestHook[]
afterResponseHook?: AfterResponseHook | AfterResponseHook[]
afterGenerateHook?: AfterGenerateHook | AfterGenerateHook[]
concurrency?: number
extensionMap?: Record<string, string>
Expand Down Expand Up @@ -342,8 +341,12 @@ export const toSSG: ToSSGInterface = async (app, fs, options) => {
const outputDir = options?.dir ?? './static'
const concurrency = options?.concurrency ?? DEFAULT_CONCURRENCY

const combinedBeforeRequestHook = combineBeforeRequestHooks(options?.beforeRequestHook || ((req) => req))
const combinedAfterResponseHook = combineAfterResponseHooks(options?.afterResponseHook || ((req) => req))
const combinedBeforeRequestHook = combineBeforeRequestHooks(
options?.beforeRequestHook || ((req) => req)
)
const combinedAfterResponseHook = combineAfterResponseHooks(
options?.afterResponseHook || ((req) => req)
)
const getInfoGen = fetchRoutesContent(
app,
combinedBeforeRequestHook,
Expand Down

0 comments on commit b9d97c2

Please sign in to comment.