|
|
@@ -28,6 +28,58 @@ function getPathsToStrip(config) {
|
|
|
return (config.stripPaths || []).map(p => expandTilde(p))
|
|
|
}
|
|
|
|
|
|
+function paginateItems(items, itemsPerPage) {
|
|
|
+ if (!itemsPerPage || itemsPerPage <= 0 || !Array.isArray(items)) {
|
|
|
+ return [{ items, page: 1, totalPages: 1 }]
|
|
|
+ }
|
|
|
+ const totalPages = Math.ceil(items.length / itemsPerPage)
|
|
|
+ if (totalPages <= 1) {
|
|
|
+ return [{ items, page: 1, totalPages: 1 }]
|
|
|
+ }
|
|
|
+ const pages = []
|
|
|
+ for (let page = 1; page <= totalPages; page++) {
|
|
|
+ const start = (page - 1) * itemsPerPage
|
|
|
+ const end = start + itemsPerPage
|
|
|
+ pages.push({
|
|
|
+ items: items.slice(start, end),
|
|
|
+ page,
|
|
|
+ totalPages,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return pages
|
|
|
+}
|
|
|
+
|
|
|
+function buildPaginationMeta(page, totalPages, basePath) {
|
|
|
+ const ext = path.extname(basePath)
|
|
|
+ const base = basePath.slice(0, -ext.length)
|
|
|
+
|
|
|
+ const getPagePath = (pageNum) => {
|
|
|
+ if (pageNum === 1) return base.endsWith("/") ? base : `${base}/`
|
|
|
+ return `${base}/${pageNum}`
|
|
|
+ }
|
|
|
+
|
|
|
+ const pages = []
|
|
|
+ for (let i = 1; i <= totalPages; i++) {
|
|
|
+ pages.push({
|
|
|
+ pageNum: i,
|
|
|
+ path: getPagePath(i),
|
|
|
+ isCurrent: i === page,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ currentPage: page,
|
|
|
+ totalPages,
|
|
|
+ hasNextPage: page < totalPages,
|
|
|
+ hasPrevPage: page > 1,
|
|
|
+ nextPagePath: page < totalPages ? getPagePath(page + 1) : null,
|
|
|
+ prevPagePath: page > 1 ? getPagePath(page - 1) : null,
|
|
|
+ firstPagePath: getPagePath(1),
|
|
|
+ lastPagePath: getPagePath(totalPages),
|
|
|
+ pages,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
export async function getConfig() {
|
|
|
const args = process.argv.slice(2)
|
|
|
const defaultPath = path.join(process.cwd(), "rhedyn.config.js")
|
|
|
@@ -201,34 +253,72 @@ function selectState(stateToSelect, meta) {
|
|
|
async function expandStateTask(stateToExpand, config, meta) {
|
|
|
const stateToProcess = selectState(stateToExpand, meta)
|
|
|
const pathsToStrip = getPathsToStrip(config)
|
|
|
- return await Promise.all(
|
|
|
- stateToProcess.map(async (stateJob, index) => {
|
|
|
+ const itemsPerPage = config.itemsPerPage ?? meta.opts.itemsPerPage
|
|
|
+
|
|
|
+ // Build jobs, potentially with pagination
|
|
|
+ const jobs = stateToProcess.flatMap((stateJob, index) => {
|
|
|
+ const items = Array.isArray(stateJob.value)
|
|
|
+ ? stateJob.value
|
|
|
+ : stateJob.value?.detail || []
|
|
|
+
|
|
|
+ // Only paginate if items is an array and itemsPerPage is set
|
|
|
+ const shouldPaginate = Array.isArray(items) && itemsPerPage > 0
|
|
|
+ const pages = shouldPaginate
|
|
|
+ ? paginateItems(items, itemsPerPage)
|
|
|
+ : [{ items, page: 1, totalPages: 1 }]
|
|
|
+
|
|
|
+ return pages.map(({ items: pageItems, page, totalPages }) => {
|
|
|
+ const basePath = buildOutputPath(
|
|
|
+ meta.opts.outDir,
|
|
|
+ config.outputDir,
|
|
|
+ pathsToStrip,
|
|
|
+ stateJob.key || String(index),
|
|
|
+ config.outputFileExtension,
|
|
|
+ )
|
|
|
+
|
|
|
+ // For page 2+, modify the output path
|
|
|
+ const fileOutputPath = page === 1
|
|
|
+ ? basePath
|
|
|
+ : (() => {
|
|
|
+ const ext = path.extname(basePath)
|
|
|
+ const base = basePath.slice(0, -ext.length)
|
|
|
+ return `${base}/${page}${ext}`
|
|
|
+ })()
|
|
|
+
|
|
|
+ const pagination = totalPages > 1
|
|
|
+ ? buildPaginationMeta(page, totalPages, basePath.replace(meta.opts.outDir, "/"))
|
|
|
+ : null
|
|
|
+
|
|
|
const decorations = {
|
|
|
...(Array.isArray(stateJob.value)
|
|
|
- ? { inputs: stateJob.value }
|
|
|
- : stateJob.value?.detail || {}),
|
|
|
-
|
|
|
- ...(config.buildFilePath
|
|
|
- ? {
|
|
|
- fileOutputPath: buildOutputPath(
|
|
|
- meta.opts.outDir,
|
|
|
- config.outputDir,
|
|
|
- pathsToStrip,
|
|
|
- stateJob.key || String(index),
|
|
|
- config.outputFileExtension,
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
+ ? { inputs: pageItems }
|
|
|
+ : { ...stateJob.value?.detail, inputs: pageItems }),
|
|
|
+ ...(config.buildFilePath ? { fileOutputPath } : {}),
|
|
|
+ ...(pagination ? { pagination } : {}),
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ stateJob,
|
|
|
+ index,
|
|
|
+ page,
|
|
|
+ decorations,
|
|
|
+ fileOutputPath,
|
|
|
}
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return await Promise.all(
|
|
|
+ jobs.map(async ({ stateJob, page, decorations }) => {
|
|
|
const jobConfig = {
|
|
|
...config,
|
|
|
...decorations,
|
|
|
stateKey: stateJob.key,
|
|
|
}
|
|
|
+ const pageInfo = page > 1 ? `:page-${page}` : ""
|
|
|
return runTask({
|
|
|
meta,
|
|
|
config: jobConfig,
|
|
|
- jobId: `${config.name} @ ${stateJob.property}:${stateJob.index}`,
|
|
|
+ jobId: `${config.name} @ ${stateJob.property}:${stateJob.index}${pageInfo}`,
|
|
|
})
|
|
|
}),
|
|
|
)
|
|
|
@@ -259,31 +349,99 @@ export async function expandAndRunTask(meta, config) {
|
|
|
const inputs = selectState(config.stateSelectors, meta).map(stateItem => {
|
|
|
return stateItem.value?.detail ?? stateItem.value
|
|
|
})
|
|
|
- const decorations = {
|
|
|
- ...(config.buildFilePath
|
|
|
- ? {
|
|
|
- fileOutputPath: buildOutputPath(
|
|
|
- meta.opts.outDir,
|
|
|
- config.outputDir,
|
|
|
- pathsToStrip,
|
|
|
- config.outputFileName || config.name,
|
|
|
- config.outputFileExtension,
|
|
|
- ),
|
|
|
- }
|
|
|
- : {}),
|
|
|
+ const itemsPerPage = config.itemsPerPage
|
|
|
+ const shouldPaginate =
|
|
|
+ Array.isArray(inputs) && itemsPerPage != null && itemsPerPage > 0
|
|
|
+ const basePath =
|
|
|
+ (config.buildFilePath || shouldPaginate)
|
|
|
+ ? buildOutputPath(
|
|
|
+ meta.opts.outDir,
|
|
|
+ config.outputDir,
|
|
|
+ pathsToStrip,
|
|
|
+ config.outputFileName || config.name,
|
|
|
+ config.outputFileExtension,
|
|
|
+ )
|
|
|
+ : null
|
|
|
+
|
|
|
+ if (!shouldPaginate) {
|
|
|
+ const decorations = {
|
|
|
+ ...(config.buildFilePath
|
|
|
+ ? { fileOutputPath: basePath }
|
|
|
+ : {}),
|
|
|
+ }
|
|
|
+ const jobConfig = {
|
|
|
+ ...config,
|
|
|
+ ...decorations,
|
|
|
+ inputs,
|
|
|
+ }
|
|
|
+ const jobId = config.jobId || config.name
|
|
|
+ const taskResult = await runTask({
|
|
|
+ meta,
|
|
|
+ config: { ...jobConfig },
|
|
|
+ jobId,
|
|
|
+ })
|
|
|
+ return [taskResult]
|
|
|
}
|
|
|
- const jobConfig = {
|
|
|
- ...config,
|
|
|
- ...decorations,
|
|
|
- inputs,
|
|
|
+
|
|
|
+ const pages = paginateItems(inputs, itemsPerPage)
|
|
|
+ if (pages.length <= 1) {
|
|
|
+ const decorations = {
|
|
|
+ ...(config.buildFilePath
|
|
|
+ ? { fileOutputPath: basePath }
|
|
|
+ : {}),
|
|
|
+ }
|
|
|
+ const jobConfig = {
|
|
|
+ ...config,
|
|
|
+ ...decorations,
|
|
|
+ inputs,
|
|
|
+ }
|
|
|
+ const jobId = config.jobId || config.name
|
|
|
+ const taskResult = await runTask({
|
|
|
+ meta,
|
|
|
+ config: { ...jobConfig },
|
|
|
+ jobId,
|
|
|
+ })
|
|
|
+ return [taskResult]
|
|
|
}
|
|
|
- const jobId = config.jobId || config.name
|
|
|
- const taskResult = await runTask({
|
|
|
- meta,
|
|
|
- config: { ...jobConfig },
|
|
|
- jobId,
|
|
|
- })
|
|
|
- return [taskResult]
|
|
|
+
|
|
|
+ const basePathForPagination = basePath
|
|
|
+ ? basePath.replace(meta.opts.outDir, "/")
|
|
|
+ : null
|
|
|
+ const baseJobId = config.jobId || config.name
|
|
|
+ return await Promise.all(
|
|
|
+ pages.map(({ items: pageItems, page, totalPages }) => {
|
|
|
+ const fileOutputPath = basePath
|
|
|
+ ? (page === 1
|
|
|
+ ? basePath
|
|
|
+ : (() => {
|
|
|
+ const ext = path.extname(basePath)
|
|
|
+ const base = basePath.slice(0, -ext.length)
|
|
|
+ return `${base}/${page}${ext}`
|
|
|
+ })())
|
|
|
+ : undefined
|
|
|
+ const pagination =
|
|
|
+ totalPages > 1 && basePathForPagination
|
|
|
+ ? buildPaginationMeta(page, totalPages, basePathForPagination)
|
|
|
+ : null
|
|
|
+ const decorations = {
|
|
|
+ ...(config.buildFilePath && fileOutputPath
|
|
|
+ ? { fileOutputPath }
|
|
|
+ : {}),
|
|
|
+ ...(pagination ? { pagination } : {}),
|
|
|
+ }
|
|
|
+ const jobConfig = {
|
|
|
+ ...config,
|
|
|
+ ...decorations,
|
|
|
+ inputs: pageItems,
|
|
|
+ }
|
|
|
+ const pageInfo = page > 1 ? `:page-${page}` : ""
|
|
|
+ return runTask({
|
|
|
+ meta,
|
|
|
+ config: { ...jobConfig },
|
|
|
+ jobId: `${baseJobId}${pageInfo}`,
|
|
|
+ })
|
|
|
+ }),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
return expandStateTask(config.stateSelectors, config, meta)
|