qlara 0.1.11 → 0.1.12
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/dist/aws.cjs +70 -19
- package/dist/aws.js +71 -20
- package/dist/cli.js +73 -22
- package/package.json +1 -1
- package/src/provider/aws/renderer.ts +63 -6
package/dist/aws.cjs
CHANGED
|
@@ -462,8 +462,25 @@ function buildTemplate(config) {
|
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
]
|
|
465
|
-
}
|
|
466
|
-
//
|
|
465
|
+
},
|
|
466
|
+
// Serve the framework's 404 page (e.g., Next.js not-found.ts → 404.html)
|
|
467
|
+
// when S3 returns 403 (missing file) or 404. This covers:
|
|
468
|
+
// - Unknown routes not in the manifest
|
|
469
|
+
// - Validation failures (renderer abandons, no file uploaded)
|
|
470
|
+
CustomErrorResponses: [
|
|
471
|
+
{
|
|
472
|
+
ErrorCode: 403,
|
|
473
|
+
ResponseCode: 404,
|
|
474
|
+
ResponsePagePath: "/404.html",
|
|
475
|
+
ErrorCachingMinTTL: 10
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
ErrorCode: 404,
|
|
479
|
+
ResponseCode: 404,
|
|
480
|
+
ResponsePagePath: "/404.html",
|
|
481
|
+
ErrorCachingMinTTL: 10
|
|
482
|
+
}
|
|
483
|
+
]
|
|
467
484
|
}
|
|
468
485
|
}
|
|
469
486
|
}
|
|
@@ -665,33 +682,55 @@ function generateFallbackFromTemplate(templateHtml, routePattern) {
|
|
|
665
682
|
);
|
|
666
683
|
return fallback;
|
|
667
684
|
}
|
|
685
|
+
function findTemplateForRoute(buildDir, routePattern) {
|
|
686
|
+
const segments = routePattern.replace(/^\//, "").split("/");
|
|
687
|
+
function walk(currentDir, segmentIndex) {
|
|
688
|
+
if (segmentIndex >= segments.length) return null;
|
|
689
|
+
if (!(0, import_node_fs3.existsSync)(currentDir)) return null;
|
|
690
|
+
const segment = segments[segmentIndex];
|
|
691
|
+
const isLast = segmentIndex === segments.length - 1;
|
|
692
|
+
const isDynamic = segment.startsWith(":");
|
|
693
|
+
if (isLast) {
|
|
694
|
+
const files = (0, import_node_fs3.readdirSync)(currentDir).filter(
|
|
695
|
+
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
696
|
+
);
|
|
697
|
+
return files.length > 0 ? (0, import_node_path3.join)(currentDir, files[0]) : null;
|
|
698
|
+
}
|
|
699
|
+
if (isDynamic) {
|
|
700
|
+
const entries = (0, import_node_fs3.readdirSync)(currentDir, { withFileTypes: true });
|
|
701
|
+
for (const entry of entries) {
|
|
702
|
+
if (entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
703
|
+
const result = walk((0, import_node_path3.join)(currentDir, entry.name), segmentIndex + 1);
|
|
704
|
+
if (result) return result;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
return walk((0, import_node_path3.join)(currentDir, segment), segmentIndex + 1);
|
|
710
|
+
}
|
|
711
|
+
return walk(buildDir, 0);
|
|
712
|
+
}
|
|
668
713
|
function generateFallbacks(buildDir, routes) {
|
|
669
714
|
const generated = [];
|
|
670
715
|
for (const route of routes) {
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
const routeDir = (0, import_node_path3.join)(buildDir, ...dirParts);
|
|
674
|
-
if (!(0, import_node_fs3.existsSync)(routeDir)) {
|
|
675
|
-
console.warn(
|
|
676
|
-
`[qlara] Warning: No output directory for route ${route.pattern} at ${routeDir}`
|
|
677
|
-
);
|
|
678
|
-
continue;
|
|
679
|
-
}
|
|
680
|
-
const files = (0, import_node_fs3.readdirSync)(routeDir).filter(
|
|
681
|
-
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
682
|
-
);
|
|
683
|
-
if (files.length === 0) {
|
|
716
|
+
const templatePath = findTemplateForRoute(buildDir, route.pattern);
|
|
717
|
+
if (!templatePath) {
|
|
684
718
|
console.warn(
|
|
685
|
-
`[qlara] Warning: No HTML
|
|
719
|
+
`[qlara] Warning: No HTML template found for route ${route.pattern}`
|
|
686
720
|
);
|
|
687
721
|
continue;
|
|
688
722
|
}
|
|
689
|
-
const templatePath = (0, import_node_path3.join)(routeDir, files[0]);
|
|
690
723
|
const templateHtml = (0, import_node_fs3.readFileSync)(templatePath, "utf-8");
|
|
691
724
|
const fallbackHtml = generateFallbackFromTemplate(templateHtml, route.pattern);
|
|
692
|
-
const
|
|
725
|
+
const parts = route.pattern.replace(/^\//, "").split("/");
|
|
726
|
+
const dirParts = parts.filter((p) => !p.startsWith(":"));
|
|
727
|
+
const fallbackDir = dirParts.length > 0 ? (0, import_node_path3.join)(buildDir, ...dirParts) : buildDir;
|
|
728
|
+
if (!(0, import_node_fs3.existsSync)(fallbackDir)) {
|
|
729
|
+
(0, import_node_fs3.mkdirSync)(fallbackDir, { recursive: true });
|
|
730
|
+
}
|
|
731
|
+
const fallbackPath = (0, import_node_path3.join)(fallbackDir, FALLBACK_FILENAME);
|
|
693
732
|
(0, import_node_fs3.writeFileSync)(fallbackPath, fallbackHtml);
|
|
694
|
-
const relativePath = (0, import_node_path3.join)(...dirParts, FALLBACK_FILENAME);
|
|
733
|
+
const relativePath = dirParts.length > 0 ? (0, import_node_path3.join)(...dirParts, FALLBACK_FILENAME) : FALLBACK_FILENAME;
|
|
695
734
|
generated.push(relativePath);
|
|
696
735
|
console.log(`[qlara] Generated fallback: ${relativePath}`);
|
|
697
736
|
}
|
|
@@ -789,6 +828,18 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
|
|
|
789
828
|
const qlaraPolicyId = await ensureCachePolicy(cf);
|
|
790
829
|
config.DefaultCacheBehavior.CachePolicyId = qlaraPolicyId;
|
|
791
830
|
}
|
|
831
|
+
const hasErrorResponses = config.CustomErrorResponses?.Items?.some(
|
|
832
|
+
(r) => r.ErrorCode === 403
|
|
833
|
+
);
|
|
834
|
+
if (!hasErrorResponses) {
|
|
835
|
+
config.CustomErrorResponses = {
|
|
836
|
+
Quantity: 2,
|
|
837
|
+
Items: [
|
|
838
|
+
{ ErrorCode: 403, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 },
|
|
839
|
+
{ ErrorCode: 404, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 }
|
|
840
|
+
]
|
|
841
|
+
};
|
|
842
|
+
}
|
|
792
843
|
await cf.send(
|
|
793
844
|
new import_client_cloudfront.UpdateDistributionCommand({
|
|
794
845
|
Id: distributionId,
|
package/dist/aws.js
CHANGED
|
@@ -459,8 +459,25 @@ function buildTemplate(config) {
|
|
|
459
459
|
}
|
|
460
460
|
}
|
|
461
461
|
]
|
|
462
|
-
}
|
|
463
|
-
//
|
|
462
|
+
},
|
|
463
|
+
// Serve the framework's 404 page (e.g., Next.js not-found.ts → 404.html)
|
|
464
|
+
// when S3 returns 403 (missing file) or 404. This covers:
|
|
465
|
+
// - Unknown routes not in the manifest
|
|
466
|
+
// - Validation failures (renderer abandons, no file uploaded)
|
|
467
|
+
CustomErrorResponses: [
|
|
468
|
+
{
|
|
469
|
+
ErrorCode: 403,
|
|
470
|
+
ResponseCode: 404,
|
|
471
|
+
ResponsePagePath: "/404.html",
|
|
472
|
+
ErrorCachingMinTTL: 10
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
ErrorCode: 404,
|
|
476
|
+
ResponseCode: 404,
|
|
477
|
+
ResponsePagePath: "/404.html",
|
|
478
|
+
ErrorCachingMinTTL: 10
|
|
479
|
+
}
|
|
480
|
+
]
|
|
464
481
|
}
|
|
465
482
|
}
|
|
466
483
|
}
|
|
@@ -592,7 +609,7 @@ async function bundleRenderer(routeFile, cacheTtl = 3600, framework) {
|
|
|
592
609
|
var STACK_NAME_PREFIX = "qlara";
|
|
593
610
|
|
|
594
611
|
// src/fallback.ts
|
|
595
|
-
import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
612
|
+
import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
596
613
|
import { join as join3 } from "path";
|
|
597
614
|
var FALLBACK_FILENAME = "_fallback.html";
|
|
598
615
|
function paramPlaceholder(paramName) {
|
|
@@ -661,33 +678,55 @@ function generateFallbackFromTemplate(templateHtml, routePattern) {
|
|
|
661
678
|
);
|
|
662
679
|
return fallback;
|
|
663
680
|
}
|
|
681
|
+
function findTemplateForRoute(buildDir, routePattern) {
|
|
682
|
+
const segments = routePattern.replace(/^\//, "").split("/");
|
|
683
|
+
function walk(currentDir, segmentIndex) {
|
|
684
|
+
if (segmentIndex >= segments.length) return null;
|
|
685
|
+
if (!existsSync2(currentDir)) return null;
|
|
686
|
+
const segment = segments[segmentIndex];
|
|
687
|
+
const isLast = segmentIndex === segments.length - 1;
|
|
688
|
+
const isDynamic = segment.startsWith(":");
|
|
689
|
+
if (isLast) {
|
|
690
|
+
const files = readdirSync2(currentDir).filter(
|
|
691
|
+
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
692
|
+
);
|
|
693
|
+
return files.length > 0 ? join3(currentDir, files[0]) : null;
|
|
694
|
+
}
|
|
695
|
+
if (isDynamic) {
|
|
696
|
+
const entries = readdirSync2(currentDir, { withFileTypes: true });
|
|
697
|
+
for (const entry of entries) {
|
|
698
|
+
if (entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
699
|
+
const result = walk(join3(currentDir, entry.name), segmentIndex + 1);
|
|
700
|
+
if (result) return result;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return walk(join3(currentDir, segment), segmentIndex + 1);
|
|
706
|
+
}
|
|
707
|
+
return walk(buildDir, 0);
|
|
708
|
+
}
|
|
664
709
|
function generateFallbacks(buildDir, routes) {
|
|
665
710
|
const generated = [];
|
|
666
711
|
for (const route of routes) {
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
const routeDir = join3(buildDir, ...dirParts);
|
|
670
|
-
if (!existsSync2(routeDir)) {
|
|
671
|
-
console.warn(
|
|
672
|
-
`[qlara] Warning: No output directory for route ${route.pattern} at ${routeDir}`
|
|
673
|
-
);
|
|
674
|
-
continue;
|
|
675
|
-
}
|
|
676
|
-
const files = readdirSync2(routeDir).filter(
|
|
677
|
-
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
678
|
-
);
|
|
679
|
-
if (files.length === 0) {
|
|
712
|
+
const templatePath = findTemplateForRoute(buildDir, route.pattern);
|
|
713
|
+
if (!templatePath) {
|
|
680
714
|
console.warn(
|
|
681
|
-
`[qlara] Warning: No HTML
|
|
715
|
+
`[qlara] Warning: No HTML template found for route ${route.pattern}`
|
|
682
716
|
);
|
|
683
717
|
continue;
|
|
684
718
|
}
|
|
685
|
-
const templatePath = join3(routeDir, files[0]);
|
|
686
719
|
const templateHtml = readFileSync3(templatePath, "utf-8");
|
|
687
720
|
const fallbackHtml = generateFallbackFromTemplate(templateHtml, route.pattern);
|
|
688
|
-
const
|
|
721
|
+
const parts = route.pattern.replace(/^\//, "").split("/");
|
|
722
|
+
const dirParts = parts.filter((p) => !p.startsWith(":"));
|
|
723
|
+
const fallbackDir = dirParts.length > 0 ? join3(buildDir, ...dirParts) : buildDir;
|
|
724
|
+
if (!existsSync2(fallbackDir)) {
|
|
725
|
+
mkdirSync2(fallbackDir, { recursive: true });
|
|
726
|
+
}
|
|
727
|
+
const fallbackPath = join3(fallbackDir, FALLBACK_FILENAME);
|
|
689
728
|
writeFileSync(fallbackPath, fallbackHtml);
|
|
690
|
-
const relativePath = join3(...dirParts, FALLBACK_FILENAME);
|
|
729
|
+
const relativePath = dirParts.length > 0 ? join3(...dirParts, FALLBACK_FILENAME) : FALLBACK_FILENAME;
|
|
691
730
|
generated.push(relativePath);
|
|
692
731
|
console.log(`[qlara] Generated fallback: ${relativePath}`);
|
|
693
732
|
}
|
|
@@ -785,6 +824,18 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
|
|
|
785
824
|
const qlaraPolicyId = await ensureCachePolicy(cf);
|
|
786
825
|
config.DefaultCacheBehavior.CachePolicyId = qlaraPolicyId;
|
|
787
826
|
}
|
|
827
|
+
const hasErrorResponses = config.CustomErrorResponses?.Items?.some(
|
|
828
|
+
(r) => r.ErrorCode === 403
|
|
829
|
+
);
|
|
830
|
+
if (!hasErrorResponses) {
|
|
831
|
+
config.CustomErrorResponses = {
|
|
832
|
+
Quantity: 2,
|
|
833
|
+
Items: [
|
|
834
|
+
{ ErrorCode: 403, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 },
|
|
835
|
+
{ ErrorCode: 404, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 }
|
|
836
|
+
]
|
|
837
|
+
};
|
|
838
|
+
}
|
|
788
839
|
await cf.send(
|
|
789
840
|
new UpdateDistributionCommand({
|
|
790
841
|
Id: distributionId,
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as
|
|
4
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
5
5
|
import { join as join4 } from "path";
|
|
6
6
|
|
|
7
7
|
// src/provider/aws/constants.ts
|
|
@@ -470,8 +470,25 @@ function buildTemplate(config) {
|
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
472
|
]
|
|
473
|
-
}
|
|
474
|
-
//
|
|
473
|
+
},
|
|
474
|
+
// Serve the framework's 404 page (e.g., Next.js not-found.ts → 404.html)
|
|
475
|
+
// when S3 returns 403 (missing file) or 404. This covers:
|
|
476
|
+
// - Unknown routes not in the manifest
|
|
477
|
+
// - Validation failures (renderer abandons, no file uploaded)
|
|
478
|
+
CustomErrorResponses: [
|
|
479
|
+
{
|
|
480
|
+
ErrorCode: 403,
|
|
481
|
+
ResponseCode: 404,
|
|
482
|
+
ResponsePagePath: "/404.html",
|
|
483
|
+
ErrorCachingMinTTL: 10
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
ErrorCode: 404,
|
|
487
|
+
ResponseCode: 404,
|
|
488
|
+
ResponsePagePath: "/404.html",
|
|
489
|
+
ErrorCachingMinTTL: 10
|
|
490
|
+
}
|
|
491
|
+
]
|
|
475
492
|
}
|
|
476
493
|
}
|
|
477
494
|
}
|
|
@@ -600,7 +617,7 @@ async function bundleRenderer(routeFile, cacheTtl = 3600, framework) {
|
|
|
600
617
|
}
|
|
601
618
|
|
|
602
619
|
// src/fallback.ts
|
|
603
|
-
import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2 } from "fs";
|
|
620
|
+
import { readFileSync as readFileSync3, writeFileSync, readdirSync as readdirSync2, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
604
621
|
import { join as join3 } from "path";
|
|
605
622
|
var FALLBACK_FILENAME = "_fallback.html";
|
|
606
623
|
function paramPlaceholder(paramName) {
|
|
@@ -669,33 +686,55 @@ function generateFallbackFromTemplate(templateHtml, routePattern) {
|
|
|
669
686
|
);
|
|
670
687
|
return fallback;
|
|
671
688
|
}
|
|
689
|
+
function findTemplateForRoute(buildDir, routePattern) {
|
|
690
|
+
const segments = routePattern.replace(/^\//, "").split("/");
|
|
691
|
+
function walk(currentDir, segmentIndex) {
|
|
692
|
+
if (segmentIndex >= segments.length) return null;
|
|
693
|
+
if (!existsSync2(currentDir)) return null;
|
|
694
|
+
const segment = segments[segmentIndex];
|
|
695
|
+
const isLast = segmentIndex === segments.length - 1;
|
|
696
|
+
const isDynamic = segment.startsWith(":");
|
|
697
|
+
if (isLast) {
|
|
698
|
+
const files = readdirSync2(currentDir).filter(
|
|
699
|
+
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
700
|
+
);
|
|
701
|
+
return files.length > 0 ? join3(currentDir, files[0]) : null;
|
|
702
|
+
}
|
|
703
|
+
if (isDynamic) {
|
|
704
|
+
const entries = readdirSync2(currentDir, { withFileTypes: true });
|
|
705
|
+
for (const entry of entries) {
|
|
706
|
+
if (entry.isDirectory() && !entry.name.startsWith("_") && !entry.name.startsWith(".")) {
|
|
707
|
+
const result = walk(join3(currentDir, entry.name), segmentIndex + 1);
|
|
708
|
+
if (result) return result;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
return walk(join3(currentDir, segment), segmentIndex + 1);
|
|
714
|
+
}
|
|
715
|
+
return walk(buildDir, 0);
|
|
716
|
+
}
|
|
672
717
|
function generateFallbacks(buildDir, routes) {
|
|
673
718
|
const generated = [];
|
|
674
719
|
for (const route of routes) {
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
const routeDir = join3(buildDir, ...dirParts);
|
|
678
|
-
if (!existsSync2(routeDir)) {
|
|
679
|
-
console.warn(
|
|
680
|
-
`[qlara] Warning: No output directory for route ${route.pattern} at ${routeDir}`
|
|
681
|
-
);
|
|
682
|
-
continue;
|
|
683
|
-
}
|
|
684
|
-
const files = readdirSync2(routeDir).filter(
|
|
685
|
-
(f) => f.endsWith(".html") && f !== FALLBACK_FILENAME
|
|
686
|
-
);
|
|
687
|
-
if (files.length === 0) {
|
|
720
|
+
const templatePath = findTemplateForRoute(buildDir, route.pattern);
|
|
721
|
+
if (!templatePath) {
|
|
688
722
|
console.warn(
|
|
689
|
-
`[qlara] Warning: No HTML
|
|
723
|
+
`[qlara] Warning: No HTML template found for route ${route.pattern}`
|
|
690
724
|
);
|
|
691
725
|
continue;
|
|
692
726
|
}
|
|
693
|
-
const templatePath = join3(routeDir, files[0]);
|
|
694
727
|
const templateHtml = readFileSync3(templatePath, "utf-8");
|
|
695
728
|
const fallbackHtml = generateFallbackFromTemplate(templateHtml, route.pattern);
|
|
696
|
-
const
|
|
729
|
+
const parts = route.pattern.replace(/^\//, "").split("/");
|
|
730
|
+
const dirParts = parts.filter((p) => !p.startsWith(":"));
|
|
731
|
+
const fallbackDir = dirParts.length > 0 ? join3(buildDir, ...dirParts) : buildDir;
|
|
732
|
+
if (!existsSync2(fallbackDir)) {
|
|
733
|
+
mkdirSync2(fallbackDir, { recursive: true });
|
|
734
|
+
}
|
|
735
|
+
const fallbackPath = join3(fallbackDir, FALLBACK_FILENAME);
|
|
697
736
|
writeFileSync(fallbackPath, fallbackHtml);
|
|
698
|
-
const relativePath = join3(...dirParts, FALLBACK_FILENAME);
|
|
737
|
+
const relativePath = dirParts.length > 0 ? join3(...dirParts, FALLBACK_FILENAME) : FALLBACK_FILENAME;
|
|
699
738
|
generated.push(relativePath);
|
|
700
739
|
console.log(`[qlara] Generated fallback: ${relativePath}`);
|
|
701
740
|
}
|
|
@@ -793,6 +832,18 @@ async function updateCloudFrontEdgeVersion(cf, distributionId, newVersionArn) {
|
|
|
793
832
|
const qlaraPolicyId = await ensureCachePolicy(cf);
|
|
794
833
|
config.DefaultCacheBehavior.CachePolicyId = qlaraPolicyId;
|
|
795
834
|
}
|
|
835
|
+
const hasErrorResponses = config.CustomErrorResponses?.Items?.some(
|
|
836
|
+
(r) => r.ErrorCode === 403
|
|
837
|
+
);
|
|
838
|
+
if (!hasErrorResponses) {
|
|
839
|
+
config.CustomErrorResponses = {
|
|
840
|
+
Quantity: 2,
|
|
841
|
+
Items: [
|
|
842
|
+
{ ErrorCode: 403, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 },
|
|
843
|
+
{ ErrorCode: 404, ResponseCode: "404", ResponsePagePath: "/404.html", ErrorCachingMinTTL: 10 }
|
|
844
|
+
]
|
|
845
|
+
};
|
|
846
|
+
}
|
|
796
847
|
await cf.send(
|
|
797
848
|
new UpdateDistributionCommand({
|
|
798
849
|
Id: distributionId,
|
|
@@ -1161,7 +1212,7 @@ function loadResources() {
|
|
|
1161
1212
|
return JSON.parse(readFileSync4(RESOURCES_PATH, "utf-8"));
|
|
1162
1213
|
}
|
|
1163
1214
|
function saveResources(resources) {
|
|
1164
|
-
|
|
1215
|
+
mkdirSync3(QLARA_DIR, { recursive: true });
|
|
1165
1216
|
writeFileSync2(RESOURCES_PATH, JSON.stringify(resources, null, 2));
|
|
1166
1217
|
}
|
|
1167
1218
|
async function deploy() {
|
package/package.json
CHANGED
|
@@ -834,6 +834,58 @@ async function findReferenceSegmentDir(bucket: string, routePrefix: string): Pro
|
|
|
834
834
|
return null;
|
|
835
835
|
}
|
|
836
836
|
|
|
837
|
+
/**
|
|
838
|
+
* Find a reference segment directory using the route pattern.
|
|
839
|
+
* For multi-param routes like /:lang/products/:id, the direct URI-based prefix
|
|
840
|
+
* (e.g., 'da/products') may not contain any build-time pages. This function
|
|
841
|
+
* walks the S3 key hierarchy following the route pattern: static segments
|
|
842
|
+
* descend directly, dynamic segments list subdirectories and try any of them.
|
|
843
|
+
*/
|
|
844
|
+
async function findReferenceSegmentDirByPattern(
|
|
845
|
+
bucket: string,
|
|
846
|
+
routePattern: string,
|
|
847
|
+
): Promise<string | null> {
|
|
848
|
+
// Remove the last segment (the page-level param) to get the parent segments
|
|
849
|
+
const segments = routePattern.replace(/^\//, '').split('/');
|
|
850
|
+
const parentSegments = segments.slice(0, -1);
|
|
851
|
+
|
|
852
|
+
async function searchPrefix(prefix: string, segmentIndex: number): Promise<string | null> {
|
|
853
|
+
if (segmentIndex >= parentSegments.length) {
|
|
854
|
+
// We've traversed all parent segments — check for segment files here
|
|
855
|
+
return findReferenceSegmentDir(bucket, prefix.replace(/\/$/, ''));
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
const segment = parentSegments[segmentIndex];
|
|
859
|
+
|
|
860
|
+
if (!segment.startsWith(':')) {
|
|
861
|
+
// Static segment — descend directly
|
|
862
|
+
const nextPrefix = prefix ? `${prefix}${segment}/` : `${segment}/`;
|
|
863
|
+
return searchPrefix(nextPrefix, segmentIndex + 1);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Dynamic segment — list subdirectories and try each
|
|
867
|
+
try {
|
|
868
|
+
const response = await s3.send(new ListObjectsV2Command({
|
|
869
|
+
Bucket: bucket,
|
|
870
|
+
Prefix: prefix,
|
|
871
|
+
Delimiter: '/',
|
|
872
|
+
MaxKeys: 10,
|
|
873
|
+
}));
|
|
874
|
+
|
|
875
|
+
for (const commonPrefix of response.CommonPrefixes || []) {
|
|
876
|
+
const subPrefix = commonPrefix.Prefix || '';
|
|
877
|
+
const result = await searchPrefix(subPrefix, segmentIndex + 1);
|
|
878
|
+
if (result) return result;
|
|
879
|
+
}
|
|
880
|
+
} catch {
|
|
881
|
+
// S3 error — skip
|
|
882
|
+
}
|
|
883
|
+
return null;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
return searchPrefix('', 0);
|
|
887
|
+
}
|
|
888
|
+
|
|
837
889
|
type SegmentFileType = 'shared' | 'tree' | 'head' | 'full' | 'page';
|
|
838
890
|
|
|
839
891
|
/**
|
|
@@ -1004,14 +1056,19 @@ async function generateSegmentFiles(
|
|
|
1004
1056
|
params: Record<string, string>,
|
|
1005
1057
|
rscData: string | null,
|
|
1006
1058
|
metadata: QlaraMetadata | null,
|
|
1059
|
+
routePattern: string,
|
|
1007
1060
|
): Promise<void> {
|
|
1008
1061
|
const cleanUri = uri.replace(/^\//, '').replace(/\/$/, '');
|
|
1009
1062
|
const parts = cleanUri.split('/');
|
|
1010
|
-
const routePrefix = parts.slice(0, -1).join('/'); // '
|
|
1011
|
-
const segmentDir = `${cleanUri}/`; // '
|
|
1012
|
-
|
|
1013
|
-
// Find a build-time page with segment files to use as reference
|
|
1014
|
-
|
|
1063
|
+
const routePrefix = parts.slice(0, -1).join('/'); // 'da/products'
|
|
1064
|
+
const segmentDir = `${cleanUri}/`; // 'da/products/42/'
|
|
1065
|
+
|
|
1066
|
+
// Find a build-time page with segment files to use as reference.
|
|
1067
|
+
// Try the direct URI prefix first; fall back to pattern-based search for multi-param routes.
|
|
1068
|
+
let refDir = await findReferenceSegmentDir(bucket, routePrefix);
|
|
1069
|
+
if (!refDir) {
|
|
1070
|
+
refDir = await findReferenceSegmentDirByPattern(bucket, routePattern);
|
|
1071
|
+
}
|
|
1015
1072
|
if (!refDir) return; // No segment files on S3 — Next.js 15 or no build-time pages
|
|
1016
1073
|
|
|
1017
1074
|
// List all segment files in the reference directory
|
|
@@ -1217,7 +1274,7 @@ export async function handler(event: RendererEvent & { warmup?: boolean }): Prom
|
|
|
1217
1274
|
// 7. Generate per-segment prefetch files (Next.js 16+ Segment Cache)
|
|
1218
1275
|
// Reads templates from a build-time reference page, patches page-specific data.
|
|
1219
1276
|
// Skips automatically for Next.js 15 (no segment files on S3).
|
|
1220
|
-
await generateSegmentFiles(bucket, uri, params, rscData, metadata);
|
|
1277
|
+
await generateSegmentFiles(bucket, uri, params, rscData, metadata, routePattern);
|
|
1221
1278
|
}
|
|
1222
1279
|
|
|
1223
1280
|
return {
|