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.
- package/.claude/settings.local.json +20 -1
- package/.plop/generators/prompts.js +10 -3
- package/.plop/templates/analytics.hbs +1 -1
- package/.plop/utils/index.js +24 -4
- package/README.md +1 -1
- package/bin/constants/config.js +14 -18
- package/bin/index.js +29 -44
- package/lib/at-deploy.js +2 -3
- package/lib/at-sync.js +2 -3
- package/lib/eslint-flat-config-plugin.js +125 -13
- package/package.json +2 -7
- package/src/constants/config.ts +28 -30
- package/src/index.ts +24 -13
|
@@ -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
|
-
|
|
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
|
-
|
|
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:
|
|
65
|
+
filter: trim
|
|
59
66
|
}
|
|
60
67
|
]);
|
|
61
68
|
};
|
package/.plop/utils/index.js
CHANGED
|
@@ -1,8 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
package/bin/constants/config.js
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
197
|
-
*
|
|
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,
|
|
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
|
|
214
|
+
case 1:
|
|
214
215
|
envContent = _a.sent();
|
|
215
|
-
|
|
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
|
-
|
|
225
|
+
envVars[key.trim()] = value.replace(/^["'](.*)["']$/, '$1');
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
});
|
|
228
|
-
return [2 /*return*/,
|
|
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
|
|
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
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
*
|
|
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
|
|
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
|
|
397
|
-
|
|
398
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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.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
|
-
"@
|
|
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",
|
package/src/constants/config.ts
CHANGED
|
@@ -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
|
|
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
|
-
*
|
|
283
|
-
*
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
302
|
-
const
|
|
303
|
-
if
|
|
304
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}.`);
|