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.
- package/.claude/settings.local.json +2 -1
- package/.plop/generators/actions.js +80 -13
- package/.plop/templates/analytics.hbs +13 -3
- package/.plop/templates/build.config.hbs +7 -0
- package/.plop/templates/component.cb.hbs +61 -16
- package/CustomWrapperPlugin.js +1 -1
- package/README.md +210 -33
- package/bin/constants/config.js +49 -0
- package/bin/index.js +73 -5
- package/bin/services/doctor.js +342 -0
- package/lib/at-deploy.js +318 -0
- package/package.json +11 -8
- package/plopfile.js +7 -1
- package/src/constants/config.ts +51 -0
- package/src/index.ts +82 -5
- package/src/services/doctor.ts +379 -0
- package/tsconfig.json +1 -1
- package/webpack.config.js +37 -1
- package/target/npmlist.json +0 -196
package/lib/at-deploy.js
ADDED
|
@@ -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.
|
|
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
|
|
10
|
-
"build
|
|
11
|
-
"
|
|
12
|
-
"dev
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
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
|
-
|
|
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 );
|
package/src/constants/config.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
|
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
|