Skip to content

Commit 1f76f9f

Browse files
aniketaniket
authored andcommitted
fix: sanitize invalid export aliases in plays/index.js before build
The create-react-play tool generates export aliases from play names, which can produce invalid JS identifiers (e.g. Bmr-TdeeCalculator). This adds a sanitize step that runs after create-react-play to convert any invalid aliases to valid PascalCase identifiers, preventing the SyntaxError that caused Netlify deploy failures.
1 parent afb40d0 commit 1f76f9f

2 files changed

Lines changed: 89 additions & 3 deletions

File tree

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,9 @@
8686
},
8787
"scripts": {
8888
"dev": "react-scripts start",
89-
"start:nolint": "npx --yes create-react-play@latest -p && react-scripts start",
90-
"start": "npx --yes create-react-play@latest -p && npm run lint && react-scripts start",
91-
"build": "npx --yes create-react-play@latest -p && react-scripts build",
89+
"start:nolint": "npx --yes create-react-play@latest -p && node scripts/sanitize-play-exports.cjs && react-scripts start",
90+
"start": "npx --yes create-react-play@latest -p && node scripts/sanitize-play-exports.cjs && npm run lint && react-scripts start",
91+
"build": "npx --yes create-react-play@latest -p && node scripts/sanitize-play-exports.cjs && react-scripts build",
9292
"snap": "react-snap",
9393
"test": "react-scripts test",
9494
"eject": "react-scripts eject",

scripts/sanitize-play-exports.cjs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
const indexPath = path.join(process.cwd(), 'src', 'plays', 'index.js');
8+
9+
if (!fs.existsSync(indexPath)) {
10+
console.warn(`[sanitize-play-exports] Skipped: file not found at ${indexPath}`);
11+
process.exit(0);
12+
}
13+
14+
const source = fs.readFileSync(indexPath, 'utf8');
15+
const newline = source.includes('\r\n') ? '\r\n' : '\n';
16+
const hasTrailingNewline = source.endsWith('\n');
17+
const lines = source.split(/\r?\n/);
18+
19+
const exportLinePattern = /^(\s*export\s*\{\s*default\s+as\s+)([^}]+?)(\s*\}\s*from\s*['"][^'"]+['"]\s*;?\s*)$/;
20+
const isValidIdentifier = (value) => /^[$A-Z_a-z][$0-9A-Z_a-z]*$/.test(value);
21+
22+
const toPascalCase = (value) => {
23+
const chunks = value
24+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
25+
.split(/[^0-9A-Z_a-z$]+/)
26+
.filter(Boolean);
27+
28+
let identifier = chunks
29+
.map((chunk) => chunk.charAt(0).toUpperCase() + chunk.slice(1))
30+
.join('');
31+
32+
if (identifier.length === 0) {
33+
identifier = 'Play';
34+
}
35+
36+
if (!/^[$A-Z_a-z]/.test(identifier)) {
37+
identifier = `Play${identifier}`;
38+
}
39+
40+
return identifier;
41+
};
42+
43+
const usedAliases = new Set();
44+
let updateCount = 0;
45+
46+
const nextLines = lines.map((line) => {
47+
const match = line.match(exportLinePattern);
48+
if (match == null) {
49+
return line;
50+
}
51+
52+
const [, prefix, rawAlias, suffix] = match;
53+
const currentAlias = rawAlias.trim();
54+
let nextAlias = currentAlias;
55+
56+
if (!isValidIdentifier(currentAlias)) {
57+
nextAlias = toPascalCase(currentAlias);
58+
}
59+
60+
const aliasBase = nextAlias;
61+
let duplicateIndex = 2;
62+
while (usedAliases.has(nextAlias)) {
63+
nextAlias = `${aliasBase}${duplicateIndex}`;
64+
duplicateIndex += 1;
65+
}
66+
usedAliases.add(nextAlias);
67+
68+
if (nextAlias !== currentAlias) {
69+
updateCount += 1;
70+
return `${prefix}${nextAlias}${suffix}`;
71+
}
72+
73+
return line;
74+
});
75+
76+
let nextSource = nextLines.join(newline);
77+
if (hasTrailingNewline) {
78+
nextSource += newline;
79+
}
80+
81+
if (nextSource !== source) {
82+
fs.writeFileSync(indexPath, nextSource, 'utf8');
83+
console.log(`[sanitize-play-exports] Updated ${updateCount} export alias(es).`);
84+
} else {
85+
console.log('[sanitize-play-exports] No invalid aliases found.');
86+
}

0 commit comments

Comments
 (0)