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.
@@ -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
  }
@@ -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!');
@@ -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
- const args = { hash: compilation.hash };
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 files
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 && require('fs').existsSync(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 and merge override configs
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 (require('fs').existsSync(resolvedPath)) {
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
- // Merge all configs (base + overrides)
66
- const mergedConfig = [
67
- ...(Array.isArray(baseConfig) ? baseConfig : [baseConfig]),
68
- ...overrideConfigs
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
- ignore: false, // Don't use default ignore patterns
83
- fix: this.options.fix // Enable auto-fix if option is set
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.1",
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",
@@ -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=""
@@ -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
- //Handle target issue for reloading the styles.
226
- let _uniqueTestId = `TargetBuild_${args.hash}`;
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(){