@pixelated-tech/components 3.7.6 → 3.7.7

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.
@@ -1,15 +1,48 @@
1
+ import { decrypt, isEncrypted } from './crypto';
2
+ import fs from 'fs';
3
+ import path from 'path';
1
4
  const debug = false;
2
5
  /**
3
- * Read the full master config blob from environment.
6
+ * Read the full master config blob from environment or local file.
4
7
  * This function is intended for server-side use only.
5
8
  */
6
9
  export function getFullPixelatedConfig() {
7
- const raw = process.env.PIXELATED_CONFIG_JSON || (process.env.PIXELATED_CONFIG_B64 && Buffer.from(process.env.PIXELATED_CONFIG_B64, 'base64').toString('utf8'));
10
+ let raw = process.env.PIXELATED_CONFIG_JSON || (process.env.PIXELATED_CONFIG_B64 && Buffer.from(process.env.PIXELATED_CONFIG_B64, 'base64').toString('utf8'));
11
+ let source = process.env.PIXELATED_CONFIG_JSON ? 'PIXELATED_CONFIG_JSON' : (process.env.PIXELATED_CONFIG_B64 ? 'PIXELATED_CONFIG_B64' : 'none');
12
+ // If not in environment, try reading from the conventional file location
8
13
  if (!raw) {
9
- console.error('PIXELATED_CONFIG not found: neither PIXELATED_CONFIG_JSON nor PIXELATED_CONFIG_B64 is set in the environment.');
14
+ const configPath = path.join(process.cwd(), 'src/app/config/pixelated.config.json');
15
+ if (fs.existsSync(configPath)) {
16
+ try {
17
+ raw = fs.readFileSync(configPath, 'utf8');
18
+ source = 'src/app/config/pixelated.config.json';
19
+ }
20
+ catch (err) {
21
+ console.error(`Failed to read config file at ${configPath}`, err);
22
+ }
23
+ }
24
+ }
25
+ if (!raw) {
26
+ console.error('PIXELATED_CONFIG not found: neither environment variables nor src/app/config/pixelated.config.json are available.');
10
27
  return {};
11
28
  }
12
- const source = process.env.PIXELATED_CONFIG_JSON ? 'PIXELATED_CONFIG_JSON' : 'PIXELATED_CONFIG_B64';
29
+ // Handle decryption if the content is encrypted
30
+ if (isEncrypted(raw)) {
31
+ const key = process.env.PIXELATED_CONFIG_KEY;
32
+ if (!key) {
33
+ console.error('PIXELATED_CONFIG is encrypted but PIXELATED_CONFIG_KEY is not set in the environment.');
34
+ return {};
35
+ }
36
+ try {
37
+ raw = decrypt(raw, key);
38
+ if (debug)
39
+ console.log(`PIXELATED_CONFIG decrypted using key from environment.`);
40
+ }
41
+ catch (err) {
42
+ console.error('Failed to decrypt PIXELATED_CONFIG', err);
43
+ return {};
44
+ }
45
+ }
13
46
  try {
14
47
  const parsed = JSON.parse(raw);
15
48
  if (debug)
@@ -1,3 +1,4 @@
1
+ "use server";
1
2
  import { jsx as _jsx } from "react/jsx-runtime";
2
3
  import PropTypes from 'prop-types';
3
4
  import { getClientOnlyPixelatedConfig } from './config';
@@ -0,0 +1,62 @@
1
+ import crypto from 'crypto';
2
+ /**
3
+ * AES-256-GCM encryption/decryption utility.
4
+ * Requires a 32-byte key (64 hex characters).
5
+ */
6
+ const ALGORITHM = 'aes-256-gcm';
7
+ const IV_LENGTH = 12; // GCM recommended IV length
8
+ const AUTH_TAG_LENGTH = 16;
9
+ const ENCRYPTED_PREFIX = 'pxl:v1:';
10
+ /**
11
+ * Encrypts a string using a hex-encoded 32-byte key.
12
+ * Returns a prefixed string: pxl:v1:iv:authTag:encryptedContent (all hex).
13
+ */
14
+ export function encrypt(text, keyHex) {
15
+ if (!keyHex)
16
+ throw new Error('Encryption key is required.');
17
+ const key = Buffer.from(keyHex, 'hex');
18
+ if (key.length !== 32) {
19
+ throw new Error('Encryption key must be 32 bytes (64 hex characters).');
20
+ }
21
+ const iv = crypto.randomBytes(IV_LENGTH);
22
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
23
+ let encrypted = cipher.update(text, 'utf8', 'hex');
24
+ encrypted += cipher.final('hex');
25
+ const authTag = cipher.getAuthTag().toString('hex');
26
+ return `${ENCRYPTED_PREFIX}${iv.toString('hex')}:${authTag}:${encrypted}`;
27
+ }
28
+ /**
29
+ * Decrypts a string using a hex-encoded 32-byte key.
30
+ * Expects format: pxl:v1:iv:authTag:encryptedContent
31
+ */
32
+ export function decrypt(payload, keyHex) {
33
+ if (!keyHex)
34
+ throw new Error('Decryption key is required.');
35
+ if (!payload.startsWith(ENCRYPTED_PREFIX)) {
36
+ throw new Error('Payload is not in a recognized encrypted format.');
37
+ }
38
+ const data = payload.slice(ENCRYPTED_PREFIX.length);
39
+ const key = Buffer.from(keyHex, 'hex');
40
+ if (key.length !== 32) {
41
+ throw new Error('Decryption key must be 32 bytes (64 hex characters).');
42
+ }
43
+ const parts = data.split(':');
44
+ if (parts.length !== 3) {
45
+ throw new Error('Invalid encrypted data format. Expected iv:authTag:encryptedContent');
46
+ }
47
+ const [ivHex, authTagHex, encryptedHex] = parts;
48
+ const iv = Buffer.from(ivHex, 'hex');
49
+ const authTag = Buffer.from(authTagHex, 'hex');
50
+ const encryptedText = Buffer.from(encryptedHex, 'hex');
51
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
52
+ decipher.setAuthTag(authTag);
53
+ let decrypted = decipher.update(encryptedText, 'hex', 'utf8');
54
+ decrypted += decipher.final('utf8');
55
+ return decrypted;
56
+ }
57
+ /**
58
+ * Checks if a string is encrypted using our format.
59
+ */
60
+ export function isEncrypted(text) {
61
+ return typeof text === 'string' && text.startsWith(ENCRYPTED_PREFIX);
62
+ }
@@ -200,6 +200,7 @@ export const SERVER_ONLY_PATTERNS = [
200
200
  /\bfs\b/,
201
201
  /\bfs\.readFileSync\b/,
202
202
  /\bfs\.existsSync\b/,
203
+ /\bcrypto\b/,
203
204
  /\bimport.*googleapis\b|\brequire.*googleapis\b/, // Actual import of googleapis
204
205
  /\bimport.*next\/server\b|\brequire.*next\/server\b/, // Actual import of next/server
205
206
  /\bimport.*path\b|\brequire.*path\b/, // Actual import of path module
package/dist/index.js CHANGED
@@ -1,7 +1,5 @@
1
1
  // sorted alphabetically and grouped by folder for easier reading
2
- export * from './components/config/config';
3
2
  export * from './components/config/config.client';
4
- export * from './components/config/config.server';
5
3
  export * from './components/config/config.types';
6
4
  export * from './components/general/404';
7
5
  export * from './components/general/accordion';
@@ -8,6 +8,7 @@ export * from './components/admin/sites/sites.integration';
8
8
  export * from './components/config/config';
9
9
  export * from './components/config/config.server';
10
10
  export * from './components/config/config.types';
11
+ export * from './components/config/crypto';
11
12
  // SEO
12
13
  export * from './components/general/contentful.delivery';
13
14
  export * from './components/general/contentful.management';
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env npx tsx
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { encrypt, decrypt, isEncrypted } from '../components/config/crypto';
5
+ /**
6
+ * CLI Tool for encrypting/decrypting pixelated.config.json
7
+ * Usage:
8
+ * npx tsx src/scripts/config-vault.js encrypt <filePath> <key>
9
+ * npx tsx src/scripts/config-vault.js decrypt <filePath> <key>
10
+ */
11
+ const [, , command, targetPath, key] = process.argv;
12
+ if (!command || !targetPath || !key) {
13
+ console.log('Usage:');
14
+ console.log(' encrypt <filePath> <key> - Encrypts the file in place');
15
+ console.log(' decrypt <filePath> <key> - Decrypts the file in place');
16
+ process.exit(1);
17
+ }
18
+ const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(process.cwd(), targetPath);
19
+ if (!fs.existsSync(fullPath)) {
20
+ console.error(`File not found: ${fullPath}`);
21
+ process.exit(1);
22
+ }
23
+ const content = fs.readFileSync(fullPath, 'utf8');
24
+ try {
25
+ if (command === 'encrypt') {
26
+ if (isEncrypted(content)) {
27
+ console.log('File is already encrypted.');
28
+ process.exit(0);
29
+ }
30
+ const encrypted = encrypt(content, key);
31
+ fs.writeFileSync(fullPath, encrypted, 'utf8');
32
+ console.log(`Successfully encrypted ${targetPath}`);
33
+ }
34
+ else if (command === 'decrypt') {
35
+ if (!isEncrypted(content)) {
36
+ console.log('File is not encrypted.');
37
+ process.exit(0);
38
+ }
39
+ const decrypted = decrypt(content, key);
40
+ fs.writeFileSync(fullPath, decrypted, 'utf8');
41
+ console.log(`Successfully decrypted ${targetPath}`);
42
+ }
43
+ else {
44
+ console.error(`Unknown command: ${command}`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ catch (err) {
49
+ console.error(`Operation failed: ${err.message}`);
50
+ process.exit(1);
51
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env npx tsx
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { encrypt, decrypt, isEncrypted } from '../components/config/crypto';
5
+
6
+ /**
7
+ * CLI Tool for encrypting/decrypting pixelated.config.json
8
+ * Usage:
9
+ * npx tsx src/scripts/config-vault.js encrypt <filePath> <key>
10
+ * npx tsx src/scripts/config-vault.js decrypt <filePath> <key>
11
+ */
12
+
13
+ const [,, command, targetPath, key] = process.argv;
14
+
15
+ if (!command || !targetPath || !key) {
16
+ console.log('Usage:');
17
+ console.log(' encrypt <filePath> <key> - Encrypts the file in place');
18
+ console.log(' decrypt <filePath> <key> - Decrypts the file in place');
19
+ process.exit(1);
20
+ }
21
+
22
+ const fullPath = path.isAbsolute(targetPath) ? targetPath : path.resolve(process.cwd(), targetPath);
23
+
24
+ if (!fs.existsSync(fullPath)) {
25
+ console.error(`File not found: ${fullPath}`);
26
+ process.exit(1);
27
+ }
28
+
29
+ const content = fs.readFileSync(fullPath, 'utf8');
30
+
31
+ try {
32
+ if (command === 'encrypt') {
33
+ if (isEncrypted(content)) {
34
+ console.log('File is already encrypted.');
35
+ process.exit(0);
36
+ }
37
+ const encrypted = encrypt(content, key);
38
+ fs.writeFileSync(fullPath, encrypted, 'utf8');
39
+ console.log(`Successfully encrypted ${targetPath}`);
40
+ } else if (command === 'decrypt') {
41
+ if (!isEncrypted(content)) {
42
+ console.log('File is not encrypted.');
43
+ process.exit(0);
44
+ }
45
+ const decrypted = decrypt(content, key);
46
+ fs.writeFileSync(fullPath, decrypted, 'utf8');
47
+ console.log(`Successfully decrypted ${targetPath}`);
48
+ } else {
49
+ console.error(`Unknown command: ${command}`);
50
+ process.exit(1);
51
+ }
52
+ } catch (err: any) {
53
+ console.error(`Operation failed: ${err.message}`);
54
+ process.exit(1);
55
+ }
@@ -103,7 +103,14 @@ if [ "$current_branch" != "dev" ]; then
103
103
  fi
104
104
 
105
105
  echo "đŸ“Ļ Step 1: Updating dependencies..."
106
- npm outdated | awk 'NR>1 {print $1"@"$4}' | xargs npm install --force --save 2>/dev/null || true
106
+ UPDATES=$(npm outdated | awk 'NR>1 {print $1"@"$4}' || true)
107
+ if [ -n "$UPDATES" ]; then
108
+ echo "Updating the following packages: $UPDATES"
109
+ echo "$UPDATES" | xargs npm install --force --save 2>/dev/null || true
110
+ echo "✅ Successfully updated: $UPDATES"
111
+ else
112
+ echo "✅ No dependency updates needed."
113
+ fi
107
114
  npm audit fix --force 2>/dev/null || true
108
115
 
109
116
  echo "🔍 Step 2: Running lint..."
@@ -124,6 +131,10 @@ if [ "$version_type" != "none" ]; then
124
131
  fi
125
132
 
126
133
  echo "💾 Step 5: Committing changes..."
134
+ if npm run | grep -q "config:encrypt"; then
135
+ echo "🔒 Encrypting configuration..."
136
+ npm run config:encrypt
137
+ fi
127
138
  commit_message=$(prompt_commit_message)
128
139
  git add . -v
129
140
  if git diff --cached --quiet; then
@@ -171,6 +182,11 @@ else
171
182
  echo "â„šī¸ Tag v$new_version already exists"
172
183
  fi
173
184
 
185
+ if npm run | grep -q "config:decrypt"; then
186
+ echo "🔓 Decrypting configuration for local development..."
187
+ npm run config:decrypt
188
+ fi
189
+
174
190
  echo "🔐 Step 9: Publishing to npm..."
175
191
  should_publish=$(prompt_publish)
176
192
  if [ "$should_publish" = "yes" ]; then
@@ -1,6 +1,6 @@
1
1
  import type { PixelatedConfig } from './config.types';
2
2
  /**
3
- * Read the full master config blob from environment.
3
+ * Read the full master config blob from environment or local file.
4
4
  * This function is intended for server-side use only.
5
5
  */
6
6
  export declare function getFullPixelatedConfig(): PixelatedConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/components/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CAexD;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,eAAe,CAwBpF"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/components/config/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAMtD;;;GAGG;AACH,wBAAgB,sBAAsB,IAAI,eAAe,CA8CxD;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,CAAC,EAAE,eAAe,GAAG,eAAe,CAwBpF"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.server.d.ts","sourceRoot":"","sources":["../../../../src/components/config/config.server.tsx"],"names":[],"mappings":"AACA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAUnD,MAAM,MAAM,iCAAiC,GAAG,UAAU,CAAC,OAAO,6BAA6B,CAAC,SAAS,CAAC,CAAC;AAC3G,wBAAsB,6BAA6B,CAAC,KAAK,EAAE,iCAAiC,oDAM3F;yBANqB,6BAA6B"}
1
+ {"version":3,"file":"config.server.d.ts","sourceRoot":"","sources":["../../../../src/components/config/config.server.tsx"],"names":[],"mappings":"AAGA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAUnD,MAAM,MAAM,iCAAiC,GAAG,UAAU,CAAC,OAAO,6BAA6B,CAAC,SAAS,CAAC,CAAC;AAC3G,wBAAsB,6BAA6B,CAAC,KAAK,EAAE,iCAAiC,oDAM3F;yBANqB,6BAA6B"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Encrypts a string using a hex-encoded 32-byte key.
3
+ * Returns a prefixed string: pxl:v1:iv:authTag:encryptedContent (all hex).
4
+ */
5
+ export declare function encrypt(text: string, keyHex: string): string;
6
+ /**
7
+ * Decrypts a string using a hex-encoded 32-byte key.
8
+ * Expects format: pxl:v1:iv:authTag:encryptedContent
9
+ */
10
+ export declare function decrypt(payload: string, keyHex: string): string;
11
+ /**
12
+ * Checks if a string is encrypted using our format.
13
+ */
14
+ export declare function isEncrypted(text: string): boolean;
15
+ //# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../../../src/components/config/crypto.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAgB5D;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6B/D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../../src/components/utilities/functions.ts"],"names":[],"mappings":"AAGA,wBAAgB,QAAQ,CAAE,GAAG,EAAE,MAAM,oBAUpC;AAGD,wBAAgB,SAAS,CAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG;;EAmBxC;AAED,wBAAgB,wBAAwB,CAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,QAIhF;AAED,wBAAgB,aAAa,CAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAOtD;AAED,wBAAgB,WAAW,WAc1B;AAED,wBAAgB,YAAY,WAK3B;AAED,wBAAgB,UAAU,CAAE,GAAG,EAAE,MAAM,UAEtC;AAQD,wBAAgB,YAAY,CAAE,YAAY,EAAE,MAAM,UAgCjD;AAID;;;;OAII;AACJ,wBAAgB,YAAY,SAoB3B;AAKD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,UA0BhC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE9D;AAGD;;GAEG;AACH,eAAO,MAAM,uBAAuB,UAQnC,CAAC;AAEF,eAAO,MAAM,wBAAwB,UAOpC,CAAC;AAGF;;GAEG;AACH,eAAO,MAAM,oBAAoB,UAiBhC,CAAC"}
1
+ {"version":3,"file":"functions.d.ts","sourceRoot":"","sources":["../../../../src/components/utilities/functions.ts"],"names":[],"mappings":"AAGA,wBAAgB,QAAQ,CAAE,GAAG,EAAE,MAAM,oBAUpC;AAGD,wBAAgB,SAAS,CAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG;;EAmBxC;AAED,wBAAgB,wBAAwB,CAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,QAIhF;AAED,wBAAgB,aAAa,CAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,UAOtD;AAED,wBAAgB,WAAW,WAc1B;AAED,wBAAgB,YAAY,WAK3B;AAED,wBAAgB,UAAU,CAAE,GAAG,EAAE,MAAM,UAEtC;AAQD,wBAAgB,YAAY,CAAE,YAAY,EAAE,MAAM,UAgCjD;AAID;;;;OAII;AACJ,wBAAgB,YAAY,SAoB3B;AAKD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,UA0BhC,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAE9D;AAGD;;GAEG;AACH,eAAO,MAAM,uBAAuB,UAQnC,CAAC;AAEF,eAAO,MAAM,wBAAwB,UAOpC,CAAC;AAGF;;GAEG;AACH,eAAO,MAAM,oBAAoB,UAkBhC,CAAC"}
@@ -1,6 +1,4 @@
1
- export * from "./components/config/config";
2
1
  export * from "./components/config/config.client";
3
- export * from "./components/config/config.server";
4
2
  export * from "./components/config/config.types";
5
3
  export * from "./components/general/404";
6
4
  export * from "./components/general/accordion";
@@ -2,6 +2,7 @@ export * from "./components/admin/sites/sites.integration";
2
2
  export * from "./components/config/config";
3
3
  export * from "./components/config/config.server";
4
4
  export * from "./components/config/config.types";
5
+ export * from "./components/config/crypto";
5
6
  export * from "./components/general/contentful.delivery";
6
7
  export * from "./components/general/contentful.management";
7
8
  export * from "./components/general/flickr";
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env npx tsx
2
+ export {};
3
+ //# sourceMappingURL=config-vault.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-vault.d.ts","sourceRoot":"","sources":["../../../src/scripts/config-vault.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixelated-tech/components",
3
- "version": "3.7.6",
3
+ "version": "3.7.7",
4
4
  "private": false,
5
5
  "author": {
6
6
  "name": "Pixelated Technologies",
@@ -85,7 +85,8 @@
85
85
  "test:coverage": "vitest run --coverage",
86
86
  "test:run": "vitest run",
87
87
  "lint": "eslint --fix",
88
- "release": "./src/scripts/release.sh"
88
+ "release": "./src/scripts/release.sh",
89
+ "vault": "tsx src/scripts/config-vault.ts"
89
90
  },
90
91
  "scripts-old": {
91
92
  "buildBabel": "npm run buildClean && NODE_ENV=production babel src --out-dir dist --copy-files",