ai-localize-aws-cloudfront 2.0.3 → 2.0.5

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/package.json CHANGED
@@ -1,10 +1,15 @@
1
1
  {
2
2
  "name": "ai-localize-aws-cloudfront",
3
- "version": "2.0.3",
4
- "description": "S3 upload, CloudFront invalidation, CDN URL generation",
3
+ "version": "2.0.5",
4
+ "description": "S3 upload, CloudFront invalidation and CDN URL migration for ai-localize-core",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "CHANGELOG.md"
12
+ ],
8
13
  "exports": {
9
14
  ".": {
10
15
  "types": "./dist/index.d.ts",
@@ -12,13 +17,28 @@
12
17
  "require": "./dist/index.js"
13
18
  }
14
19
  },
20
+ "keywords": [
21
+ "i18n",
22
+ "localization",
23
+ "l10n",
24
+ "internationalization",
25
+ "ai-localize",
26
+ "aws",
27
+ "s3",
28
+ "cloudfront",
29
+ "cdn",
30
+ "migration"
31
+ ],
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
15
35
  "dependencies": {
16
36
  "@aws-sdk/client-s3": "^3.511.0",
17
37
  "@aws-sdk/client-cloudfront": "^3.511.0",
18
38
  "@aws-sdk/lib-storage": "^3.511.0",
19
39
  "mime-types": "^2.1.35",
20
- "ai-localize-shared": "2.0.3",
21
- "ai-localize-codemods": "2.0.3"
40
+ "ai-localize-shared": "2.0.5",
41
+ "ai-localize-codemods": "2.0.5"
22
42
  },
23
43
  "devDependencies": {
24
44
  "@types/mime-types": "^2.1.4",
@@ -1,81 +0,0 @@
1
- import * as path from "path";
2
- import type { AwsConfig, CloudFrontAsset, LegacyCdnUrl } from "ai-localize-shared";
3
- import { S3Uploader } from "./s3-uploader.js";
4
- import { CloudFrontInvalidator } from "./cloudfront-invalidator.js";
5
-
6
- export interface MigrationOptions {
7
- sourceDir: string;
8
- assetsDir: string;
9
- legacyCdnUrls: LegacyCdnUrl[];
10
- dryRun?: boolean;
11
- invalidateCache?: boolean;
12
- onProgress?: (step: string) => void;
13
- }
14
-
15
- export interface MigrationResult {
16
- uploadedAssets: CloudFrontAsset[];
17
- replacedUrls: number;
18
- invalidationId?: string;
19
- duration: number;
20
- }
21
-
22
- /**
23
- * Orchestrates the full CDN migration:
24
- * 1. Upload assets to S3
25
- * 2. Replace legacy CDN URLs in source files
26
- * 3. Invalidate CloudFront cache
27
- */
28
- export class CdnMigrator {
29
- private config: AwsConfig;
30
-
31
- constructor(config: AwsConfig) {
32
- this.config = config;
33
- }
34
-
35
- async migrate(options: MigrationOptions): Promise<MigrationResult> {
36
- const startTime = Date.now();
37
- const { dryRun = false, onProgress } = options;
38
-
39
- onProgress?.("Uploading assets to S3...");
40
- let uploadedAssets: CloudFrontAsset[] = [];
41
-
42
- if (!dryRun) {
43
- const uploader = new S3Uploader(this.config);
44
- const uploadResult = await uploader.uploadDirectory({
45
- assetsDir: options.assetsDir,
46
- onProgress: (asset, done, total) => onProgress?.(`Uploading ${done}/${total}: ${path.basename(asset)}`),
47
- });
48
- uploadedAssets = uploadResult.uploaded;
49
- }
50
-
51
- onProgress?.("Replacing legacy CDN URLs...");
52
- let replacedUrls = 0;
53
-
54
- if (!dryRun && uploadedAssets.length > 0) {
55
- const { batchReplaceCdnUrls } = await import("ai-localize-codemods");
56
- const fileUrlMap = new Map<string, LegacyCdnUrl[]>();
57
- for (const url of options.legacyCdnUrls) {
58
- const existing = fileUrlMap.get(url.filePath) || [];
59
- existing.push(url);
60
- fileUrlMap.set(url.filePath, existing);
61
- }
62
- const results = await batchReplaceCdnUrls(fileUrlMap, uploadedAssets, dryRun);
63
- replacedUrls = results.reduce((sum, r) => sum + r.replacedCount, 0);
64
- }
65
-
66
- let invalidationId: string | undefined;
67
- if (!dryRun && options.invalidateCache && uploadedAssets.length > 0) {
68
- onProgress?.("Invalidating CloudFront cache...");
69
- const invalidator = new CloudFrontInvalidator(this.config);
70
- const inv = await invalidator.invalidate(["/*"]);
71
- invalidationId = inv.invalidationId;
72
- }
73
-
74
- return {
75
- uploadedAssets,
76
- replacedUrls,
77
- invalidationId,
78
- duration: Date.now() - startTime,
79
- };
80
- }
81
- }
@@ -1,37 +0,0 @@
1
- import { CloudFrontClient, CreateInvalidationCommand } from "@aws-sdk/client-cloudfront";
2
- import type { AwsConfig } from "ai-localize-shared";
3
-
4
- export interface InvalidationResult {
5
- invalidationId: string;
6
- paths: string[];
7
- status: string;
8
- }
9
-
10
- export class CloudFrontInvalidator {
11
- private client: CloudFrontClient;
12
- private distributionId: string;
13
-
14
- constructor(config: AwsConfig) {
15
- this.distributionId = config.distributionId;
16
- this.client = new CloudFrontClient({ region: config.region || "us-east-1" });
17
- }
18
-
19
- async invalidate(paths: string[] = ["/*"]): Promise<InvalidationResult> {
20
- const callerReference = `ai-localize-${Date.now()}`;
21
- const response = await this.client.send(
22
- new CreateInvalidationCommand({
23
- DistributionId: this.distributionId,
24
- InvalidationBatch: {
25
- CallerReference: callerReference,
26
- Paths: { Quantity: paths.length, Items: paths },
27
- },
28
- })
29
- );
30
-
31
- return {
32
- invalidationId: response.Invalidation?.Id || "",
33
- paths,
34
- status: response.Invalidation?.Status || "InProgress",
35
- };
36
- }
37
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from "./s3-uploader.js";
2
- export * from "./cloudfront-invalidator.js";
3
- export * from "./cdn-migrator.js";
@@ -1,110 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import * as crypto from "crypto";
4
- import { S3Client, PutObjectCommand, HeadObjectCommand } from "@aws-sdk/client-s3";
5
- import * as mime from "mime-types";
6
-
7
- import type { AwsConfig, CloudFrontAsset } from "ai-localize-shared";
8
- import { collectFiles, ASSET_EXTENSIONS } from "ai-localize-shared";
9
-
10
- export interface UploadOptions {
11
- assetsDir: string;
12
- force?: boolean;
13
- onProgress?: (asset: string, done: number, total: number) => void;
14
- }
15
-
16
- export interface UploadResult {
17
- uploaded: CloudFrontAsset[];
18
- skipped: string[];
19
- failed: string[];
20
- duration: number;
21
- }
22
-
23
- /**
24
- * Uploads static assets to S3 with content-hash in filename for cache-busting.
25
- */
26
- export class S3Uploader {
27
- private client: S3Client;
28
- private config: AwsConfig;
29
-
30
- constructor(config: AwsConfig) {
31
- this.config = config;
32
- this.client = new S3Client({
33
- region: config.region || "us-east-1",
34
- ...(config.profile ? { credentials: { accessKeyId: "", secretAccessKey: "" } } : {}),
35
- });
36
- }
37
-
38
- async uploadDirectory(options: UploadOptions): Promise<UploadResult> {
39
- const startTime = Date.now();
40
- const assetFiles = collectFiles(options.assetsDir, ASSET_EXTENSIONS, ["node_modules"]);
41
- const uploaded: CloudFrontAsset[] = [];
42
- const skipped: string[] = [];
43
- const failed: string[] = [];
44
- let done = 0;
45
-
46
- for (const filePath of assetFiles) {
47
- try {
48
- const asset = await this.uploadFile(filePath, options.assetsDir, options.force);
49
- if (asset) {
50
- uploaded.push(asset);
51
- } else {
52
- skipped.push(filePath);
53
- }
54
- } catch (err) {
55
- failed.push(filePath);
56
- console.error(`Failed to upload ${filePath}:`, err);
57
- }
58
- done++;
59
- options.onProgress?.(filePath, done, assetFiles.length);
60
- }
61
-
62
- return { uploaded, skipped, failed, duration: Date.now() - startTime };
63
- }
64
-
65
- async uploadFile(filePath: string, baseDir: string, force = false): Promise<CloudFrontAsset | null> {
66
- const content = fs.readFileSync(filePath);
67
- const hash = crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
68
- const ext = path.extname(filePath);
69
- const basename = path.basename(filePath, ext);
70
- const hashedName = `${basename}.${hash}${ext}`;
71
- const relativePath = path.relative(baseDir, filePath);
72
- const s3Key = path.join(this.config.assetsPrefix || "assets", path.dirname(relativePath), hashedName).replace(/\\/g, "/");
73
-
74
- // Check if already uploaded
75
- if (!force) {
76
- const exists = await this.objectExists(s3Key);
77
- if (exists) return null;
78
- }
79
-
80
- const contentType = mime.lookup(filePath) || "application/octet-stream";
81
- await this.client.send(
82
- new PutObjectCommand({
83
- Bucket: this.config.bucket,
84
- Key: s3Key,
85
- Body: content,
86
- ContentType: contentType,
87
- CacheControl: "public, max-age=31536000, immutable",
88
- })
89
- );
90
-
91
- const cdnBase = this.config.cdnBaseUrl || `https://${this.config.distributionId}.cloudfront.net`;
92
- return {
93
- localPath: filePath,
94
- s3Key,
95
- hash,
96
- cloudfrontUrl: `${cdnBase}/${s3Key}`,
97
- contentType,
98
- size: content.length,
99
- };
100
- }
101
-
102
- private async objectExists(key: string): Promise<boolean> {
103
- try {
104
- await this.client.send(new HeadObjectCommand({ Bucket: this.config.bucket, Key: key }));
105
- return true;
106
- } catch {
107
- return false;
108
- }
109
- }
110
- }
package/tsconfig.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.base.json",
3
- "compilerOptions": { "outDir": "./dist", "rootDir": "./src" },
4
- "include": ["src/**/*"],
5
- "exclude": ["node_modules", "dist"]
6
- }