at-builder 1.4.1 → 1.4.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/.claude/settings.local.json +8 -1
- package/bin/constants/config.js +1 -1
- package/bin/services/doctor.js +1 -1
- package/lib/CustomWrapperPlugin.js +18 -1
- package/lib/eslint-flat-config-plugin.js +125 -13
- package/package.json +2 -1
- package/src/constants/config.ts +7 -0
- package/src/services/doctor.ts +7 -0
- package/webpack.config.js +11 -2
|
@@ -64,7 +64,14 @@
|
|
|
64
64
|
"Bash(npm view *)",
|
|
65
65
|
"Bash(git tag *)",
|
|
66
66
|
"Bash(git push *)",
|
|
67
|
-
"Bash(npm publish *)"
|
|
67
|
+
"Bash(npm publish *)",
|
|
68
|
+
"Bash(git commit -m 'feat\\(eslint\\): honor consumer .eslintrc.* via FlatCompat *)",
|
|
69
|
+
"Bash(atb build *)",
|
|
70
|
+
"Bash(ACTIVITY_FOLDER_NAME=\"UPSDDO - 7313 - AB-26.3.1 - Left Hand Navigation With Flyout - Dashboard - EWS - FWS - Tracking - Support Portal - Billing - Pickup - Claims\" atb build --prod)",
|
|
71
|
+
"Bash(grep -oE \"\\\\\\([a-z-]+\\\\\\)$\")",
|
|
72
|
+
"Bash(awk '/^ERROR in/{flag=1; next} /^WARNING in/{flag=0; next} flag')",
|
|
73
|
+
"Bash(git commit -m 'fix\\(eslint\\): honor consumer ESLint config as sole authority + 2 FlatCompat bugs *)",
|
|
74
|
+
"Bash(git commit -m 'feat\\(wrapper\\): configurable TARGET_BUILD_PREFIX + per-asset contentHash in window flag *)"
|
|
68
75
|
]
|
|
69
76
|
}
|
|
70
77
|
}
|
package/bin/constants/config.js
CHANGED
|
@@ -119,7 +119,7 @@ var setupEnv = function (basePath) { return __awaiter(void 0, void 0, void 0, fu
|
|
|
119
119
|
envPath = path_1.default.join(basePath, '.env');
|
|
120
120
|
// Check if the .env file exists
|
|
121
121
|
if (!fs_1.default.existsSync(envPath)) {
|
|
122
|
-
envContent = "\nACTIVITIES_BASE_FOLDER=\"Activities\"\nACTIVITY_FOLDER_NAME=\"\"\nPUPPETEER_LANDING_PAGE=\"\"\nTARGET_URL=\"\"\nLOGIN_URL=\"\"\n\n# Dev-server selection (used by `atb dev --browser`).\n# Edit and save while puppeteer is running to hot-swap the previewed bundle.\n# PAGE is only meaningful for multi-page activities \u2014 leave empty otherwise.\nVARIATION=\"Variation-1\"\nPAGE=\"\"\n\nNODE_ENV=\"development\"\nVERBOSE=false\n\n# Adobe Target Deployment Configuration\n# ADOBE_TENANT is your AT tenant slug \u2014 find it in the AT URL after \"mc.adobe.io/\".\nADOBE_TENANT=\"\"\nADOBE_CLIENT_ID=\"\"\nADOBE_CLIENT_SECRET=\"\"\n ";
|
|
122
|
+
envContent = "\nACTIVITIES_BASE_FOLDER=\"Activities\"\nACTIVITY_FOLDER_NAME=\"\"\nPUPPETEER_LANDING_PAGE=\"\"\nTARGET_URL=\"\"\nLOGIN_URL=\"\"\n\n# Dev-server selection (used by `atb dev --browser`).\n# Edit and save while puppeteer is running to hot-swap the previewed bundle.\n# PAGE is only meaningful for multi-page activities \u2014 leave empty otherwise.\nVARIATION=\"Variation-1\"\nPAGE=\"\"\n\nNODE_ENV=\"development\"\nVERBOSE=false\n\n# Build wrapper config.\n# TARGET_BUILD_PREFIX customizes the window flag baked into each build \u2014\n# rendered as window.${TARGET_BUILD_PREFIX}_${contentHash}_${hash}. Defaults\n# to \"TargetBuild\" when empty. Useful when multiple at-builder activities\n# end up loaded on the same page and you want each project's flag namespaced.\nTARGET_BUILD_PREFIX=\"\"\n\n# Adobe Target Deployment Configuration\n# ADOBE_TENANT is your AT tenant slug \u2014 find it in the AT URL after \"mc.adobe.io/\".\nADOBE_TENANT=\"\"\nADOBE_CLIENT_ID=\"\"\nADOBE_CLIENT_SECRET=\"\"\n ";
|
|
123
123
|
// Write the content to the .env file
|
|
124
124
|
fs_1.default.writeFileSync(envPath, envContent.trim(), 'utf8');
|
|
125
125
|
console.log('.env file created successfully!');
|
package/bin/services/doctor.js
CHANGED
|
@@ -655,7 +655,7 @@ var createEnvFile = function (projectPath) { return __awaiter(void 0, void 0, vo
|
|
|
655
655
|
var envPath, envContent;
|
|
656
656
|
return __generator(this, function (_a) {
|
|
657
657
|
envPath = path_1.default.join(projectPath, '.env');
|
|
658
|
-
envContent = "ACTIVITIES_BASE_FOLDER=\"Activities\"\nACTIVITY_FOLDER_NAME=\"\"\nPUPPETEER_LANDING_PAGE=\"\"\nTARGET_URL=\"\"\nLOGIN_URL=\"\"\n\n# Dev-server selection (used by `atb dev --browser`).\n# Edit and save while puppeteer is running to hot-swap the previewed bundle.\n# PAGE is only meaningful for multi-page activities \u2014 leave empty otherwise.\nVARIATION=\"Variation-1\"\nPAGE=\"\"\n\nNODE_ENV=\"development\"\nVERBOSE=false\n\n# Adobe Target Deployment Configuration\n# ADOBE_TENANT is your AT tenant slug \u2014 find it in the AT URL after \"mc.adobe.io/\".\nADOBE_TENANT=\"\"\nADOBE_CLIENT_ID=\"\"\nADOBE_CLIENT_SECRET=\"\"";
|
|
658
|
+
envContent = "ACTIVITIES_BASE_FOLDER=\"Activities\"\nACTIVITY_FOLDER_NAME=\"\"\nPUPPETEER_LANDING_PAGE=\"\"\nTARGET_URL=\"\"\nLOGIN_URL=\"\"\n\n# Dev-server selection (used by `atb dev --browser`).\n# Edit and save while puppeteer is running to hot-swap the previewed bundle.\n# PAGE is only meaningful for multi-page activities \u2014 leave empty otherwise.\nVARIATION=\"Variation-1\"\nPAGE=\"\"\n\nNODE_ENV=\"development\"\nVERBOSE=false\n\n# Build wrapper config.\n# TARGET_BUILD_PREFIX customizes the window flag baked into each build \u2014\n# rendered as window.${TARGET_BUILD_PREFIX}_${contentHash}_${hash}. Defaults\n# to \"TargetBuild\" when empty. Useful when multiple at-builder activities\n# end up loaded on the same page and you want each project's flag namespaced.\nTARGET_BUILD_PREFIX=\"\"\n\n# Adobe Target Deployment Configuration\n# ADOBE_TENANT is your AT tenant slug \u2014 find it in the AT URL after \"mc.adobe.io/\".\nADOBE_TENANT=\"\"\nADOBE_CLIENT_ID=\"\"\nADOBE_CLIENT_SECRET=\"\"";
|
|
659
659
|
try {
|
|
660
660
|
fs_1.default.writeFileSync(envPath, envContent, 'utf8');
|
|
661
661
|
return [2 /*return*/, true];
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* A custom Webpack plugin to wrap bundled assets with a header and footer.
|
|
5
7
|
*/
|
|
@@ -35,7 +37,22 @@ class CustomWrapperPlugin {
|
|
|
35
37
|
if (this.options.test && this.options.test.test(pathname)) {
|
|
36
38
|
const asset = compilation.getAsset(pathname);
|
|
37
39
|
const source = asset.source.source();
|
|
38
|
-
|
|
40
|
+
// contentHash is derived from the asset's source so
|
|
41
|
+
// it changes whenever the bundled output changes,
|
|
42
|
+
// independent of the compilation-wide `hash`. Both
|
|
43
|
+
// are exposed so consumers can reference either or
|
|
44
|
+
// combine them (e.g. for a window-flag id that's
|
|
45
|
+
// unique per-asset AND per-build).
|
|
46
|
+
const contentHash = crypto
|
|
47
|
+
.createHash('md5')
|
|
48
|
+
.update(typeof source === 'string' ? source : Buffer.from(source))
|
|
49
|
+
.digest('hex')
|
|
50
|
+
.slice(0, 20);
|
|
51
|
+
const args = {
|
|
52
|
+
hash: compilation.hash,
|
|
53
|
+
contentHash,
|
|
54
|
+
pathname
|
|
55
|
+
};
|
|
39
56
|
|
|
40
57
|
const wrappedSource =
|
|
41
58
|
(typeof this.options.header === 'function'
|
|
@@ -1,13 +1,95 @@
|
|
|
1
1
|
/* eslint-disable no-undef */
|
|
2
2
|
const { ESLint } = require('eslint');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// Legacy .eslintrc.* names ESLint v8 supported. ESLint v9 dropped them, but a
|
|
7
|
+
// lot of existing consumer projects still ship them — auto-convert via
|
|
8
|
+
// @eslint/eslintrc's FlatCompat shim so those rules are honored.
|
|
9
|
+
const LEGACY_ESLINTRC_NAMES = [
|
|
10
|
+
'.eslintrc.js',
|
|
11
|
+
'.eslintrc.cjs',
|
|
12
|
+
'.eslintrc.json',
|
|
13
|
+
'.eslintrc'
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Find the first .eslintrc.* file in `context`. Returns absolute path or null.
|
|
18
|
+
*/
|
|
19
|
+
function findLegacyEslintrc(context) {
|
|
20
|
+
for (const name of LEGACY_ESLINTRC_NAMES) {
|
|
21
|
+
const p = path.join(context, name);
|
|
22
|
+
if (fs.existsSync(p)) return p;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Convert a legacy ESLint config (.eslintrc.*) to the flat-config array
|
|
29
|
+
* format ESLint v9 expects. Uses FlatCompat from @eslint/eslintrc.
|
|
30
|
+
*
|
|
31
|
+
* Returns [] (and warns once) if @eslint/eslintrc isn't installed or the
|
|
32
|
+
* config can't be parsed — better to ship a partial config than to fail
|
|
33
|
+
* the build outright.
|
|
34
|
+
*/
|
|
35
|
+
function convertLegacyEslintrc(legacyPath, context) {
|
|
36
|
+
let FlatCompat;
|
|
37
|
+
try {
|
|
38
|
+
({ FlatCompat } = require('@eslint/eslintrc'));
|
|
39
|
+
} catch (_e) {
|
|
40
|
+
console.warn(
|
|
41
|
+
`[at-builder] Found ${path.relative(context, legacyPath)} but @eslint/eslintrc is not installed; ` +
|
|
42
|
+
'legacy config will be ignored. Migrate to eslint.config.js or report this.'
|
|
43
|
+
);
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let legacyConfig;
|
|
48
|
+
try {
|
|
49
|
+
if (legacyPath.endsWith('.json') || legacyPath.endsWith('.eslintrc')) {
|
|
50
|
+
legacyConfig = JSON.parse(fs.readFileSync(legacyPath, 'utf8'));
|
|
51
|
+
} else {
|
|
52
|
+
delete require.cache[require.resolve(legacyPath)];
|
|
53
|
+
legacyConfig = require(legacyPath);
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.warn(`[at-builder] Failed to read ${path.relative(context, legacyPath)}: ${err.message}`);
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// recommendedConfig/allConfig are required when the legacy file
|
|
61
|
+
// `extends: ["eslint:recommended"]` (or "eslint:all"). Without them,
|
|
62
|
+
// FlatCompat throws "Missing parameter 'recommendedConfig'" because
|
|
63
|
+
// ESLint v9 no longer ships those built-in strings — they live in the
|
|
64
|
+
// separate @eslint/js package now.
|
|
65
|
+
let jsConfigs;
|
|
66
|
+
try {
|
|
67
|
+
jsConfigs = require('@eslint/js').configs;
|
|
68
|
+
} catch (_e) {
|
|
69
|
+
jsConfigs = {};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const compat = new FlatCompat({
|
|
74
|
+
baseDirectory: context,
|
|
75
|
+
recommendedConfig: jsConfigs.recommended,
|
|
76
|
+
allConfig: jsConfigs.all
|
|
77
|
+
});
|
|
78
|
+
const flatArray = compat.config(legacyConfig);
|
|
79
|
+
console.log(`[at-builder] Loaded legacy ESLint config from ${path.relative(context, legacyPath)}`);
|
|
80
|
+
return flatArray;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
console.warn(`[at-builder] FlatCompat could not convert ${path.relative(context, legacyPath)}: ${err.message}`);
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
4
86
|
|
|
5
87
|
class ESLintFlatConfigPlugin {
|
|
6
88
|
constructor(options = {}) {
|
|
7
89
|
this.options = {
|
|
8
90
|
context: options.context || process.cwd(),
|
|
9
91
|
configFile: options.configFile,
|
|
10
|
-
overrideConfigFiles: options.overrideConfigFiles || [], // Array of additional config
|
|
92
|
+
overrideConfigFiles: options.overrideConfigFiles || [], // Array of additional flat-config file paths
|
|
11
93
|
failOnError: options.failOnError !== false,
|
|
12
94
|
failOnWarning: options.failOnWarning || false,
|
|
13
95
|
fix: options.fix || false, // Enable auto-fix
|
|
@@ -40,33 +122,52 @@ class ESLintFlatConfigPlugin {
|
|
|
40
122
|
if (filesToLint.length === 0) return;
|
|
41
123
|
|
|
42
124
|
try {
|
|
43
|
-
// Load base config
|
|
125
|
+
// Load base config (at-builder's bundled flat config)
|
|
44
126
|
let baseConfig = [];
|
|
45
|
-
if (this.options.configFile &&
|
|
127
|
+
if (this.options.configFile && fs.existsSync(this.options.configFile)) {
|
|
46
128
|
delete require.cache[require.resolve(this.options.configFile)];
|
|
47
129
|
baseConfig = require(this.options.configFile);
|
|
48
130
|
}
|
|
49
131
|
|
|
50
|
-
// Load
|
|
132
|
+
// Load consumer flat-config overrides (eslint.config.js etc.)
|
|
51
133
|
const overrideConfigs = [];
|
|
52
134
|
for (const configPath of this.options.overrideConfigFiles) {
|
|
53
135
|
try {
|
|
54
136
|
const resolvedPath = path.resolve(this.options.context, configPath);
|
|
55
|
-
if (
|
|
137
|
+
if (fs.existsSync(resolvedPath)) {
|
|
56
138
|
delete require.cache[require.resolve(resolvedPath)];
|
|
57
139
|
const config = require(resolvedPath);
|
|
58
140
|
overrideConfigs.push(...(Array.isArray(config) ? config : [config]));
|
|
59
141
|
}
|
|
60
142
|
} catch (err) {
|
|
61
|
-
// Silently skip if config doesn't exist
|
|
143
|
+
// Silently skip if config doesn't exist or can't be required
|
|
62
144
|
}
|
|
63
145
|
}
|
|
64
146
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
147
|
+
// Auto-discover legacy .eslintrc.* in the consumer project and
|
|
148
|
+
// convert it via FlatCompat. Only kicks in if no flat-config
|
|
149
|
+
// override was found (flat config wins by convention).
|
|
150
|
+
let legacyConfigs = [];
|
|
151
|
+
if (overrideConfigs.length === 0) {
|
|
152
|
+
const legacyPath = findLegacyEslintrc(this.options.context);
|
|
153
|
+
if (legacyPath) {
|
|
154
|
+
legacyConfigs = convertLegacyEslintrc(legacyPath, this.options.context);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// When the consumer ships their own ESLint config (flat or
|
|
159
|
+
// legacy), it becomes the sole authority — at-builder's
|
|
160
|
+
// bundled defaults are NOT merged in. Otherwise consumers
|
|
161
|
+
// would have to defensively `'no-console': 'off'` every
|
|
162
|
+
// rule at-builder happens to opinionate on. at-builder's
|
|
163
|
+
// bundle is a fallback for projects with no ESLint config
|
|
164
|
+
// at all, not a baseline that gets merged with theirs.
|
|
165
|
+
const consumerHasConfig =
|
|
166
|
+
overrideConfigs.length > 0 || legacyConfigs.length > 0;
|
|
167
|
+
|
|
168
|
+
const mergedConfig = consumerHasConfig
|
|
169
|
+
? [...overrideConfigs, ...legacyConfigs]
|
|
170
|
+
: (Array.isArray(baseConfig) ? baseConfig : [baseConfig]);
|
|
70
171
|
|
|
71
172
|
// overrideConfigFile: true tells ESLint v9 to skip the
|
|
72
173
|
// cwd-based flat-config lookup entirely and treat
|
|
@@ -75,12 +176,23 @@ class ESLintFlatConfigPlugin {
|
|
|
75
176
|
// `eslint.config.js` (which they don't have, since at-builder
|
|
76
177
|
// ships its own) and fails the build with "Could not find
|
|
77
178
|
// config file." even though we provided everything inline.
|
|
179
|
+
// ignore: true (default) respects the consumer's
|
|
180
|
+
// ignorePatterns / ignores entries. We previously had
|
|
181
|
+
// ignore:false thinking it suppressed ESLint's defaults,
|
|
182
|
+
// but it actually disabled ALL ignore handling — including
|
|
183
|
+
// the consumer's own patterns — so e.g. `.plop/**` in
|
|
184
|
+
// their .eslintrc.js was silently ignored.
|
|
185
|
+
//
|
|
186
|
+
// warnIgnored:false suppresses ESLint's "File ignored
|
|
187
|
+
// because of a matching ignore pattern" warnings — webpack
|
|
188
|
+
// happens to walk into ignored files via imports, and we
|
|
189
|
+
// don't want to surface a warning for each one.
|
|
78
190
|
const eslint = new ESLint({
|
|
79
191
|
cwd: this.options.context,
|
|
80
192
|
overrideConfigFile: true,
|
|
81
193
|
overrideConfig: mergedConfig,
|
|
82
|
-
|
|
83
|
-
fix: this.options.fix
|
|
194
|
+
warnIgnored: false,
|
|
195
|
+
fix: this.options.fix
|
|
84
196
|
});
|
|
85
197
|
|
|
86
198
|
const results = await eslint.lintFiles(filesToLint);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "at-builder",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"main": "bin/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"atb": "bin/index.js"
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"@babel/eslint-parser": "^7.26.8",
|
|
25
25
|
"@babel/plugin-transform-runtime": "^7.18.9",
|
|
26
26
|
"@babel/preset-env": "^7.18.9",
|
|
27
|
+
"@eslint/eslintrc": "^3.2.0",
|
|
27
28
|
"@eslint/js": "^9.20.0",
|
|
28
29
|
"@types/node": "^22.13.2",
|
|
29
30
|
"async": "^3.2.3",
|
package/src/constants/config.ts
CHANGED
|
@@ -157,6 +157,13 @@ PAGE=""
|
|
|
157
157
|
NODE_ENV="development"
|
|
158
158
|
VERBOSE=false
|
|
159
159
|
|
|
160
|
+
# Build wrapper config.
|
|
161
|
+
# TARGET_BUILD_PREFIX customizes the window flag baked into each build —
|
|
162
|
+
# rendered as window.\${TARGET_BUILD_PREFIX}_\${contentHash}_\${hash}. Defaults
|
|
163
|
+
# to "TargetBuild" when empty. Useful when multiple at-builder activities
|
|
164
|
+
# end up loaded on the same page and you want each project's flag namespaced.
|
|
165
|
+
TARGET_BUILD_PREFIX=""
|
|
166
|
+
|
|
160
167
|
# Adobe Target Deployment Configuration
|
|
161
168
|
# ADOBE_TENANT is your AT tenant slug — find it in the AT URL after "mc.adobe.io/".
|
|
162
169
|
ADOBE_TENANT=""
|
package/src/services/doctor.ts
CHANGED
|
@@ -558,6 +558,13 @@ PAGE=""
|
|
|
558
558
|
NODE_ENV="development"
|
|
559
559
|
VERBOSE=false
|
|
560
560
|
|
|
561
|
+
# Build wrapper config.
|
|
562
|
+
# TARGET_BUILD_PREFIX customizes the window flag baked into each build —
|
|
563
|
+
# rendered as window.\${TARGET_BUILD_PREFIX}_\${contentHash}_\${hash}. Defaults
|
|
564
|
+
# to "TargetBuild" when empty. Useful when multiple at-builder activities
|
|
565
|
+
# end up loaded on the same page and you want each project's flag namespaced.
|
|
566
|
+
TARGET_BUILD_PREFIX=""
|
|
567
|
+
|
|
561
568
|
# Adobe Target Deployment Configuration
|
|
562
569
|
# ADOBE_TENANT is your AT tenant slug — find it in the AT URL after "mc.adobe.io/".
|
|
563
570
|
ADOBE_TENANT=""
|
package/webpack.config.js
CHANGED
|
@@ -222,8 +222,17 @@ const plugins = [
|
|
|
222
222
|
new WrapperPlugin({
|
|
223
223
|
test: /\.js$/,
|
|
224
224
|
header: function (_, args) {
|
|
225
|
-
//
|
|
226
|
-
|
|
225
|
+
// The window flag has to be unique per build so a fresh injection
|
|
226
|
+
// doesn't no-op against a stale flag from the previous version.
|
|
227
|
+
// Includes both contentHash (asset-content scoped) and hash
|
|
228
|
+
// (compilation scoped) so it changes whenever either does.
|
|
229
|
+
//
|
|
230
|
+
// Prefix is configurable per project via TARGET_BUILD_PREFIX in .env
|
|
231
|
+
// (defaults to "TargetBuild") — useful when multiple at-builder
|
|
232
|
+
// activities load on the same page and you want each one's flag
|
|
233
|
+
// namespaced.
|
|
234
|
+
const prefix = (process.env.TARGET_BUILD_PREFIX || 'TargetBuild').trim() || 'TargetBuild';
|
|
235
|
+
const _uniqueTestId = `${prefix}_${args.contentHash}_${args.hash}`;
|
|
227
236
|
args._uniqueTestId = _uniqueTestId;
|
|
228
237
|
return `
|
|
229
238
|
(function(){
|