at-builder 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,318 @@
1
+ /* eslint-disable no-undef */
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
3
+
4
+ /*
5
+ * Adobe Target Build Deploy Script
6
+ * --------------------------------
7
+ * This script automates the deployment of build assets to an Adobe Target activity.
8
+ *
9
+ * Features:
10
+ * - Reads build config and allowed variations from build.config.json in the build folder.
11
+ * - Dynamically finds and injects built HTML assets for each experience/variation.
12
+ * - Updates only the allowed experiences in Adobe Target.
13
+ * - Provides color-coded logs and a summary of what was updated, skipped, or unchanged.
14
+ * - Can be run in verbose mode to show more details (use -v or --verbose).
15
+ *
16
+ * Usage:
17
+ * node at-deploy.js [-v|--verbose]
18
+ *
19
+ * Environment variables required:
20
+ * ACTIVITY_FOLDER_NAME, ADOBE_CLIENT_ID, ADOBE_CLIENT_SECRET
21
+ * (and .env file for any secrets)
22
+ *
23
+ * Example:
24
+ * ACTIVITY_FOLDER_NAME="UPSDDO-XXXX" \
25
+ * ADOBE_CLIENT_ID=... ADOBE_CLIENT_SECRET=... node at-deploy.js -v
26
+ *
27
+ * Maintainers: Update build.config.json and build output structure as needed for new activities.
28
+ *
29
+ * Author: Upendra Sengar
30
+ * Date: 2024-03-12
31
+ */
32
+
33
+ /* eslint-env node */
34
+ const path = require('path');
35
+
36
+ const dotenv = require('dotenv');
37
+
38
+ const PWD = process.env.executionPath || process.cwd();
39
+
40
+ dotenv.config({ path: path.join(PWD, ".env"), quiet: true });
41
+
42
+ const { execSync } = require('child_process');
43
+ const axios = require('axios');
44
+ const qs = require('querystring');
45
+ const fs = require('fs');
46
+
47
+ const { BASE_URL, IMS_TOKEN_URL, IMS_SCOPE } = require(path.join(PWD, 'adobe.config'));
48
+ // Fallback to ANSI codes since chalk 5.x is ESM only
49
+ const chalk = {
50
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
51
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
52
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
53
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
54
+ bold: (s) => `\x1b[1m${s}\x1b[0m`
55
+ };
56
+
57
+ // Validate required environment variables
58
+ if (!process.env.ACTIVITY_FOLDER_NAME || process.env.ACTIVITY_FOLDER_NAME.trim() === '') {
59
+ console.error(chalk.red('āŒ Error: ACTIVITY_FOLDER_NAME environment variable is required but not set.'));
60
+ console.error(chalk.yellow('šŸ’” Please set ACTIVITY_FOLDER_NAME in your .env file or run "atb doctor --fix" to check your configuration.'));
61
+ process.exit(1);
62
+ }
63
+
64
+ // Build root directory for variations, using same approach as webpack config
65
+ const ACTIVITIES_BASE_FOLDER = process.env.ACTIVITIES_BASE_FOLDER || 'Activities';
66
+ const ACTIVITY_FOLDER = process.env.ACTIVITY_FOLDER_NAME.trim();
67
+ const BUILD_ROOT = path.join(PWD, ACTIVITIES_BASE_FOLDER, ACTIVITY_FOLDER).toString();
68
+
69
+ // Validate Adobe credentials
70
+ if (!process.env.ADOBE_CLIENT_ID || process.env.ADOBE_CLIENT_ID.trim() === '') {
71
+ console.error(chalk.red('āŒ Error: ADOBE_CLIENT_ID environment variable is required but not set.'));
72
+ console.error(chalk.yellow('šŸ’” Please set your Adobe Target API credentials in your .env file.'));
73
+ process.exit(1);
74
+ }
75
+
76
+ if (!process.env.ADOBE_CLIENT_SECRET || process.env.ADOBE_CLIENT_SECRET.trim() === '') {
77
+ console.error(chalk.red('āŒ Error: ADOBE_CLIENT_SECRET environment variable is required but not set.'));
78
+ console.error(chalk.yellow('šŸ’” Please set your Adobe Target API credentials in your .env file.'));
79
+ process.exit(1);
80
+ }
81
+
82
+ console.log(chalk.cyan(`šŸ“¦ Deploying activity: ${ACTIVITY_FOLDER}`));
83
+ console.log(chalk.cyan(`šŸ“ Activities folder: ${ACTIVITIES_BASE_FOLDER}`));
84
+
85
+ // Utility: normalize experience name from folder name
86
+ function normalizeExperienceName(folder) {
87
+ if (!folder) return '';
88
+ const name = folder.toLowerCase();
89
+ if (name === 'vcontrol') return 'control';
90
+ if (name.startsWith('variation-')) return 'variation ' + name.split('-')[1];
91
+ return name;
92
+ }
93
+
94
+ // Read build.config.json for activity id and allowed variations
95
+ function readBuildJson() {
96
+ const buildJsonPath = path.join(BUILD_ROOT, "shared", 'build.config.json');
97
+ if (!fs.existsSync(buildJsonPath)) {
98
+ throw new Error(`build.json not found at ${buildJsonPath}`);
99
+ }
100
+ const buildJson = JSON.parse(fs.readFileSync(buildJsonPath, 'utf8'));
101
+ const activityId = buildJson.activityInfo?.id;
102
+ if (activityId === null) {
103
+ throw new Error('Invalid build.config.json: missing activity id');
104
+ }
105
+ console.log('buildJson.activityInfo?.variations', buildJson.activityInfo?.variations);
106
+ // To support older versions , keeping array checks
107
+ let allowedVariations = buildJson.activityInfo?.variations;
108
+ if (buildJson.activityInfo?.variations && Array.isArray(buildJson.activityInfo.variations)) {
109
+ // convert to object
110
+ allowedVariations = Object.fromEntries((buildJson.activityInfo?.variations || []).map(v => [v, v]));
111
+ }
112
+
113
+ console.log('allowedVariations', allowedVariations);
114
+
115
+ if (!activityId || !allowedVariations || Object.keys(allowedVariations).length === 0) {
116
+ throw new Error('Invalid build.json: missing activity id or variations');
117
+ }
118
+ const activityName = buildJson.activityInfo?.name.toLowerCase();
119
+ if (!activityName) {
120
+ throw new Error('Invalid build.config.json: missing activity name');
121
+ }
122
+ return { activityId, allowedVariations, activityName };
123
+ }
124
+
125
+ // Dynamically discover build assets for each experience/variation
126
+ function getBuildAssets() {
127
+ const buildAssets = {};
128
+ if (!fs.existsSync(BUILD_ROOT)) {
129
+ console.error('Build root does not exist:', BUILD_ROOT);
130
+ return buildAssets;
131
+ }
132
+ const subdirs = fs.readdirSync(BUILD_ROOT, { withFileTypes: true })
133
+ .filter(dirent => dirent.isDirectory());
134
+ for (const dirent of subdirs) {
135
+ const subdir = dirent.name;
136
+ const distPath = path.join(BUILD_ROOT, subdir, 'dist', 'at-build.html');
137
+ if (fs.existsSync(distPath)) {
138
+ const expName = normalizeExperienceName(subdir);
139
+ buildAssets[expName] = fs.readFileSync(distPath, 'utf8');
140
+ }
141
+ }
142
+ return buildAssets;
143
+ }
144
+
145
+ // Fetch Adobe OAuth token using client credentials
146
+ async function fetchAdobeToken() {
147
+ const data = qs.stringify({
148
+ grant_type: 'client_credentials',
149
+ client_id: process.env.ADOBE_CLIENT_ID,
150
+ client_secret: process.env.ADOBE_CLIENT_SECRET,
151
+ scope: IMS_SCOPE
152
+ });
153
+ const headers = {
154
+ 'Content-Type': 'application/x-www-form-urlencoded'
155
+ };
156
+ try {
157
+ const res = await axios.post(IMS_TOKEN_URL, data, { headers });
158
+ return res.data.access_token;
159
+ } catch (err) {
160
+ console.error('Failed to fetch Adobe token:', err.response ? err.response.data : err.message);
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ // 1. Run the build (calls your build system, e.g. webpack)
166
+ console.log('Running build...');
167
+ execSync('npm run atb:build:prod', { stdio: 'inherit' });
168
+
169
+ (async () => {
170
+ try {
171
+ // Read build config and allowed variations
172
+ const { activityId, allowedVariations, activityName } = readBuildJson();
173
+ let listOfAllowedVariations = [];
174
+
175
+ listOfAllowedVariations = Object.keys(allowedVariations).map(v => v.toLowerCase());
176
+
177
+ const allowedVariationReversedKey = Object.fromEntries(Object.entries(allowedVariations).map(([key, value]) => [value.toLowerCase(), key.toLowerCase()]));
178
+
179
+ const API_KEY = process.env.ADOBE_CLIENT_ID;
180
+ const token = await fetchAdobeToken();
181
+
182
+ // 2. Fetch current Adobe Target activity
183
+ const getActivity = async (activityId) => {
184
+ const res = await axios.get(`${BASE_URL}${activityId}`, {
185
+ headers: {
186
+ 'X-Api-Key': API_KEY,
187
+ Accept: 'application/vnd.adobe.target.v3+json',
188
+ Authorization: `Bearer ${token}`
189
+ }
190
+ });
191
+ return res.data;
192
+ };
193
+
194
+ // 3. Update activity with new build info
195
+ const updateActivity = async (activity, activityId) => {
196
+ if (activity.state === "approved") {
197
+ /*
198
+ Status Mapping Logic for UI Display:
199
+
200
+ API State | Start At | End At | UI Status
201
+ -------------------------------------------------------------
202
+ Approved | in the past | in the future | Live
203
+ Approved | in the past | in the past | Ended
204
+ Approved | in the future | in the future | Scheduled
205
+ Saved | - | - | Inactive
206
+ Deactivated | - | - | Archived
207
+ Deleted | - | - | Does Not Appear in the UI
208
+
209
+ Notes:
210
+ - "Start At" and "End At" are datetime values used to determine temporal status.
211
+ - For states with "-", time values are not evaluated.
212
+ - Items with "Deleted" status should be filtered out entirely from the UI.
213
+ */
214
+
215
+ throw new Error("Live activity can't be updated");
216
+ }
217
+ // Dynamically get build assets
218
+ const buildAssets = getBuildAssets();
219
+
220
+ // Prepare new payload (copy structure)
221
+ const newPayload = {
222
+ name: activity.name,
223
+ options: activity.options,
224
+ locations: activity.locations,
225
+ experiences: activity.experiences,
226
+ metrics: activity.metrics,
227
+ analytics: activity.analytics
228
+ };
229
+
230
+ if (activityName !== activity.name.toLowerCase()) {
231
+ throw new Error('Activity name in build.config.json does not match activity name in Adobe Target');
232
+ }
233
+
234
+ const updatedExperiences = [];
235
+ const skippedExperiences = [];
236
+ const unchangedExperiences = [];
237
+ let anyChange = false;
238
+
239
+ // For each experience, update only if in allowed variations and asset exists
240
+ newPayload.experiences.forEach(exp => {
241
+ let expName = exp.name.trim().toLowerCase();
242
+ // Added to handle new type of build config
243
+ if (allowedVariationReversedKey) {
244
+ expName = allowedVariationReversedKey[expName] || expName;
245
+ }
246
+
247
+ if (!listOfAllowedVariations.includes(expName)) {
248
+ skippedExperiences.push({ name: exp.name, reason: 'Not in allowed variations in build.config.json' });
249
+ return;
250
+ }
251
+
252
+ if (!buildAssets[expName]) {
253
+ skippedExperiences.push({ name: exp.name, reason: 'No build asset found' });
254
+ return;
255
+ }
256
+ let changed = false;
257
+ exp.optionLocations.forEach(ol => {
258
+ const { optionLocalId } = ol;
259
+ const option = newPayload.options.find(o => o.optionLocalId === optionLocalId);
260
+ if (!option) return;
261
+ option.offerTemplates.forEach(offerTemplate => {
262
+ if (offerTemplate.offerTemplateId === 133) {
263
+ offerTemplate.templateParameters.forEach(param => {
264
+ if (param.name === 'offerContent') {
265
+ if (param.value !== buildAssets[expName]) {
266
+ param.value = buildAssets[expName];
267
+ changed = true;
268
+ anyChange = true;
269
+ }
270
+ }
271
+ });
272
+ }
273
+ });
274
+ });
275
+ if (changed) {
276
+ updatedExperiences.push(exp.name);
277
+ } else {
278
+ unchangedExperiences.push(exp.name);
279
+ }
280
+ });
281
+
282
+ // Logging summary
283
+ console.log(chalk.bold('--- Experience Update Summary ---'));
284
+ if (updatedExperiences.length > 0) {
285
+ console.log(chalk.green('Updated experiences:'), updatedExperiences.join(', '));
286
+ }
287
+ if (unchangedExperiences.length > 0) {
288
+ console.log(chalk.cyan('No changes needed for experiences:'), unchangedExperiences.join(', '));
289
+ }
290
+ if (skippedExperiences.length > 0) {
291
+ skippedExperiences.forEach(e => {
292
+ console.log(chalk.yellow(`Skipped experience: ${e.name} (${e.reason})`));
293
+ });
294
+ }
295
+ if (!anyChange) {
296
+ console.log(chalk.cyan('No changes were made to any experience.'));
297
+ } else {
298
+ // Uncomment to actually update Adobe Target
299
+ await axios.put(`${BASE_URL}${activityId}`, newPayload, {
300
+ headers: {
301
+ 'X-Api-Key': API_KEY,
302
+ 'Content-Type': 'application/vnd.adobe.target.v3+json',
303
+ Authorization: `Bearer ${token}`
304
+ }
305
+ });
306
+ console.log('New build deployed!');
307
+ }
308
+ console.log(chalk.bold('---------------------------------'));
309
+ };
310
+
311
+ const activity = await getActivity(activityId);
312
+ // console.log('Current activity:', activity);
313
+ await updateActivity(activity, activityId);
314
+ } catch (err) {
315
+ console.error(chalk.red('Error:'), err.response ? err.response.data : err.message);
316
+ process.exit(1);
317
+ }
318
+ })();
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "at-builder",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "main": "bin/index.js",
5
5
  "bin": {
6
6
  "atb": "bin/index.js"
7
7
  },
8
8
  "scripts": {
9
- "build": "tsc -w",
10
- "build-prod": "cross-env NODE_ENV=production webpack",
11
- "dev": "webpack -- -w",
12
- "dev-puppeteer": "webpack -- -w & npm run puppeteer",
13
- "test": "echo \"Error: no test specified\" && exit 1",
14
- "new-activity": "plop",
15
- "puppeteer": "node puppeteer.js $b"
9
+ "build:atb": "tsc",
10
+ "build:atb:dev": "tsc -w",
11
+ "atb:build:prod": "cross-env NODE_ENV=production webpack",
12
+ "atb:build:dev": "webpack -- -w",
13
+ "atb:build:dev:puppeteer": "webpack -- -w & npm run puppeteer",
14
+ "atb:puppeteer": "node puppeteer.js $b",
15
+ "atb:plop:new:activity": "plop page",
16
+ "atb:build:deploy": "node lib/at-deploy.js"
16
17
  },
17
18
  "author": "Upendra Sengar <upendrasengar456@gmail.com>",
18
19
  "license": "MIT",
@@ -26,6 +27,7 @@
26
27
  "@babel/preset-react": "^7.18.6",
27
28
  "@types/node": "^22.13.2",
28
29
  "async": "^3.2.3",
30
+ "axios": "^1.12.2",
29
31
  "babel-loader": "^9.2.1",
30
32
  "babel-plugin-transform-async-to-generator": "^6.24.1",
31
33
  "build": "^0.1.4",
@@ -43,6 +45,7 @@
43
45
  "postcss-loader": "^8.1.1",
44
46
  "postcss-preset-env": "^10.1.4",
45
47
  "puppeteer": "^24.2.0",
48
+ "querystring": "^0.2.1",
46
49
  "readline": "^1.3.0",
47
50
  "sass": "^1.53.0",
48
51
  "sass-loader": "^16.0.4",
package/plopfile.js CHANGED
@@ -1,5 +1,11 @@
1
- const components = require("./.plop/generators/components");
1
+ /* eslint-disable no-undef */
2
+ /* eslint-disable @typescript-eslint/no-require-imports */
2
3
 
4
+ const components = require("./.plop/generators/components");
5
+ const path = require('path');
6
+ const dotenv = require('dotenv');
7
+ const PWD = process.env.executionPath || process.cwd();
8
+ dotenv.config({ path: path.join(PWD, ".env") });
3
9
 
4
10
  module.exports = function (plop) {
5
11
  plop.setGenerator('components', components );
@@ -69,6 +69,10 @@ ${formatText("COMMANDS", 'yellow', true)}
69
69
  ${formatText("build --prod", 'cyan', true)} Build for production deployment
70
70
  ${formatText("dev", 'cyan', true)} Start development server with file watching
71
71
  ${formatText("dev --browser", 'cyan', true)} Start development server and open in browser
72
+ ${formatText("deploy", 'cyan', true)} Deploy activity to Adobe Target
73
+ ${formatText("deploy --dry-run", 'cyan', true)} Deploy in dry-run mode without actual deployment
74
+ ${formatText("doctor", 'cyan', true)} Diagnose and fix project configuration issues
75
+ ${formatText("doctor --fix", 'cyan', true)} Automatically fix detected configuration issues
72
76
 
73
77
  ${formatText("GLOBAL OPTIONS", 'yellow', true)}
74
78
  ${formatText("-v, --verbose", 'green')} Enable detailed logging
@@ -90,6 +94,18 @@ ${formatText("EXAMPLES", 'yellow', true)}
90
94
 
91
95
  ${formatText("# Development server with browser", 'gray')}
92
96
  ${formatText("atb dev --browser", 'white')}
97
+
98
+ ${formatText("# Deploy to Adobe Target", 'gray')}
99
+ ${formatText("atb deploy", 'white')}
100
+
101
+ ${formatText("# Deploy in dry-run mode", 'gray')}
102
+ ${formatText("atb deploy --dry-run", 'white')}
103
+
104
+ ${formatText("# Check project configuration", 'gray')}
105
+ ${formatText("atb doctor", 'white')}
106
+
107
+ ${formatText("# Auto-fix configuration issues", 'gray')}
108
+ ${formatText("atb doctor --fix", 'white')}
93
109
 
94
110
  ${formatText("WORKFLOW", 'yellow', true)}
95
111
  ${formatText("1.", 'cyan')} Run ${formatText("atb init", 'white')} to set up project configuration
@@ -97,6 +113,7 @@ ${formatText("WORKFLOW", 'yellow', true)}
97
113
  ${formatText("3.", 'cyan')} Develop variations in ${formatText("/Activities/{name}/Variation-*/", 'gray')}
98
114
  ${formatText("4.", 'cyan')} Run ${formatText("atb build", 'white')} for development testing
99
115
  ${formatText("5.", 'cyan')} Run ${formatText("atb build --prod", 'white')} for Adobe Target deployment
116
+ ${formatText("6.", 'cyan')} Run ${formatText("atb deploy", 'white')} to deploy to Adobe Target
100
117
 
101
118
  ${formatText("SUPPORT", 'yellow', true)}
102
119
  Repository: ${formatText("https://github.com/upesenga/at-builder", 'blue')}
@@ -114,6 +131,7 @@ export const setupEnv = async (basePath) => {
114
131
  if (!fs.existsSync(envPath)) {
115
132
  // Define the content of the .env file
116
133
  const envContent = `
134
+ ACTIVITIES_BASE_FOLDER="Activities"
117
135
  ACTIVITY_FOLDER_NAME=""
118
136
  PUPPETEER_LANDING_PAGE=""
119
137
  TARGET_URL=""
@@ -121,6 +139,10 @@ LOGIN_URL=""
121
139
  VARIATION="Variation-1"
122
140
  NODE_ENV="development"
123
141
  VERBOSE=false
142
+
143
+ # Adobe Target Deployment Configuration
144
+ ADOBE_CLIENT_ID=""
145
+ ADOBE_CLIENT_SECRET=""
124
146
  `;
125
147
  // Write the content to the .env file
126
148
  fs.writeFileSync(envPath, envContent.trim(), 'utf8');
@@ -129,6 +151,7 @@ VERBOSE=false
129
151
  console.log('.env file already exists!');
130
152
  }
131
153
  createWatchConfig(basePath);
154
+ createAdobeConfig(basePath);
132
155
  }
133
156
 
134
157
 
@@ -151,6 +174,34 @@ const createWatchConfig = (basePath) => {
151
174
  }
152
175
  }
153
176
 
177
+ const createAdobeConfig = (basePath) => {
178
+ // Path to the adobe.config.js file
179
+ const adobeConfigPath = path.join(basePath, 'adobe.config.js');
180
+
181
+ // Default content for adobe.config.js
182
+ const adobeConfigContent = `/**
183
+ * Adobe Target API Configuration
184
+ *
185
+ * Configuration constants for Adobe Target API integration.
186
+ * These values are used by the deployment script to connect to Adobe Target.
187
+ */
188
+
189
+ module.exports = {
190
+ BASE_URL_NEW: 'https://mc.adobe.io/ups/target/',
191
+ BASE_URL: 'https://mc.adobe.io/ups/target/activities/ab/',
192
+ IMS_TOKEN_URL: 'https://ims-na1.adobelogin.com/ims/token/v3',
193
+ IMS_SCOPE: 'openid,AdobeID,target_sdk,additional_info.projectedProductContext'
194
+ };`;
195
+
196
+ // Check if the adobe.config.js file already exists
197
+ if (!fs.existsSync(adobeConfigPath)) {
198
+ fs.writeFileSync(adobeConfigPath, adobeConfigContent, 'utf8');
199
+ console.log('adobe.config.js file created successfully!');
200
+ } else {
201
+ console.log('adobe.config.js file already exists!');
202
+ }
203
+ }
204
+
154
205
  /**
155
206
  * Reads and returns environment variables from the specified .env file
156
207
  * @param {string} basePath - The base path where the .env file is located
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import path from "path";
4
4
  import fs from "fs";
5
5
  import { Command } from 'commander';
6
6
  import { getVersion, getHelpInfo, setupEnv } from "./constants/config";
7
+ import { runDiagnostics, fixIssues } from "./services/doctor";
7
8
  import logger from "./services/logger";
8
9
  /**
9
10
  * Checks if the .env file exists at the current working directory
@@ -17,7 +18,9 @@ const checkEnvFile = () => {
17
18
  fs.accessSync(path.join(process.cwd(), ".env"), fs.constants.R_OK);
18
19
  logger.info("checkEnvFile", ".env file found");
19
20
  } catch (error) {
20
- // If the file does not exist, throw an error
21
+ // If the file does not exist, provide helpful guidance
22
+ console.error('\x1b[31m%s\x1b[0m', `āŒ Error: .env file not found at ${process.cwd()}`);
23
+ console.error('\x1b[33m%s\x1b[0m', 'šŸ’” Run "atb init" to create .env file or "atb doctor --fix" to diagnose and fix configuration issues.');
21
24
  logger.error("checkEnvFile", `Error: Couldn't find .env file at location ${process.cwd()}`);
22
25
  throw error;
23
26
  }
@@ -78,6 +81,25 @@ const setupCommander = async () => {
78
81
  await handleDev(options.browser, globalOpts.verbose);
79
82
  });
80
83
 
84
+ program
85
+ .command('deploy')
86
+ .description('Deploy activity to Adobe Target using at-deploy.js')
87
+ .option('--dry-run', 'Run deployment in dry-run mode without actual deployment')
88
+ .action(async (options, command) => {
89
+ const globalOpts = command.parent.opts();
90
+ checkEnvFile();
91
+ await handleDeploy(options.dryRun, globalOpts.verbose);
92
+ });
93
+
94
+ program
95
+ .command('doctor')
96
+ .description('Diagnose and fix project configuration issues')
97
+ .option('--fix', 'Automatically fix detected issues')
98
+ .action(async (options, command) => {
99
+ const globalOpts = command.parent.opts();
100
+ await handleDoctor(options.fix, globalOpts.verbose);
101
+ });
102
+
81
103
  await program.parseAsync(process.argv);
82
104
 
83
105
  logger.info("setupCommander", "Commander setup complete");
@@ -135,7 +157,7 @@ const handleInit = async (verbose: boolean): Promise<void> => {
135
157
  */
136
158
  const handleNew = async (verbose: boolean): Promise<void> => {
137
159
  if (verbose) logger.info("verbose", "Creating new activity");
138
- runCommand(["run", "new-activity"], productionEnv);
160
+ runCommand(["run", "atb:plop:new:activity"], productionEnv);
139
161
  };
140
162
 
141
163
  /**
@@ -147,10 +169,10 @@ const handleBuild = async (prod: boolean, verbose: boolean): Promise<void> => {
147
169
  if (prod) {
148
170
  process.env['NODE_ENV'] = 'production';
149
171
  logger.info("handleBuild", "Running build with production environment");
150
- runCommand(['run', 'build-prod'], productionEnv);
172
+ runCommand(['run', 'atb:build:prod'], productionEnv);
151
173
  } else {
152
174
  logger.info("handleBuild", "Running build with development environment");
153
- runCommand(['run', 'dev'], productionEnv);
175
+ runCommand(['run', 'atb:build:dev'], productionEnv);
154
176
  }
155
177
  };
156
178
 
@@ -160,12 +182,67 @@ const handleBuild = async (prod: boolean, verbose: boolean): Promise<void> => {
160
182
  const handleDev = async (browser: boolean, verbose: boolean): Promise<void> => {
161
183
  if (verbose) logger.info("verbose", `Starting development server with browser=${browser}`);
162
184
 
163
- const commandArr = ['run', browser ? 'dev-puppeteer' : 'dev'];
185
+ const commandArr = ['run', browser ? 'atb:build:dev:puppeteer' : 'atb:build:dev'];
164
186
  logger.info("handleDev", `Running command: ${commandArr.join(', ')}`);
165
187
 
166
188
  runCommand(commandArr, productionEnv);
167
189
  };
168
190
 
191
+ /**
192
+ * Handles the deploy command
193
+ */
194
+ const handleDeploy = async (dryRun: boolean, verbose: boolean): Promise<void> => {
195
+ if (verbose) logger.info("verbose", `Deploying to Adobe Target with dry-run=${dryRun}`);
196
+
197
+ logger.info("handleDeploy", "Running Adobe Target deployment");
198
+
199
+ runCommand(['run', 'atb:build:deploy'], productionEnv);
200
+ };
201
+
202
+ /**
203
+ * Handles the doctor command
204
+ */
205
+ const handleDoctor = async (autoFix: boolean, verbose: boolean): Promise<void> => {
206
+ if (verbose) logger.info("verbose", `Running diagnostics with auto-fix=${autoFix}`);
207
+
208
+ logger.info("handleDoctor", "Diagnosing project configuration");
209
+
210
+ try {
211
+ // Run diagnostics
212
+ const issues = await runDiagnostics(process.cwd(), verbose);
213
+
214
+ if (issues.length === 0) {
215
+ console.log("āœ… No issues found. Your project configuration is healthy!");
216
+ return;
217
+ }
218
+
219
+ // Display issues
220
+ console.log(`\nšŸ” Found ${issues.length} issue(s):\n`);
221
+ issues.forEach((issue, index) => {
222
+ console.log(`${index + 1}. ${issue.severity === 'error' ? 'āŒ' : 'āš ļø'} ${issue.message}`);
223
+ if (issue.suggestion) {
224
+ console.log(` šŸ’” ${issue.suggestion}`);
225
+ }
226
+ });
227
+
228
+ if (autoFix) {
229
+ console.log('\nšŸ”§ Attempting to fix issues...\n');
230
+ const fixed = await fixIssues(issues, process.cwd(), verbose);
231
+ console.log(`\nāœ… Fixed ${fixed} issue(s).`);
232
+
233
+ if (fixed < issues.length) {
234
+ console.log(`āš ļø ${issues.length - fixed} issue(s) require manual attention.`);
235
+ }
236
+ } else {
237
+ console.log('\nšŸ’” Run `atb doctor --fix` to automatically fix resolvable issues.');
238
+ }
239
+
240
+ } catch (error) {
241
+ logger.error("handleDoctor", `Doctor command failed: ${error.message}`);
242
+ process.exit(1);
243
+ }
244
+ };
245
+
169
246
  // Command execution logic
170
247
  /**
171
248
  * Main command execution entry point