at-builder 1.4.0 β†’ 1.4.3

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.
@@ -51,7 +51,26 @@
51
51
  "Bash(ls -la \"$\\(yarn global bin\\)/atb\")",
52
52
  "Bash(ls -la \"$\\(yarn global dir \\)/node_modules/at-builder\")",
53
53
  "Bash(git commit -m 'fix: surface ESLint error detail and clean up atb deploy build-failure exit *)",
54
- "Bash(git commit -m 'fix\\(webpack\\): emit correct `variation=` attribute on at-build.html script tag *)"
54
+ "Bash(git commit -m 'fix\\(webpack\\): emit correct `variation=` attribute on at-build.html script tag *)",
55
+ "Bash(yarn install *)",
56
+ "Bash(git rm *)",
57
+ "Bash(git commit -m 'chore\\(deps\\): drop unused/deprecated deps + migrate qs to URLSearchParams *)",
58
+ "Bash(git commit -m 'fix\\(config\\): stop printing red \"Error reading .env\" on every atb invocation *)",
59
+ "Bash(yarn why *)",
60
+ "Bash(git commit -m 'fix\\(plop\\): trim whitespace on all atb new text prompts *)",
61
+ "Bash(atb install-extension *)",
62
+ "Bash(git commit -m 'feat\\(install-extension\\): first-class support for Antigravity IDE \\(agy\\) *)",
63
+ "Bash(npm whoami *)",
64
+ "Bash(npm view *)",
65
+ "Bash(git tag *)",
66
+ "Bash(git push *)",
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 *)"
55
74
  ]
56
75
  }
57
76
  }
@@ -19,6 +19,11 @@ const prompts = function (inquirer) {
19
19
  console.log(kleur.cyan().bold(' πŸ§ͺ Create a new Adobe Target activity'));
20
20
  console.log(kleur.gray(' ─ scaffolds variation folders + shared/build.config.json\n'));
21
21
 
22
+ // All text inputs go through the same trim filter so downstream code never
23
+ // has to deal with stray leading/trailing whitespace (which would otherwise
24
+ // become folder names with trailing spaces, etc.).
25
+ const trim = (input) => (input || '').trim();
26
+
22
27
  return inquirer.prompt([
23
28
  {
24
29
  type: PROMPT_TYPE_LIST,
@@ -35,7 +40,8 @@ const prompts = function (inquirer) {
35
40
  name: 'testName',
36
41
  prefix: kleur.cyan('β€Ί'),
37
42
  message: ask('Activity name', 'folder name, e.g. TEST-1234 hero-banner'),
38
- validate: (input) => input && input.trim()
43
+ filter: trim,
44
+ validate: (input) => input
39
45
  ? true
40
46
  : 'Activity name cannot be empty.'
41
47
  },
@@ -46,7 +52,8 @@ const prompts = function (inquirer) {
46
52
  message: (answers) => answers.activityType === 'xt'
47
53
  ? ask('How many experiences?')
48
54
  : ask('How many variations?', 'control is added automatically'),
49
- validate: (input) => /^[1-9]\d*$/.test(String(input).trim())
55
+ filter: trim,
56
+ validate: (input) => /^[1-9]\d*$/.test(input)
50
57
  ? true
51
58
  : 'Enter a positive integer (1, 2, 3, ...).'
52
59
  },
@@ -55,7 +62,7 @@ const prompts = function (inquirer) {
55
62
  name: 'pageNames',
56
63
  prefix: kleur.cyan('β€Ί'),
57
64
  message: ask('Page names', 'comma-separated for multi-page, blank for single-page'),
58
- filter: (input) => (input || '').trim()
65
+ filter: trim
59
66
  }
60
67
  ]);
61
68
  };
@@ -28,6 +28,6 @@
28
28
  }
29
29
  }
30
30
  catch (err){
31
- console.log(err);
31
+ console.error(err);
32
32
  }
33
33
  }());
@@ -1,8 +1,28 @@
1
- const _camelCase = require('lodash/camelCase');
2
- const _startCase = require('lodash/startCase');
3
-
1
+ /**
2
+ * Convert an arbitrary string to PascalCase. Drop-in replacement for the
3
+ * previous `_startCase(_camelCase(str)).replace(/ /g, '')` lodash chain so we
4
+ * don't pull in 70KB+ of lodash for two trivial string ops.
5
+ *
6
+ * Handles the input shapes plop prompts actually receive:
7
+ * "hello world" β†’ "HelloWorld"
8
+ * "kebab-case-name" β†’ "KebabCaseName"
9
+ * "snake_case_name" β†’ "SnakeCaseName"
10
+ * "FOO_BAR" β†’ "FooBar"
11
+ * "UPSDDO-1234 my-test" β†’ "Upsddo1234MyTest"
12
+ * "alreadyCamel" β†’ "AlreadyCamel"
13
+ * "MyComponent" β†’ "MyComponent"
14
+ */
4
15
  exports.toPascalCase = str => {
5
- return _startCase(_camelCase(str)).replace(/ /g, '');
16
+ if (!str) return '';
17
+ return String(str)
18
+ // insert space at lower→Upper boundary: "fooBar" → "foo Bar"
19
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
20
+ // insert space at UPPER→Upperlower boundary: "FOOBar" → "FOO Bar"
21
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
22
+ .split(/[^a-zA-Z0-9]+/)
23
+ .filter(Boolean)
24
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
25
+ .join('');
6
26
  };
7
27
 
8
28
  /**
package/README.md CHANGED
@@ -67,7 +67,7 @@ atb deploy
67
67
  | `atb deploy` | Deploy activity to Adobe Target | `--dry-run` for testing, `--force` to override the 60s cooldown |
68
68
  | `atb sync` | Sync `build.config.json` with the AT activity (pages, experiences, names) | `--scaffold` to auto-create missing variation folders |
69
69
  | `atb doctor` | Diagnose and fix configuration issues | `--fix` to auto-fix |
70
- | `atb install-extension` | Install the at-builder VSCode extension from the Marketplace | `--editor cursor` (or `codium`, etc.) to use a non-VSCode editor CLI |
70
+ | `atb install-extension` | Install the at-builder extension from the Marketplace (defaults to the VSCode `code` CLI) | `--editor agy` for Antigravity IDE; `--editor cursor`, `--editor codium`, or any VSCode-fork CLI on `PATH` |
71
71
 
72
72
  ### **Examples**
73
73
 
@@ -108,7 +108,7 @@ var getHelpInfo = function () { return __awaiter(void 0, void 0, void 0, functio
108
108
  case 0: return [4 /*yield*/, (0, exports.getVersion)()];
109
109
  case 1:
110
110
  version = _a.sent();
111
- return [2 /*return*/, "\n".concat(formatText("\uD83C\uDFAF at-builder", 'cyan', true), " ").concat(formatText("v".concat(version), 'gray'), "\n\n").concat(formatText("Adobe Target Activity Development CLI", 'white', true), "\n\nA streamlined command-line tool for creating, building, and deploying Adobe Target \nA/B testing activities with modern web technologies.\n\n").concat(formatText("USAGE", 'yellow', true), "\n ").concat(formatText("atb", 'cyan'), " ").concat(formatText("<command> [options]", 'gray'), "\n\n").concat(formatText("COMMANDS", 'yellow', true), "\n ").concat(formatText("init", 'cyan', true), " Initialize project with .env configuration and templates\n ").concat(formatText("new", 'cyan', true), " Create a new Adobe Target activity with variations\n ").concat(formatText("build", 'cyan', true), " Build activity for development\n ").concat(formatText("build --prod", 'cyan', true), " Build for production deployment\n ").concat(formatText("dev", 'cyan', true), " Start development server with file watching\n ").concat(formatText("dev --browser", 'cyan', true), " Start development server and open in browser\n ").concat(formatText("deploy", 'cyan', true), " Deploy activity to Adobe Target\n ").concat(formatText("deploy --dry-run", 'cyan', true), " Deploy in dry-run mode without actual deployment\n ").concat(formatText("deploy --force", 'cyan', true), " Override the 60s post-deploy cooldown lock\n ").concat(formatText("sync", 'cyan', true), " Sync build.config.json with the AT activity (pages, experiences, names)\n ").concat(formatText("sync --scaffold", 'cyan', true), " Sync and auto-create any missing variation folders with boilerplate\n ").concat(formatText("doctor", 'cyan', true), " Diagnose and fix project configuration issues\n ").concat(formatText("doctor --fix", 'cyan', true), " Automatically fix detected configuration issues\n ").concat(formatText("install-extension", 'cyan', true), " Install the bundled at-builder VSCode extension (.vsix)\n \n").concat(formatText("GLOBAL OPTIONS", 'yellow', true), "\n ").concat(formatText("-v, --verbose", 'green'), " Enable detailed logging\n ").concat(formatText("-V, --version", 'green'), " Display version information\n ").concat(formatText("-h, --help", 'green'), " Show help information\n\n").concat(formatText("EXAMPLES", 'yellow', true), "\n ").concat(formatText("# Initialize new project", 'gray'), "\n ").concat(formatText("atb init", 'white'), "\n \n ").concat(formatText("# Create new activity", 'gray'), "\n ").concat(formatText("atb new", 'white'), "\n \n ").concat(formatText("# Development build with hot reload", 'gray'), "\n ").concat(formatText("atb build", 'white'), "\n \n ").concat(formatText("# Production build for Adobe Target deployment", 'gray'), "\n ").concat(formatText("atb build --prod", 'white'), "\n \n ").concat(formatText("# Development server with browser", 'gray'), "\n ").concat(formatText("atb dev --browser", 'white'), "\n \n ").concat(formatText("# Deploy to Adobe Target", 'gray'), "\n ").concat(formatText("atb deploy", 'white'), "\n \n ").concat(formatText("# Deploy in dry-run mode", 'gray'), "\n ").concat(formatText("atb deploy --dry-run", 'white'), "\n\n ").concat(formatText("# Sync build.config.json from Adobe Target", 'gray'), "\n ").concat(formatText("atb sync", 'white'), "\n\n ").concat(formatText("# Sync and scaffold missing variation folders", 'gray'), "\n ").concat(formatText("atb sync --scaffold", 'white'), "\n\n ").concat(formatText("# Check project configuration", 'gray'), "\n ").concat(formatText("atb doctor", 'white'), "\n \n ").concat(formatText("# Auto-fix configuration issues", 'gray'), "\n ").concat(formatText("atb doctor --fix", 'white'), "\n\n").concat(formatText("WORKFLOW", 'yellow', true), "\n ").concat(formatText("1.", 'cyan'), " Run ").concat(formatText("atb init", 'white'), " to set up project configuration\n ").concat(formatText("2.", 'cyan'), " Run ").concat(formatText("atb new", 'white'), " to create activity templates \n ").concat(formatText("3.", 'cyan'), " Develop variations in ").concat(formatText("/Activities/{name}/Variation-*/", 'gray'), "\n ").concat(formatText("4.", 'cyan'), " Run ").concat(formatText("atb build", 'white'), " for development testing\n ").concat(formatText("5.", 'cyan'), " Run ").concat(formatText("atb build --prod", 'white'), " for Adobe Target deployment\n ").concat(formatText("6.", 'cyan'), " Run ").concat(formatText("atb deploy", 'white'), " to deploy to Adobe Target\n\n").concat(formatText("SUPPORT", 'yellow', true), "\n Repository: ").concat(formatText("https://github.com/upesenga/at-builder", 'blue'), "\n Issues: ").concat(formatText("https://github.com/upesenga/at-builder/issues", 'blue'), "\n License: ").concat(formatText("MIT", 'green'), "\n")];
111
+ return [2 /*return*/, "\n".concat(formatText("\uD83C\uDFAF at-builder", 'cyan', true), " ").concat(formatText("v".concat(version), 'gray'), "\n\n").concat(formatText("Adobe Target Activity Development CLI", 'white', true), "\n\nA streamlined command-line tool for creating, building, and deploying Adobe Target \nA/B testing activities with modern web technologies.\n\n").concat(formatText("USAGE", 'yellow', true), "\n ").concat(formatText("atb", 'cyan'), " ").concat(formatText("<command> [options]", 'gray'), "\n\n").concat(formatText("COMMANDS", 'yellow', true), "\n ").concat(formatText("init", 'cyan', true), " Initialize project with .env configuration and templates\n ").concat(formatText("new", 'cyan', true), " Create a new Adobe Target activity with variations\n ").concat(formatText("build", 'cyan', true), " Build activity for development\n ").concat(formatText("build --prod", 'cyan', true), " Build for production deployment\n ").concat(formatText("dev", 'cyan', true), " Start development server with file watching\n ").concat(formatText("dev --browser", 'cyan', true), " Start development server and open in browser\n ").concat(formatText("deploy", 'cyan', true), " Deploy activity to Adobe Target\n ").concat(formatText("deploy --dry-run", 'cyan', true), " Deploy in dry-run mode without actual deployment\n ").concat(formatText("deploy --force", 'cyan', true), " Override the 60s post-deploy cooldown lock\n ").concat(formatText("sync", 'cyan', true), " Sync build.config.json with the AT activity (pages, experiences, names)\n ").concat(formatText("sync --scaffold", 'cyan', true), " Sync and auto-create any missing variation folders with boilerplate\n ").concat(formatText("doctor", 'cyan', true), " Diagnose and fix project configuration issues\n ").concat(formatText("doctor --fix", 'cyan', true), " Automatically fix detected configuration issues\n ").concat(formatText("install-extension", 'cyan', true), " Install the at-builder extension from the Marketplace (default: VSCode)\n ").concat(formatText("install-extension --editor agy", 'cyan', true), " Install into Antigravity IDE (also: cursor, codium, etc.)\n \n").concat(formatText("GLOBAL OPTIONS", 'yellow', true), "\n ").concat(formatText("-v, --verbose", 'green'), " Enable detailed logging\n ").concat(formatText("-V, --version", 'green'), " Display version information\n ").concat(formatText("-h, --help", 'green'), " Show help information\n\n").concat(formatText("EXAMPLES", 'yellow', true), "\n ").concat(formatText("# Initialize new project", 'gray'), "\n ").concat(formatText("atb init", 'white'), "\n \n ").concat(formatText("# Create new activity", 'gray'), "\n ").concat(formatText("atb new", 'white'), "\n \n ").concat(formatText("# Development build with hot reload", 'gray'), "\n ").concat(formatText("atb build", 'white'), "\n \n ").concat(formatText("# Production build for Adobe Target deployment", 'gray'), "\n ").concat(formatText("atb build --prod", 'white'), "\n \n ").concat(formatText("# Development server with browser", 'gray'), "\n ").concat(formatText("atb dev --browser", 'white'), "\n \n ").concat(formatText("# Deploy to Adobe Target", 'gray'), "\n ").concat(formatText("atb deploy", 'white'), "\n \n ").concat(formatText("# Deploy in dry-run mode", 'gray'), "\n ").concat(formatText("atb deploy --dry-run", 'white'), "\n\n ").concat(formatText("# Sync build.config.json from Adobe Target", 'gray'), "\n ").concat(formatText("atb sync", 'white'), "\n\n ").concat(formatText("# Sync and scaffold missing variation folders", 'gray'), "\n ").concat(formatText("atb sync --scaffold", 'white'), "\n\n ").concat(formatText("# Check project configuration", 'gray'), "\n ").concat(formatText("atb doctor", 'white'), "\n \n ").concat(formatText("# Auto-fix configuration issues", 'gray'), "\n ").concat(formatText("atb doctor --fix", 'white'), "\n\n").concat(formatText("WORKFLOW", 'yellow', true), "\n ").concat(formatText("1.", 'cyan'), " Run ").concat(formatText("atb init", 'white'), " to set up project configuration\n ").concat(formatText("2.", 'cyan'), " Run ").concat(formatText("atb new", 'white'), " to create activity templates \n ").concat(formatText("3.", 'cyan'), " Develop variations in ").concat(formatText("/Activities/{name}/Variation-*/", 'gray'), "\n ").concat(formatText("4.", 'cyan'), " Run ").concat(formatText("atb build", 'white'), " for development testing\n ").concat(formatText("5.", 'cyan'), " Run ").concat(formatText("atb build --prod", 'white'), " for Adobe Target deployment\n ").concat(formatText("6.", 'cyan'), " Run ").concat(formatText("atb deploy", 'white'), " to deploy to Adobe Target\n\n").concat(formatText("SUPPORT", 'yellow', true), "\n Repository: ").concat(formatText("https://github.com/upesenga/at-builder", 'blue'), "\n Issues: ").concat(formatText("https://github.com/upesenga/at-builder/issues", 'blue'), "\n License: ").concat(formatText("MIT", 'green'), "\n")];
112
112
  }
113
113
  });
114
114
  }); };
@@ -192,27 +192,28 @@ var createAdobeConfig = function (basePath) {
192
192
  }
193
193
  };
194
194
  /**
195
- * Reads and returns environment variables from the specified .env file
196
- * @param {string} basePath - The base path where the .env file is located
197
- * @returns {Promise<Object>} Object containing environment variables
195
+ * Reads and returns environment variables from the specified .env file.
196
+ *
197
+ * Throws if the file is missing or unreadable. Callers decide whether
198
+ * to surface the failure β€” printing here would spam every `atb` run from
199
+ * a directory that doesn't have a `.env` (e.g. `atb --version` from /tmp).
200
+ *
201
+ * @param basePath - The base path where the .env file is located
202
+ * @returns Object containing environment variables
198
203
  */
199
204
  var getENV = function (basePath) { return __awaiter(void 0, void 0, void 0, function () {
200
- var envPath, envContent, envVars_1, error_2;
205
+ var envPath, envContent, envVars;
201
206
  return __generator(this, function (_a) {
202
207
  switch (_a.label) {
203
208
  case 0:
204
209
  envPath = path_1.default.join(basePath, '.env');
205
- _a.label = 1;
206
- case 1:
207
- _a.trys.push([1, 3, , 4]);
208
- // Check if .env file exists
209
210
  if (!fs_1.default.existsSync(envPath)) {
210
211
  throw new Error('.env file not found');
211
212
  }
212
213
  return [4 /*yield*/, fs_1.default.promises.readFile(envPath, { encoding: 'utf8' })];
213
- case 2:
214
+ case 1:
214
215
  envContent = _a.sent();
215
- envVars_1 = {};
216
+ envVars = {};
216
217
  envContent.split('\n').forEach(function (line) {
217
218
  line = line.trim();
218
219
  // Skip empty lines and comments
@@ -221,16 +222,11 @@ var getENV = function (basePath) { return __awaiter(void 0, void 0, void 0, func
221
222
  if (key) {
222
223
  var value = valueParts.join('=').trim();
223
224
  // Remove quotes if present
224
- envVars_1[key.trim()] = value.replace(/^["'](.*)["']$/, '$1');
225
+ envVars[key.trim()] = value.replace(/^["'](.*)["']$/, '$1');
225
226
  }
226
227
  }
227
228
  });
228
- return [2 /*return*/, envVars_1];
229
- case 3:
230
- error_2 = _a.sent();
231
- console.error(cli_color_1.default.red("Error reading .env file: ".concat(error_2.message)));
232
- throw error_2;
233
- case 4: return [2 /*return*/];
229
+ return [2 /*return*/, envVars];
234
230
  }
235
231
  });
236
232
  }); };
package/bin/index.js CHANGED
@@ -230,8 +230,8 @@ var setupCommander = function () { return __awaiter(void 0, void 0, void 0, func
230
230
  }); });
231
231
  program
232
232
  .command('install-extension')
233
- .description('Install the bundled at-builder VSCode extension (.vsix)')
234
- .option('--editor <bin>', 'Editor CLI to use (e.g. code, cursor, codium)', 'code')
233
+ .description('Install the at-builder VSCode extension from the Marketplace')
234
+ .option('--editor <bin>', 'Editor CLI to use (e.g. code, agy, cursor, codium)', 'code')
235
235
  .action(function (options, command) { return __awaiter(void 0, void 0, void 0, function () {
236
236
  var globalOpts;
237
237
  return __generator(this, function (_a) {
@@ -382,50 +382,41 @@ var handleSync = function (scaffold, verbose) { return __awaiter(void 0, void 0,
382
382
  /**
383
383
  * Handles the install-extension command.
384
384
  *
385
- * Locates the bundled at-builder-*.vsix in at-builder's install directory
386
- * (one level up from this compiled file) and installs it via the editor's
387
- * CLI. Defaults to `code`; users on Cursor/VSCodium can pass --editor.
388
- *
389
- * The .vsix lives inside the at-builder package itself, NOT the consumer's
390
- * project β€” so it resolves via __dirname, not PWD.
385
+ * Installs the at-builder VSCode extension from the VS Code Marketplace
386
+ * using the editor's CLI. Defaults to `code`; users on Cursor/VSCodium
387
+ * can pass --editor.
388
+ */
389
+ /**
390
+ * Per-editor install hints for when the CLI binary isn't on PATH. Each
391
+ * recognized editor gets a one-line, copy-pasteable suggestion. Anything
392
+ * else falls back to a generic hint.
391
393
  */
394
+ var EDITOR_INSTALL_HINTS = {
395
+ code: 'Open VSCode and run Command Palette β†’ "Shell Command: Install \'code\' command in PATH".',
396
+ agy: 'Open Antigravity IDE and run Command Palette β†’ "Shell Command: Install \'agy\' command in PATH".',
397
+ cursor: 'Open Cursor and run Command Palette β†’ "Shell Command: Install \'cursor\' command in PATH".',
398
+ codium: 'Open VSCodium and run Command Palette β†’ "Shell Command: Install \'codium\' command in PATH".',
399
+ };
400
+ var reportEditorNotFound = function (editor) {
401
+ console.error("\u274C \"".concat(editor, "\" CLI not found in PATH."));
402
+ var hint = EDITOR_INSTALL_HINTS[editor]
403
+ || "Make sure the \"".concat(editor, "\" CLI is installed and on your PATH, or pass a different --editor.");
404
+ console.error("\uD83D\uDCA1 ".concat(hint));
405
+ };
392
406
  var handleInstallExtension = function (editor, verbose) { return __awaiter(void 0, void 0, void 0, function () {
393
- var installRoot, candidates, vsixPath, result;
407
+ var extensionId, result;
394
408
  return __generator(this, function (_a) {
409
+ extensionId = "UpendraSengar.at-builder";
395
410
  if (verbose)
396
- logger_1.default.info("verbose", "Installing VSCode extension via ".concat(editor));
397
- installRoot = path_1.default.join(__dirname, "..");
398
- try {
399
- candidates = fs_1.default.readdirSync(installRoot)
400
- .filter(function (f) { return /^at-builder-.+\.vsix$/.test(f); })
401
- .sort()
402
- .reverse(); // latest version first by lexicographic sort
403
- }
404
- catch (error) {
405
- console.error("\u274C Failed to scan at-builder directory: ".concat(error.message));
406
- process.exit(1);
407
- }
408
- if (candidates.length === 0) {
409
- console.error("❌ No at-builder VSCode extension (.vsix) found bundled with this install.");
410
- console.error("\uD83D\uDCA1 Expected at: ".concat(installRoot, "/at-builder-*.vsix"));
411
- process.exit(1);
412
- }
413
- vsixPath = path_1.default.join(installRoot, candidates[0]);
414
- console.log("\uD83D\uDCE6 Installing ".concat(candidates[0], " via \"").concat(editor, "\"..."));
415
- result = (0, child_process_1.spawn)(editor, ["--install-extension", vsixPath], {
411
+ logger_1.default.info("verbose", "Installing ".concat(extensionId, " via ").concat(editor));
412
+ console.log("\uD83D\uDCE6 Installing ".concat(extensionId, " from the Marketplace via \"").concat(editor, "\"..."));
413
+ result = (0, child_process_1.spawn)(editor, ["--install-extension", extensionId], {
416
414
  stdio: "inherit",
417
415
  shell: true
418
416
  });
419
417
  result.on("error", function (err) {
420
- // ENOENT here means the editor binary isn't on PATH.
421
418
  if (err.code === "ENOENT") {
422
- console.error("\u274C \"".concat(editor, "\" CLI not found in PATH."));
423
- if (editor === "code") {
424
- console.error("πŸ’‘ Open VSCode and run Command Palette β†’ \"Shell Command: Install 'code' command in PATH\".");
425
- }
426
- else {
427
- console.error("\uD83D\uDCA1 Make sure the \"".concat(editor, "\" CLI is installed and on your PATH, or pass a different --editor."));
428
- }
419
+ reportEditorNotFound(editor);
429
420
  }
430
421
  else {
431
422
  console.error("\u274C Failed to launch \"".concat(editor, "\": ").concat(err.message));
@@ -440,13 +431,7 @@ var handleInstallExtension = function (editor, verbose) { return __awaiter(void
440
431
  // 127 = shell's "command not found". With shell:true the spawn 'error'
441
432
  // event never fires for missing CLIs, so we surface the same hint here.
442
433
  if (code === 127) {
443
- console.error("\u274C \"".concat(editor, "\" CLI not found in PATH."));
444
- if (editor === "code") {
445
- console.error("πŸ’‘ Open VSCode and run Command Palette β†’ \"Shell Command: Install 'code' command in PATH\".");
446
- }
447
- else {
448
- console.error("\uD83D\uDCA1 Make sure the \"".concat(editor, "\" CLI is installed and on your PATH, or pass a different --editor."));
449
- }
434
+ reportEditorNotFound(editor);
450
435
  process.exit(1);
451
436
  }
452
437
  console.error("\u274C \"".concat(editor, " --install-extension\" exited with code ").concat(code, "."));
package/lib/at-deploy.js CHANGED
@@ -47,7 +47,6 @@ dotenv.config({ path: path.join(PWD, '.env'), quiet: true });
47
47
 
48
48
  const { execFileSync } = require('child_process');
49
49
  const axios = require('axios');
50
- const qs = require('querystring');
51
50
  const fs = require('fs');
52
51
 
53
52
  const { BASE_URL, IMS_TOKEN_URL, IMS_SCOPE } = require(path.join(PWD, 'adobe.config'));
@@ -207,12 +206,12 @@ function getBuildAssets(isMultiPage, allowedVariations) {
207
206
  }
208
207
 
209
208
  async function fetchAdobeToken() {
210
- const data = qs.stringify({
209
+ const data = new URLSearchParams({
211
210
  grant_type: 'client_credentials',
212
211
  client_id: process.env.ADOBE_CLIENT_ID,
213
212
  client_secret: process.env.ADOBE_CLIENT_SECRET,
214
213
  scope: IMS_SCOPE
215
- });
214
+ }).toString();
216
215
  try {
217
216
  const res = await axios.post(IMS_TOKEN_URL, data, {
218
217
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
package/lib/at-sync.js CHANGED
@@ -36,7 +36,6 @@
36
36
  const path = require('path');
37
37
  const fs = require('fs');
38
38
  const axios = require('axios');
39
- const qs = require('querystring');
40
39
  const dotenv = require('dotenv');
41
40
 
42
41
  const PWD = process.env.executionPath || process.cwd();
@@ -81,12 +80,12 @@ const CONFIG_PATHS = [
81
80
 
82
81
  // ─── Auth ───
83
82
  async function fetchToken() {
84
- const data = qs.stringify({
83
+ const data = new URLSearchParams({
85
84
  grant_type: 'client_credentials',
86
85
  client_id: process.env.ADOBE_CLIENT_ID,
87
86
  client_secret: process.env.ADOBE_CLIENT_SECRET,
88
87
  scope: IMS_SCOPE
89
- });
88
+ }).toString();
90
89
  const res = await axios.post(IMS_TOKEN_URL, data, {
91
90
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
92
91
  });
@@ -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.0",
3
+ "version": "1.4.3",
4
4
  "main": "bin/index.js",
5
5
  "bin": {
6
6
  "atb": "bin/index.js"
@@ -22,18 +22,14 @@
22
22
  "dependencies": {
23
23
  "@babel/core": "^7.26.9",
24
24
  "@babel/eslint-parser": "^7.26.8",
25
- "@babel/plugin-proposal-class-properties": "^7.18.6",
26
- "@babel/plugin-syntax-dynamic-import": "^7.8.3",
27
25
  "@babel/plugin-transform-runtime": "^7.18.9",
28
26
  "@babel/preset-env": "^7.18.9",
29
- "@babel/preset-react": "^7.18.6",
27
+ "@eslint/eslintrc": "^3.2.0",
30
28
  "@eslint/js": "^9.20.0",
31
29
  "@types/node": "^22.13.2",
32
30
  "async": "^3.2.3",
33
31
  "axios": "^1.12.2",
34
32
  "babel-loader": "^9.2.1",
35
- "babel-plugin-transform-async-to-generator": "^6.24.1",
36
- "build": "^0.1.4",
37
33
  "chokidar": "^4.0.3",
38
34
  "cli-color": "^2.0.3",
39
35
  "commander": "^13.1.0",
@@ -50,7 +46,6 @@
50
46
  "postcss-loader": "^8.1.1",
51
47
  "postcss-preset-env": "^10.1.4",
52
48
  "puppeteer": "^24.2.0",
53
- "querystring": "^0.2.1",
54
49
  "readline": "^1.3.0",
55
50
  "sass": "^1.53.0",
56
51
  "sass-loader": "^16.0.4",
@@ -76,7 +76,8 @@ ${formatText("COMMANDS", 'yellow', true)}
76
76
  ${formatText("sync --scaffold", 'cyan', true)} Sync and auto-create any missing variation folders with boilerplate
77
77
  ${formatText("doctor", 'cyan', true)} Diagnose and fix project configuration issues
78
78
  ${formatText("doctor --fix", 'cyan', true)} Automatically fix detected configuration issues
79
- ${formatText("install-extension", 'cyan', true)} Install the at-builder VSCode extension from the Marketplace
79
+ ${formatText("install-extension", 'cyan', true)} Install the at-builder extension from the Marketplace (default: VSCode)
80
+ ${formatText("install-extension --editor agy", 'cyan', true)} Install into Antigravity IDE (also: cursor, codium, etc.)
80
81
 
81
82
  ${formatText("GLOBAL OPTIONS", 'yellow', true)}
82
83
  ${formatText("-v, --verbose", 'green')} Enable detailed logging
@@ -278,39 +279,36 @@ module.exports = {
278
279
  }
279
280
 
280
281
  /**
281
- * Reads and returns environment variables from the specified .env file
282
- * @param {string} basePath - The base path where the .env file is located
283
- * @returns {Promise<Object>} Object containing environment variables
282
+ * Reads and returns environment variables from the specified .env file.
283
+ *
284
+ * Throws if the file is missing or unreadable. Callers decide whether
285
+ * to surface the failure β€” printing here would spam every `atb` run from
286
+ * a directory that doesn't have a `.env` (e.g. `atb --version` from /tmp).
287
+ *
288
+ * @param basePath - The base path where the .env file is located
289
+ * @returns Object containing environment variables
284
290
  */
285
291
  export const getENV = async (basePath: string) => {
286
292
  const envPath = path.join(basePath, '.env');
287
- try {
288
- // Check if .env file exists
289
- if (!fs.existsSync(envPath)) {
290
- throw new Error('.env file not found');
291
- }
293
+ if (!fs.existsSync(envPath)) {
294
+ throw new Error('.env file not found');
295
+ }
292
296
 
293
- // Read the .env file
294
- const envContent = await fs.promises.readFile(envPath, { encoding: 'utf8' });
295
-
296
- // Parse the .env file content
297
- const envVars: { [key: string]: string } = {};
298
- envContent.split('\n').forEach(line => {
299
- line = line.trim();
300
- // Skip empty lines and comments
301
- if (line && !line.startsWith('#')) {
302
- const [key, ...valueParts] = line.split('=');
303
- if (key) {
304
- const value = valueParts.join('=').trim();
305
- // Remove quotes if present
306
- envVars[key.trim()] = value.replace(/^["'](.*)["']$/, '$1');
307
- }
297
+ const envContent = await fs.promises.readFile(envPath, { encoding: 'utf8' });
298
+
299
+ const envVars: { [key: string]: string } = {};
300
+ envContent.split('\n').forEach(line => {
301
+ line = line.trim();
302
+ // Skip empty lines and comments
303
+ if (line && !line.startsWith('#')) {
304
+ const [key, ...valueParts] = line.split('=');
305
+ if (key) {
306
+ const value = valueParts.join('=').trim();
307
+ // Remove quotes if present
308
+ envVars[key.trim()] = value.replace(/^["'](.*)["']$/, '$1');
308
309
  }
309
- });
310
+ }
311
+ });
310
312
 
311
- return envVars;
312
- } catch (error) {
313
- console.error(clc.red(`Error reading .env file: ${error.message}`));
314
- throw error;
315
- }
313
+ return envVars;
316
314
  };
package/src/index.ts CHANGED
@@ -114,7 +114,7 @@ const setupCommander = async () => {
114
114
  program
115
115
  .command('install-extension')
116
116
  .description('Install the at-builder VSCode extension from the Marketplace')
117
- .option('--editor <bin>', 'Editor CLI to use (e.g. code, cursor, codium)', 'code')
117
+ .option('--editor <bin>', 'Editor CLI to use (e.g. code, agy, cursor, codium)', 'code')
118
118
  .action(async (options, command) => {
119
119
  const globalOpts = command.parent.opts();
120
120
  await handleInstallExtension(options.editor, globalOpts.verbose);
@@ -250,6 +250,25 @@ const handleSync = async (scaffold: boolean, verbose: boolean): Promise<void> =>
250
250
  * using the editor's CLI. Defaults to `code`; users on Cursor/VSCodium
251
251
  * can pass --editor.
252
252
  */
253
+ /**
254
+ * Per-editor install hints for when the CLI binary isn't on PATH. Each
255
+ * recognized editor gets a one-line, copy-pasteable suggestion. Anything
256
+ * else falls back to a generic hint.
257
+ */
258
+ const EDITOR_INSTALL_HINTS: Record<string, string> = {
259
+ code: 'Open VSCode and run Command Palette β†’ "Shell Command: Install \'code\' command in PATH".',
260
+ agy: 'Open Antigravity IDE and run Command Palette β†’ "Shell Command: Install \'agy\' command in PATH".',
261
+ cursor: 'Open Cursor and run Command Palette β†’ "Shell Command: Install \'cursor\' command in PATH".',
262
+ codium: 'Open VSCodium and run Command Palette β†’ "Shell Command: Install \'codium\' command in PATH".',
263
+ };
264
+
265
+ const reportEditorNotFound = (editor: string): void => {
266
+ console.error(`❌ "${editor}" CLI not found in PATH.`);
267
+ const hint = EDITOR_INSTALL_HINTS[editor]
268
+ || `Make sure the "${editor}" CLI is installed and on your PATH, or pass a different --editor.`;
269
+ console.error(`πŸ’‘ ${hint}`);
270
+ };
271
+
253
272
  const handleInstallExtension = async (editor: string, verbose: boolean): Promise<void> => {
254
273
  const extensionId = "UpendraSengar.at-builder";
255
274
 
@@ -264,12 +283,7 @@ const handleInstallExtension = async (editor: string, verbose: boolean): Promise
264
283
 
265
284
  result.on("error", (err) => {
266
285
  if ((err as NodeJS.ErrnoException).code === "ENOENT") {
267
- console.error(`❌ "${editor}" CLI not found in PATH.`);
268
- if (editor === "code") {
269
- console.error("πŸ’‘ Open VSCode and run Command Palette β†’ \"Shell Command: Install 'code' command in PATH\".");
270
- } else {
271
- console.error(`πŸ’‘ Make sure the "${editor}" CLI is installed and on your PATH, or pass a different --editor.`);
272
- }
286
+ reportEditorNotFound(editor);
273
287
  } else {
274
288
  console.error(`❌ Failed to launch "${editor}": ${err.message}`);
275
289
  }
@@ -281,13 +295,10 @@ const handleInstallExtension = async (editor: string, verbose: boolean): Promise
281
295
  console.log("βœ… Extension installed. Reload your editor window to activate.");
282
296
  return;
283
297
  }
298
+ // 127 = shell's "command not found". With shell:true the spawn 'error'
299
+ // event never fires for missing CLIs, so we surface the same hint here.
284
300
  if (code === 127) {
285
- console.error(`❌ "${editor}" CLI not found in PATH.`);
286
- if (editor === "code") {
287
- console.error("πŸ’‘ Open VSCode and run Command Palette β†’ \"Shell Command: Install 'code' command in PATH\".");
288
- } else {
289
- console.error(`πŸ’‘ Make sure the "${editor}" CLI is installed and on your PATH, or pass a different --editor.`);
290
- }
301
+ reportEditorNotFound(editor);
291
302
  process.exit(1);
292
303
  }
293
304
  console.error(`❌ "${editor} --install-extension" exited with code ${code}.`);