at-builder 1.4.5 → 1.5.0

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.
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.trackTelemetry = void 0;
43
+ var os_1 = __importDefault(require("os"));
44
+ var fs_1 = __importDefault(require("fs"));
45
+ var path_1 = __importDefault(require("path"));
46
+ var crypto_1 = __importDefault(require("crypto"));
47
+ var child_process_1 = require("child_process");
48
+ var axios_1 = __importDefault(require("axios"));
49
+ var config_1 = require("../constants/config");
50
+ var logger_1 = __importDefault(require("./logger"));
51
+ // Generate a unique identifier for this CLI execution run
52
+ var eventId = crypto_1.default.randomUUID();
53
+ var startTime = performance.now();
54
+ // Cache user details to avoid multiple shell exec calls
55
+ var cachedUser = null;
56
+ var getUserDetails = function () {
57
+ if (cachedUser)
58
+ return cachedUser;
59
+ var osUsername = os_1.default.userInfo().username || process.env.USER || process.env.USERNAME || "unknown";
60
+ var gitEmail;
61
+ var gitName;
62
+ try {
63
+ gitEmail = (0, child_process_1.execSync)("git config user.email", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
64
+ }
65
+ catch (e) {
66
+ // Git config email not set or not in a git repo
67
+ }
68
+ try {
69
+ gitName = (0, child_process_1.execSync)("git config user.name", { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
70
+ }
71
+ catch (e) {
72
+ // Git config name not set or not in a git repo
73
+ }
74
+ cachedUser = {
75
+ osUsername: osUsername,
76
+ gitEmail: gitEmail || undefined,
77
+ gitName: gitName || undefined,
78
+ };
79
+ return cachedUser;
80
+ };
81
+ // Sanitize command line arguments to avoid logging sensitive information
82
+ var sanitizeArgs = function (args) {
83
+ return args.map(function (arg) {
84
+ // Redact any argument value that looks like a secret, token, key or password
85
+ var lowercaseArg = arg.toLowerCase();
86
+ if (lowercaseArg.includes("secret") ||
87
+ lowercaseArg.includes("token") ||
88
+ lowercaseArg.includes("key") ||
89
+ lowercaseArg.includes("password")) {
90
+ // If it's an option value like --token=12345 or similar, redact the value part
91
+ if (arg.includes("=")) {
92
+ var parts = arg.split("=");
93
+ return "".concat(parts[0], "=[REDACTED]");
94
+ }
95
+ return "[REDACTED]";
96
+ }
97
+ return arg;
98
+ });
99
+ };
100
+ /**
101
+ * Retrieves or generates a persistent client ID for Google Analytics user identification.
102
+ */
103
+ var getClientId = function () {
104
+ try {
105
+ var telemetryDir = path_1.default.join(os_1.default.homedir(), ".atb-builder");
106
+ var clientIdFile = path_1.default.join(telemetryDir, "client_id");
107
+ if (!fs_1.default.existsSync(telemetryDir)) {
108
+ fs_1.default.mkdirSync(telemetryDir, { recursive: true });
109
+ }
110
+ if (fs_1.default.existsSync(clientIdFile)) {
111
+ return fs_1.default.readFileSync(clientIdFile, "utf8").trim();
112
+ }
113
+ var newClientId = crypto_1.default.randomUUID();
114
+ fs_1.default.writeFileSync(clientIdFile, newClientId, "utf8");
115
+ return newClientId;
116
+ }
117
+ catch (e) {
118
+ return crypto_1.default.randomUUID(); // Fallback to transient random if file access fails
119
+ }
120
+ };
121
+ /**
122
+ * Sends telemetry event to a remote custom endpoint if configured.
123
+ */
124
+ var sendRemoteTelemetry = function (url, payload) { return __awaiter(void 0, void 0, void 0, function () {
125
+ var error_1;
126
+ return __generator(this, function (_a) {
127
+ switch (_a.label) {
128
+ case 0:
129
+ _a.trys.push([0, 2, , 3]);
130
+ logger_1.default.info("telemetry", "Sending telemetry to ".concat(url));
131
+ // We set a 500ms timeout to ensure we don't hold up CLI execution.
132
+ return [4 /*yield*/, axios_1.default.post(url, payload, {
133
+ headers: { "Content-Type": "application/json" },
134
+ timeout: 500,
135
+ })];
136
+ case 1:
137
+ // We set a 500ms timeout to ensure we don't hold up CLI execution.
138
+ _a.sent();
139
+ logger_1.default.info("telemetry", "Remote telemetry event successfully sent");
140
+ return [3 /*break*/, 3];
141
+ case 2:
142
+ error_1 = _a.sent();
143
+ // Silence telemetry send errors so the CLI never fails due to tracking issues
144
+ logger_1.default.error("telemetry", "Failed to send remote telemetry: ".concat(error_1.message));
145
+ return [3 /*break*/, 3];
146
+ case 3: return [2 /*return*/];
147
+ }
148
+ });
149
+ }); };
150
+ /**
151
+ * Sends telemetry event to Google Analytics 4 via Measurement Protocol.
152
+ */
153
+ var sendGA4Telemetry = function (measurementId, apiSecret, payload) { return __awaiter(void 0, void 0, void 0, function () {
154
+ var url, clientId, userEmail, gaPayload, error_2;
155
+ return __generator(this, function (_a) {
156
+ switch (_a.label) {
157
+ case 0:
158
+ // If placeholders are present, do not attempt to send
159
+ if (!measurementId ||
160
+ measurementId === "YOUR_GA_MEASUREMENT_ID" ||
161
+ !apiSecret ||
162
+ apiSecret === "YOUR_GA_API_SECRET") {
163
+ logger_1.default.info("telemetry", "GA4 telemetry credentials not configured");
164
+ return [2 /*return*/];
165
+ }
166
+ _a.label = 1;
167
+ case 1:
168
+ _a.trys.push([1, 3, , 4]);
169
+ url = "https://www.google-analytics.com/mp/collect?measurement_id=".concat(measurementId, "&api_secret=").concat(apiSecret);
170
+ clientId = getClientId();
171
+ userEmail = payload.user.gitEmail || payload.user.osUsername;
172
+ gaPayload = {
173
+ client_id: clientId,
174
+ user_id: userEmail,
175
+ events: [
176
+ {
177
+ name: "atb_command_run",
178
+ params: {
179
+ command: payload.command,
180
+ status: payload.execution.status,
181
+ duration_ms: payload.execution.durationMs || 0,
182
+ error_message: payload.execution.errorMessage || "",
183
+ os_username: payload.user.osUsername,
184
+ git_email: payload.user.gitEmail || "",
185
+ git_name: payload.user.gitName || "",
186
+ project_name: payload.project.projectName,
187
+ project_path: payload.project.projectPath,
188
+ tenant: payload.project.tenant,
189
+ platform: payload.system.platform,
190
+ node_version: payload.system.nodeVersion,
191
+ cli_version: payload.system.cliVersion,
192
+ },
193
+ },
194
+ ],
195
+ };
196
+ logger_1.default.info("telemetry", "Sending GA4 event for command: ".concat(payload.command));
197
+ return [4 /*yield*/, axios_1.default.post(url, gaPayload, {
198
+ headers: { "Content-Type": "application/json" },
199
+ timeout: 800, // 800ms timeout for GA4 request
200
+ })];
201
+ case 2:
202
+ _a.sent();
203
+ logger_1.default.info("telemetry", "GA4 telemetry event successfully sent");
204
+ return [3 /*break*/, 4];
205
+ case 3:
206
+ error_2 = _a.sent();
207
+ logger_1.default.error("telemetry", "Failed to send GA4 telemetry: ".concat(error_2.message));
208
+ return [3 /*break*/, 4];
209
+ case 4: return [2 /*return*/];
210
+ }
211
+ });
212
+ }); };
213
+ /**
214
+ * Appends telemetry event to a local log file in the user's home directory.
215
+ */
216
+ var writeLocalTelemetry = function (payload) {
217
+ try {
218
+ var telemetryDir = path_1.default.join(os_1.default.homedir(), ".atb-builder");
219
+ var telemetryFile = path_1.default.join(telemetryDir, "telemetry.jsonl");
220
+ if (!fs_1.default.existsSync(telemetryDir)) {
221
+ fs_1.default.mkdirSync(telemetryDir, { recursive: true });
222
+ }
223
+ fs_1.default.appendFileSync(telemetryFile, JSON.stringify(payload) + "\n", "utf8");
224
+ logger_1.default.info("telemetry", "Appended telemetry to ".concat(telemetryFile));
225
+ }
226
+ catch (error) {
227
+ logger_1.default.error("telemetry", "Failed to write local telemetry: ".concat(error.message));
228
+ }
229
+ };
230
+ /**
231
+ * Tracks a CLI execution event.
232
+ *
233
+ * @param command - The command name
234
+ * @param status - Execution status
235
+ * @param err - Error if the status is "failed"
236
+ */
237
+ var trackTelemetry = function (command, status, err) { return __awaiter(void 0, void 0, void 0, function () {
238
+ var cwd, envVars, e_1, isEnabled, cliVersion, userDetails, customUser, durationMs, tenant, projectName, payload, telemetryUrl, measurementId, apiSecret, e_2;
239
+ return __generator(this, function (_a) {
240
+ switch (_a.label) {
241
+ case 0:
242
+ _a.trys.push([0, 10, , 11]);
243
+ cwd = process.cwd();
244
+ envVars = {};
245
+ _a.label = 1;
246
+ case 1:
247
+ _a.trys.push([1, 3, , 4]);
248
+ return [4 /*yield*/, (0, config_1.getENV)(cwd)];
249
+ case 2:
250
+ envVars = _a.sent();
251
+ return [3 /*break*/, 4];
252
+ case 3:
253
+ e_1 = _a.sent();
254
+ return [3 /*break*/, 4];
255
+ case 4:
256
+ isEnabled = envVars.TELEMETRY_ENABLED !== "false" && process.env.TELEMETRY_ENABLED !== "false";
257
+ if (!isEnabled) {
258
+ logger_1.default.info("telemetry", "Telemetry is disabled via configuration");
259
+ return [2 /*return*/];
260
+ }
261
+ return [4 /*yield*/, (0, config_1.getVersion)()];
262
+ case 5:
263
+ cliVersion = _a.sent();
264
+ userDetails = getUserDetails();
265
+ customUser = envVars.TELEMETRY_USER || process.env.TELEMETRY_USER;
266
+ if (customUser) {
267
+ userDetails.customUser = customUser;
268
+ }
269
+ durationMs = status !== "started" ? Math.round(performance.now() - startTime) : undefined;
270
+ tenant = envVars.ADOBE_TENANT || "";
271
+ projectName = path_1.default.basename(cwd);
272
+ payload = {
273
+ eventId: eventId,
274
+ timestamp: new Date().toISOString(),
275
+ command: command,
276
+ args: sanitizeArgs(process.argv.slice(2)), // capture all command line arguments starting from index 2
277
+ user: userDetails,
278
+ project: {
279
+ projectName: projectName,
280
+ projectPath: cwd,
281
+ tenant: tenant,
282
+ },
283
+ execution: {
284
+ status: status,
285
+ durationMs: durationMs,
286
+ errorMessage: err === null || err === void 0 ? void 0 : err.message,
287
+ },
288
+ system: {
289
+ platform: os_1.default.platform(),
290
+ nodeVersion: process.version,
291
+ cliVersion: cliVersion,
292
+ },
293
+ };
294
+ // Always write to local log file for auditing
295
+ writeLocalTelemetry(payload);
296
+ if (!(status !== "started")) return [3 /*break*/, 9];
297
+ telemetryUrl = envVars.TELEMETRY_URL || process.env.TELEMETRY_URL;
298
+ if (!telemetryUrl) return [3 /*break*/, 7];
299
+ return [4 /*yield*/, sendRemoteTelemetry(telemetryUrl, payload)];
300
+ case 6:
301
+ _a.sent();
302
+ _a.label = 7;
303
+ case 7:
304
+ measurementId = envVars.GA_MEASUREMENT_ID || process.env.GA_MEASUREMENT_ID || "G-K9GL7DDSE5";
305
+ apiSecret = envVars.GA_API_SECRET || process.env.GA_API_SECRET || "WVnUPXFAR2GRtOEn_0EDPg";
306
+ return [4 /*yield*/, sendGA4Telemetry(measurementId, apiSecret, payload)];
307
+ case 8:
308
+ _a.sent();
309
+ _a.label = 9;
310
+ case 9: return [3 /*break*/, 11];
311
+ case 10:
312
+ e_2 = _a.sent();
313
+ // Catch-all to make sure telemetry never crashes CLI commands
314
+ logger_1.default.error("telemetry", "Error in telemetry tracker: ".concat(e_2.message));
315
+ return [3 /*break*/, 11];
316
+ case 11: return [2 /*return*/];
317
+ }
318
+ });
319
+ }); };
320
+ exports.trackTelemetry = trackTelemetry;
package/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "at-builder",
3
- "version": "1.4.5",
3
+ "version": "1.5.0",
4
4
  "main": "bin/index.js",
5
5
  "bin": {
6
6
  "atb": "bin/index.js"
7
7
  },
8
+ "files": [
9
+ "bin/",
10
+ "lib/",
11
+ ".plop/",
12
+ "plopfile.js",
13
+ "puppeteer.js",
14
+ "webpack.config.js",
15
+ "babel.config.js",
16
+ "eslint.config.js",
17
+ "tsconfig.json"
18
+ ],
8
19
  "scripts": {
9
20
  "build:atb": "tsc",
10
21
  "build:atb:dev": "tsc -w",
@@ -1,6 +0,0 @@
1
- {
2
- "Slingshot.enableAutomaticCreationOfFilesWithFilenameClick": true,
3
- "[handlebars]": {
4
- "editor.formatOnSave": false
5
- }
6
- }
package/DEVELOPMENT.md DELETED
@@ -1,164 +0,0 @@
1
- # Local Development
2
-
3
- Notes-to-self on how to test `at-builder` against a real consumer project before publishing to npm. Not for end users — this is internal.
4
-
5
- ---
6
-
7
- ## TL;DR
8
-
9
- ```bash
10
- # one-time setup in this repo
11
- yarn link
12
- yarn build:atb:dev & # leave running; recompiles TS on save
13
-
14
- # work against the bundled test/ sandbox (already gitignored, pre-populated)
15
- cd test
16
- atb doctor
17
- atb new
18
- atb dev --browser
19
- # ... etc
20
- ```
21
-
22
- When done:
23
-
24
- ```bash
25
- # in at-builder
26
- yarn unlink
27
- ```
28
-
29
- ---
30
-
31
- ## Two patterns
32
-
33
- ### 1. `yarn link` — active iteration
34
-
35
- Symlinks the global `atb` binary to this repo. Edits to source are picked up on the next invocation (after rebuild for TypeScript).
36
-
37
- ```bash
38
- # in at-builder
39
- yarn link # or: npm link
40
- ```
41
-
42
- `atb` is now a global command pointing at this repo. Run it from any directory.
43
-
44
- To remove the symlink later:
45
-
46
- ```bash
47
- yarn unlink # in at-builder
48
- yarn unlink at-builder # in any project that also linked it (rarely needed)
49
- ```
50
-
51
- ### 2. `npm pack` — pre-publish verification
52
-
53
- `yarn link` uses symlinks, which can paper over packaging issues — forgotten `.npmignore` exclusions, missing bundled assets (like `at-builder-*.vsix`), files declared in `package.json` but not actually present, etc. Before `npm publish`, do this to test the **actual artifact** npm will ship:
54
-
55
- ```bash
56
- # in at-builder
57
- yarn build:atb # ensure bin/ is fresh
58
- npm pack # produces at-builder-<version>.tgz
59
-
60
- # in a clean throwaway directory
61
- mkdir /tmp/atb-publish-test && cd /tmp/atb-publish-test
62
- npm install -g /path/to/at-builder/at-builder-<version>.tgz
63
-
64
- # verify the published surface
65
- which atb
66
- atb --version
67
- atb install-extension --editor code # confirms the bundled .vsix actually shipped
68
- atb init
69
- # ... exercise other commands
70
-
71
- # cleanup
72
- npm uninstall -g at-builder
73
- rm /path/to/at-builder/at-builder-<version>.tgz
74
- ```
75
-
76
- If anything's missing from the tarball, this catches it before users do.
77
-
78
- ---
79
-
80
- ## What needs rebuilding
81
-
82
- The single thing that bites people most often:
83
-
84
- | You edit | Rebuild needed? |
85
- |---|---|
86
- | `src/**/*.ts` (CLI surface) | **Yes** — `yarn build:atb` (one-shot) or `yarn build:atb:dev` (`tsc -w`) running in another terminal |
87
- | `lib/at-deploy.js`, `lib/at-sync.js` | No — plain JS, picked up directly on next `atb` invocation |
88
- | `puppeteer.js` | No |
89
- | `webpack.config.js` | No |
90
- | `.plop/generators/*`, `.plop/templates/*` | No |
91
- | `babel.config.js`, `eslint.config.js`, `tsconfig.json` | No |
92
-
93
- Easiest workflow: open a second terminal in this repo and leave `yarn build:atb:dev` running. Then any `src/` edit is reflected on the next `atb` invocation in your test project.
94
-
95
- ---
96
-
97
- ## The bundled `test/` sandbox
98
-
99
- This repo ships a `test/` directory (gitignored) pre-populated with `.env`, `adobe.config.js`, `.gitignore`, and `package.json` — basically what `atb init` would create. Use it as a throwaway consumer project:
100
-
101
- ```bash
102
- cd test
103
- atb doctor # exercises all checks
104
- atb doctor --fix # exercises all auto-fixes (incl. watch-config migration)
105
- atb new # exercise the prompts (AB/XT, page names)
106
- atb sync # if you've set activityInfo.id
107
- atb dev --browser # exercise the puppeteer fixes
108
- atb deploy --dry-run # test deploy logic without PUTting to AT
109
- atb install-extension # test the .vsix install flow
110
- ```
111
-
112
- If `test/` ever gets corrupted, just delete its contents and re-run `atb init` — it'll regenerate from scratch.
113
-
114
- ---
115
-
116
- ## Publishing checklist
117
-
118
- Before `npm publish`:
119
-
120
- 1. `yarn build:atb` — ensure `bin/` is fresh and committed.
121
- 2. Bump `version` in `package.json`. Use semver:
122
- - **patch** — bug fixes, doc changes
123
- - **minor** — new commands, new flags, backwards-compatible features
124
- - **major** — breaking schema changes (e.g. requiring fields, renaming commands)
125
- 3. `npm pack` and run the verification block above.
126
- 4. `git tag v<version>` + `git push --tags`.
127
- 5. `npm publish`.
128
-
129
- ---
130
-
131
- ## Common gotchas
132
-
133
- - **`atb` runs the wrong copy (link vs global)** — you have at-builder installed globally from npm AND linked locally. Run `which atb` and `atb --version`; if it doesn't match the local repo, `yarn unlink at-builder` (in linked consumer dirs) and `npm uninstall -g at-builder` to clear stale globals before re-linking.
134
- - **`atb` resolves to a stale install in a *different* npm prefix** — distinct from the link-vs-global case above. It's easy to end up with at-builder installed at multiple locations (e.g. `/usr/local/lib/node_modules/at-builder` from a system npm AND `~/.nvm/versions/node/<v>/lib/node_modules/at-builder` from nvm). `npm install -g` only writes to the prefix the *currently-active* `npm` uses; a stale install in the *other* prefix can still come first on PATH. Diagnose:
135
-
136
- ```bash
137
- which -a atb # every atb on PATH
138
- npm config get prefix # where the active npm installs
139
- ls -la $(which atb) # see where the symlink points
140
- ```
141
-
142
- If `which -a` lists multiple entries, remove the stale one(s):
143
-
144
- ```bash
145
- # active prefix is nvm but a stale /usr/local install is shadowing it:
146
- rm -f /usr/local/bin/atb
147
- rm -rf /usr/local/lib/node_modules/at-builder
148
-
149
- # or the reverse:
150
- rm -f ~/.nvm/versions/node/<v>/bin/atb
151
- rm -rf ~/.nvm/versions/node/<v>/lib/node_modules/at-builder
152
- ```
153
-
154
- Then reinstall once: `npm install -g /path/to/at-builder-<version>.tgz`.
155
-
156
- - **`atb --version` shows the same number after re-packing** — `npm pack` honors whatever's in `package.json`; it doesn't bump for you. If you forgot to update the version before packing, the tarball reports the same number as the previously-published release and `atb --version` looks like nothing changed even though the code did. Always bump `package.json` first (see the publishing checklist for the semver rules), then re-pack. Verify before installing:
157
-
158
- ```bash
159
- tar xzOf at-builder-<version>.tgz package/package.json | grep '"version"'
160
- ```
161
-
162
- - **Edits to `src/` not reflected** — `bin/` is stale. Run `yarn build:atb` or check `yarn build:atb:dev` is actually running.
163
- - **`npm install` exits 1** — historical issue, the `postinstall` script referenced a missing `bin/postinstall.js`. Already fixed (commit `d3fe05e`); if it resurfaces, drop the `postinstall` line from `package.json` again.
164
- - **`.vsix` not in tarball** — verify with `npm pack --dry-run` before publishing. The `.vsix` lives at the repo root and is included by default (no `files` field, no `.npmignore` excluding it). If you ever add a `files` field, remember to whitelist `at-builder-*.vsix`.
Binary file