qlara 0.1.3 → 0.1.4
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 +37 -3
- package/dist/aws.js +37 -3
- package/dist/cli.js +37 -3
- package/package.json +1 -1
- package/src/provider/aws/edge-handler.ts +10 -3
- package/src/provider/aws/renderer.ts +1 -0
package/dist/aws.cjs
CHANGED
|
@@ -95,8 +95,26 @@ function listFiles(dir) {
|
|
|
95
95
|
}
|
|
96
96
|
return files;
|
|
97
97
|
}
|
|
98
|
+
async function listAllKeys(client, bucketName) {
|
|
99
|
+
const keys = /* @__PURE__ */ new Set();
|
|
100
|
+
let continuationToken;
|
|
101
|
+
do {
|
|
102
|
+
const response = await client.send(
|
|
103
|
+
new import_client_s3.ListObjectsV2Command({
|
|
104
|
+
Bucket: bucketName,
|
|
105
|
+
ContinuationToken: continuationToken
|
|
106
|
+
})
|
|
107
|
+
);
|
|
108
|
+
for (const obj of response.Contents || []) {
|
|
109
|
+
if (obj.Key) keys.add(obj.Key);
|
|
110
|
+
}
|
|
111
|
+
continuationToken = response.NextContinuationToken;
|
|
112
|
+
} while (continuationToken);
|
|
113
|
+
return keys;
|
|
114
|
+
}
|
|
98
115
|
async function syncToS3(client, bucketName, buildDir) {
|
|
99
116
|
const files = listFiles(buildDir);
|
|
117
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
100
118
|
let uploaded = 0;
|
|
101
119
|
const batchSize = 10;
|
|
102
120
|
for (let i = 0; i < files.length; i += batchSize) {
|
|
@@ -104,6 +122,7 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
104
122
|
await Promise.all(
|
|
105
123
|
batch.map(async (filePath) => {
|
|
106
124
|
const key = (0, import_node_path.relative)(buildDir, filePath);
|
|
125
|
+
newKeys.add(key);
|
|
107
126
|
const body = (0, import_node_fs.readFileSync)(filePath);
|
|
108
127
|
const contentType = getContentType(filePath);
|
|
109
128
|
const cacheControl = getCacheControl(key);
|
|
@@ -120,7 +139,22 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
120
139
|
})
|
|
121
140
|
);
|
|
122
141
|
}
|
|
123
|
-
|
|
142
|
+
const existingKeys = await listAllKeys(client, bucketName);
|
|
143
|
+
const staleKeys = [...existingKeys].filter((key) => !newKeys.has(key));
|
|
144
|
+
let deleted = 0;
|
|
145
|
+
for (let i = 0; i < staleKeys.length; i += 1e3) {
|
|
146
|
+
const batch = staleKeys.slice(i, i + 1e3);
|
|
147
|
+
await client.send(
|
|
148
|
+
new import_client_s3.DeleteObjectsCommand({
|
|
149
|
+
Bucket: bucketName,
|
|
150
|
+
Delete: {
|
|
151
|
+
Objects: batch.map((key) => ({ Key: key }))
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
deleted += batch.length;
|
|
156
|
+
}
|
|
157
|
+
return { uploaded, deleted };
|
|
124
158
|
}
|
|
125
159
|
async function emptyBucket(client, bucketName) {
|
|
126
160
|
let continuationToken;
|
|
@@ -844,8 +878,8 @@ function aws(awsConfig = {}) {
|
|
|
844
878
|
console.log(`[qlara/aws] Generated ${fallbacks.length} fallback page(s)`);
|
|
845
879
|
console.log("[qlara/aws] Syncing build output to S3...");
|
|
846
880
|
const s3 = createS3Client(res.region);
|
|
847
|
-
const
|
|
848
|
-
console.log(`[qlara/aws] Uploaded ${
|
|
881
|
+
const { uploaded, deleted } = await syncToS3(s3, res.bucketName, buildDir);
|
|
882
|
+
console.log(`[qlara/aws] Uploaded ${uploaded} files, deleted ${deleted} stale files`);
|
|
849
883
|
console.log("[qlara/aws] Bundling edge handler...");
|
|
850
884
|
const edgeZip = await bundleEdgeHandler({
|
|
851
885
|
bucketName: res.bucketName,
|
package/dist/aws.js
CHANGED
|
@@ -92,8 +92,26 @@ function listFiles(dir) {
|
|
|
92
92
|
}
|
|
93
93
|
return files;
|
|
94
94
|
}
|
|
95
|
+
async function listAllKeys(client, bucketName) {
|
|
96
|
+
const keys = /* @__PURE__ */ new Set();
|
|
97
|
+
let continuationToken;
|
|
98
|
+
do {
|
|
99
|
+
const response = await client.send(
|
|
100
|
+
new ListObjectsV2Command({
|
|
101
|
+
Bucket: bucketName,
|
|
102
|
+
ContinuationToken: continuationToken
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
for (const obj of response.Contents || []) {
|
|
106
|
+
if (obj.Key) keys.add(obj.Key);
|
|
107
|
+
}
|
|
108
|
+
continuationToken = response.NextContinuationToken;
|
|
109
|
+
} while (continuationToken);
|
|
110
|
+
return keys;
|
|
111
|
+
}
|
|
95
112
|
async function syncToS3(client, bucketName, buildDir) {
|
|
96
113
|
const files = listFiles(buildDir);
|
|
114
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
97
115
|
let uploaded = 0;
|
|
98
116
|
const batchSize = 10;
|
|
99
117
|
for (let i = 0; i < files.length; i += batchSize) {
|
|
@@ -101,6 +119,7 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
101
119
|
await Promise.all(
|
|
102
120
|
batch.map(async (filePath) => {
|
|
103
121
|
const key = relative(buildDir, filePath);
|
|
122
|
+
newKeys.add(key);
|
|
104
123
|
const body = readFileSync(filePath);
|
|
105
124
|
const contentType = getContentType(filePath);
|
|
106
125
|
const cacheControl = getCacheControl(key);
|
|
@@ -117,7 +136,22 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
117
136
|
})
|
|
118
137
|
);
|
|
119
138
|
}
|
|
120
|
-
|
|
139
|
+
const existingKeys = await listAllKeys(client, bucketName);
|
|
140
|
+
const staleKeys = [...existingKeys].filter((key) => !newKeys.has(key));
|
|
141
|
+
let deleted = 0;
|
|
142
|
+
for (let i = 0; i < staleKeys.length; i += 1e3) {
|
|
143
|
+
const batch = staleKeys.slice(i, i + 1e3);
|
|
144
|
+
await client.send(
|
|
145
|
+
new DeleteObjectsCommand({
|
|
146
|
+
Bucket: bucketName,
|
|
147
|
+
Delete: {
|
|
148
|
+
Objects: batch.map((key) => ({ Key: key }))
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
deleted += batch.length;
|
|
153
|
+
}
|
|
154
|
+
return { uploaded, deleted };
|
|
121
155
|
}
|
|
122
156
|
async function emptyBucket(client, bucketName) {
|
|
123
157
|
let continuationToken;
|
|
@@ -840,8 +874,8 @@ function aws(awsConfig = {}) {
|
|
|
840
874
|
console.log(`[qlara/aws] Generated ${fallbacks.length} fallback page(s)`);
|
|
841
875
|
console.log("[qlara/aws] Syncing build output to S3...");
|
|
842
876
|
const s3 = createS3Client(res.region);
|
|
843
|
-
const
|
|
844
|
-
console.log(`[qlara/aws] Uploaded ${
|
|
877
|
+
const { uploaded, deleted } = await syncToS3(s3, res.bucketName, buildDir);
|
|
878
|
+
console.log(`[qlara/aws] Uploaded ${uploaded} files, deleted ${deleted} stale files`);
|
|
845
879
|
console.log("[qlara/aws] Bundling edge handler...");
|
|
846
880
|
const edgeZip = await bundleEdgeHandler({
|
|
847
881
|
bucketName: res.bucketName,
|
package/dist/cli.js
CHANGED
|
@@ -103,8 +103,26 @@ function listFiles(dir) {
|
|
|
103
103
|
}
|
|
104
104
|
return files;
|
|
105
105
|
}
|
|
106
|
+
async function listAllKeys(client, bucketName) {
|
|
107
|
+
const keys = /* @__PURE__ */ new Set();
|
|
108
|
+
let continuationToken;
|
|
109
|
+
do {
|
|
110
|
+
const response = await client.send(
|
|
111
|
+
new ListObjectsV2Command({
|
|
112
|
+
Bucket: bucketName,
|
|
113
|
+
ContinuationToken: continuationToken
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
for (const obj of response.Contents || []) {
|
|
117
|
+
if (obj.Key) keys.add(obj.Key);
|
|
118
|
+
}
|
|
119
|
+
continuationToken = response.NextContinuationToken;
|
|
120
|
+
} while (continuationToken);
|
|
121
|
+
return keys;
|
|
122
|
+
}
|
|
106
123
|
async function syncToS3(client, bucketName, buildDir) {
|
|
107
124
|
const files = listFiles(buildDir);
|
|
125
|
+
const newKeys = /* @__PURE__ */ new Set();
|
|
108
126
|
let uploaded = 0;
|
|
109
127
|
const batchSize = 10;
|
|
110
128
|
for (let i = 0; i < files.length; i += batchSize) {
|
|
@@ -112,6 +130,7 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
112
130
|
await Promise.all(
|
|
113
131
|
batch.map(async (filePath) => {
|
|
114
132
|
const key = relative(buildDir, filePath);
|
|
133
|
+
newKeys.add(key);
|
|
115
134
|
const body = readFileSync(filePath);
|
|
116
135
|
const contentType = getContentType(filePath);
|
|
117
136
|
const cacheControl = getCacheControl(key);
|
|
@@ -128,7 +147,22 @@ async function syncToS3(client, bucketName, buildDir) {
|
|
|
128
147
|
})
|
|
129
148
|
);
|
|
130
149
|
}
|
|
131
|
-
|
|
150
|
+
const existingKeys = await listAllKeys(client, bucketName);
|
|
151
|
+
const staleKeys = [...existingKeys].filter((key) => !newKeys.has(key));
|
|
152
|
+
let deleted = 0;
|
|
153
|
+
for (let i = 0; i < staleKeys.length; i += 1e3) {
|
|
154
|
+
const batch = staleKeys.slice(i, i + 1e3);
|
|
155
|
+
await client.send(
|
|
156
|
+
new DeleteObjectsCommand({
|
|
157
|
+
Bucket: bucketName,
|
|
158
|
+
Delete: {
|
|
159
|
+
Objects: batch.map((key) => ({ Key: key }))
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
deleted += batch.length;
|
|
164
|
+
}
|
|
165
|
+
return { uploaded, deleted };
|
|
132
166
|
}
|
|
133
167
|
async function emptyBucket(client, bucketName) {
|
|
134
168
|
let continuationToken;
|
|
@@ -848,8 +882,8 @@ function aws(awsConfig = {}) {
|
|
|
848
882
|
console.log(`[qlara/aws] Generated ${fallbacks.length} fallback page(s)`);
|
|
849
883
|
console.log("[qlara/aws] Syncing build output to S3...");
|
|
850
884
|
const s3 = createS3Client(res.region);
|
|
851
|
-
const
|
|
852
|
-
console.log(`[qlara/aws] Uploaded ${
|
|
885
|
+
const { uploaded, deleted } = await syncToS3(s3, res.bucketName, buildDir);
|
|
886
|
+
console.log(`[qlara/aws] Uploaded ${uploaded} files, deleted ${deleted} stale files`);
|
|
853
887
|
console.log("[qlara/aws] Bundling edge handler...");
|
|
854
888
|
const edgeZip = await bundleEdgeHandler({
|
|
855
889
|
bucketName: res.bucketName,
|
package/package.json
CHANGED
|
@@ -276,13 +276,20 @@ export async function handler(
|
|
|
276
276
|
|
|
277
277
|
// At this point, the file does NOT exist in S3 (403 from OAC or 404)
|
|
278
278
|
|
|
279
|
-
// 2.
|
|
279
|
+
// 2. Skip non-HTML file requests — these are never dynamic routes
|
|
280
|
+
// e.g. /product/20.txt (RSC flight data), /product/20.json, etc.
|
|
281
|
+
const nonHtmlExt = uri.match(/\.([a-z0-9]+)$/)?.[1];
|
|
282
|
+
if (nonHtmlExt && nonHtmlExt !== 'html') {
|
|
283
|
+
return response;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 3. Fetch manifest and check if this URL matches a Qlara dynamic route
|
|
280
287
|
const manifest = await getManifest();
|
|
281
288
|
// Strip .html suffix that the URL rewrite function adds before matching
|
|
282
289
|
const cleanUri = uri.replace(/\.html$/, '');
|
|
283
290
|
const match = manifest ? matchRoute(cleanUri, manifest.routes) : null;
|
|
284
291
|
|
|
285
|
-
//
|
|
292
|
+
// 4. If route matches: invoke renderer synchronously to get fully rendered HTML
|
|
286
293
|
if (match) {
|
|
287
294
|
// Try to render with full SEO metadata (synchronous — waits for result)
|
|
288
295
|
const renderedHtml = await invokeRenderer(cleanUri, match);
|
|
@@ -299,6 +306,6 @@ export async function handler(
|
|
|
299
306
|
}
|
|
300
307
|
}
|
|
301
308
|
|
|
302
|
-
//
|
|
309
|
+
// 5. No match or no fallback — return original error
|
|
303
310
|
return response;
|
|
304
311
|
}
|
|
@@ -86,6 +86,7 @@ const FALLBACK_PLACEHOLDER = '__QLARA_FALLBACK__';
|
|
|
86
86
|
function deriveS3Key(uri: string): string {
|
|
87
87
|
const cleanUri = uri.replace(/^\//, '').replace(/\/$/, '');
|
|
88
88
|
if (!cleanUri) return 'index.html';
|
|
89
|
+
if (cleanUri.endsWith('.html')) return cleanUri;
|
|
89
90
|
return `${cleanUri}.html`;
|
|
90
91
|
}
|
|
91
92
|
|