at-builder 1.2.8 → 1.3.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.
@@ -1,15 +1,57 @@
1
1
  {
2
2
  "permissions": {
3
3
  "allow": [
4
- "Bash(npm run build:prod:*)",
5
- "Bash(node:*)",
6
- "Bash(npx tsc:*)",
7
- "Bash(find:*)",
8
- "Bash(rm:*)",
9
- "Bash(npm ls:*)",
10
- "Bash(ls:*)",
11
- "Bash(npx eslint:*)"
12
- ],
13
- "deny": []
4
+ "Bash(npm run *)",
5
+ "Bash(npm install *)",
6
+ "Bash(/Users/upesenga/Documents/Projects/AT/at-builder/node_modules/.bin/tsc *)",
7
+ "Bash(node /Users/upesenga/Documents/Projects/AT/at-builder/bin/index.js)",
8
+ "Bash(node /Users/upesenga/Documents/Projects/AT/at-builder/bin/index.js --help)",
9
+ "Bash(node /Users/upesenga/Documents/Projects/AT/at-builder/bin/index.js sync --help)",
10
+ "Bash(node /Users/upesenga/Documents/Projects/AT/at-builder/bin/index.js deploy --help)",
11
+ "Bash(node --check /Users/upesenga/Documents/Projects/AT/at-builder/lib/at-deploy.js)",
12
+ "Bash(node --check /Users/upesenga/Documents/Projects/AT/at-builder/lib/at-sync.js)",
13
+ "Bash(node --check /Users/upesenga/Documents/Projects/AT/at-builder/webpack.config.js)",
14
+ "Bash(node *)",
15
+ "Bash(plop --help)",
16
+ "Bash(plop page *)",
17
+ "Bash(git rev-list *)",
18
+ "Bash(PATH=/dev/null node /Users/upesenga/Documents/Projects/AT/at-builder/bin/index.js install-extension)",
19
+ "Bash(git commit -m 'chore: drop broken postinstall reference, ignore test/ sandbox *)",
20
+ "Bash(git commit -m 'fix\\(plop\\): random windowFlagName per activity *)",
21
+ "Bash(git commit -m 'feat: doctor build.config validation, .env consolidation, install-extension command *)",
22
+ "Bash(git commit -m 'docs: document local-dev workflow \\(yarn link + npm pack\\) *)",
23
+ "Bash(git fetch *)",
24
+ "Bash(git check-ignore *)",
25
+ "Bash(git commit -m 'docs: move local-dev guide to standalone DEVELOPMENT.md *)",
26
+ "Bash(git add *)",
27
+ "Bash(atb --version)",
28
+ "Bash(atb --help)",
29
+ "Bash(tar tzf *)",
30
+ "Bash(npm ls *)",
31
+ "Bash(/usr/local/bin/atb --version)",
32
+ "Bash(grep -E \"\\(at-deploy|at-sync|index\\\\.js|\\\\.vsix|package\\\\.json\\)$\")",
33
+ "Bash(npm pack *)",
34
+ "Bash(tar xzf *)",
35
+ "Bash(tar xzOf at-builder-1.3.0.tgz package/package.json)",
36
+ "Bash(git commit -m '1.3.0 *)",
37
+ "Read(//usr/local/lib/node_modules/at-builder/bin/**)",
38
+ "Read(//Users/upesenga/.nvm/versions/node/v22.12.0/lib/node_modules/at-builder/bin/**)",
39
+ "Read(//usr/local/share/.config/yarn/link/**)",
40
+ "Bash(npm uninstall *)",
41
+ "Bash(/Users/upesenga/.nvm/versions/node/v22.12.0/bin/atb --version)",
42
+ "Bash(/Users/upesenga/.nvm/versions/node/v22.12.0/bin/atb --help)",
43
+ "Bash(/Users/upesenga/.nvm/versions/node/v22.12.0/lib/node_modules/at-builder/bin/index.js --version)",
44
+ "Bash(tar xzOf /Users/upesenga/Documents/Projects/AT/at-builder/at-builder-1.3.0.tgz package/package.json)",
45
+ "Bash(npm config *)",
46
+ "Bash(npm cache *)",
47
+ "Bash(git commit -m 'docs: capture two install-time gotchas hit during 1.3.0 verification *)",
48
+ "Bash(kill %1)",
49
+ "Bash(git commit -m 'fix\\(eslint-plugin\\): pass overrideConfigFile: true so build doesn'\\\\''t require consumer eslint.config.js *)",
50
+ "Bash(yarn global *)",
51
+ "Bash(ls -la \"$\\(yarn global bin\\)/atb\")",
52
+ "Bash(ls -la \"$\\(yarn global dir \\)/node_modules/at-builder\")",
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 *)"
55
+ ]
14
56
  }
15
- }
57
+ }
@@ -1,11 +1,4 @@
1
- exports.path = require('path');
2
- exports.PATTERN_CLASS_BASED = 'Class-Based Component';
3
- exports.PATTERN_FUNCTIONAL = 'Functional Component';
4
1
  exports.TEMPLATE_DIR = '.plop/templates';
5
- exports.TEMPLATE_TYPE_SUFFIX_CLASS_BASED = 'cb';
6
- exports.TEMPLATE_TYPE_SUFFIX_FUNCTIONAL = 'func';
7
2
  exports.PROMPT_TYPE_LIST = 'list';
8
3
  exports.PROMPT_TYPE_INPUT = 'input';
9
4
  exports.ACTION_ADD = 'add';
10
- exports.type_script = 'script';
11
- exports.type_style = 'style';
@@ -1,186 +1,277 @@
1
- const { template } = require("lodash");
2
- const { ACTION_ADD, PATTERN_CLASS_BASED, PATTERN_FUNCTIONAL, TEMPLATE_DIR, TEMPLATE_TYPE_SUFFIX_CLASS_BASED, TEMPLATE_TYPE_SUFFIX_FUNCTIONAL } = require("../constants");
3
- const { toCamelCase, toPascalCase } = require("../utils");
1
+ const { ACTION_ADD, TEMPLATE_DIR } = require("../constants");
2
+ const { toPascalCase, generateUniqueId } = require("../utils");
4
3
  const path = require('path');
5
-
6
4
  const fs = require('fs');
7
5
 
8
6
  /**
9
- * Action handler for plop
10
- * @param {Object} data - Data that contains inputs from the user
11
- * @returns {Array} - Array of actions that plop needs to perform
7
+ * Builds the plop actions for `atb new`.
8
+ *
9
+ * Supported shapes:
10
+ * AB single-page → Activities/<test>/{VControl, Variation-N}/...
11
+ * AB multi-page → Activities/<test>/{VControl, Variation-N}/<Page>/...
12
+ * XT single-page → Activities/<test>/Experience-{A,B,...}/...
13
+ * XT multi-page → Activities/<test>/Experience-{A,B,...}/<Page>/...
14
+ *
15
+ * Always writes:
16
+ * Activities/<test>/Vanalytics/analytics.js
17
+ * Activities/<test>/shared/build.config.json (generated inline; matches the
18
+ * schema consumed by lib/at-deploy.js and lib/at-sync.js)
19
+ *
20
+ * Side effect:
21
+ * Updates ACTIVITY_FOLDER_NAME in the consumer's .env so subsequent
22
+ * `atb build` / `atb deploy` find the right activity.
12
23
  */
13
24
  exports.actionHandler = (data) => {
14
- let actions = [];
15
- let fileName = '';
16
- let folderName = '';
17
- let testPath = '';
18
- let numberOfVariations;
19
- let { trackName, testName, variationName, name } = data;
20
-
21
- /**
22
- * Assigns constants to the files
23
- * @param {String} variation - The name of the variation
24
- * @returns {Array} - Array of objects with the path and template
25
- */
26
- function assignConstants(variation) {
27
- return [{ 'global': false, 'path': `${folderName}/index.js`, 'templateFile': `${TEMPLATE_DIR}/index.hbs`, variation },
28
- { 'global': true, 'path': `${testPath}/Vanalytics/analytics.js`, 'templateFile': `${TEMPLATE_DIR}/analytics.hbs`, variation },
29
- { 'global': false, 'path': `${folderName}/constants/index.js`, 'templateFile': `${TEMPLATE_DIR}/constants.hbs`, variation },
30
- { 'global': false, 'path': `${folderName}/scripts/app.js`, 'templateFile': `${TEMPLATE_DIR}/component.cb.hbs`, variation },
31
- { 'global': false, 'path': `${folderName}/css/style.scss`, 'templateFile': `${TEMPLATE_DIR}/style.hbs`, variation },
32
- { 'global': true, 'path': `${testPath}/shared/build.config.json`, 'templateFile': `${TEMPLATE_DIR}/build.config.hbs`, variation }
33
- ];
25
+ const { testName, variationName, activityType, pageNames } = data;
26
+
27
+ const isXT = activityType === 'xt';
28
+ const pages = pageNames
29
+ ? pageNames.split(',').map(p => p.trim()).filter(Boolean)
30
+ : [];
31
+ const isMultiPage = pages.length > 0;
32
+ const numberOfVariations = parseInt(variationName, 10);
33
+
34
+ if (!testName || Number.isNaN(numberOfVariations) || numberOfVariations < 1) {
35
+ console.error('❌ Invalid input: testName and a positive variation count are required.');
36
+ return [];
34
37
  }
35
38
 
36
- // Get the activities path from environment variables (same approach as webpack config)
39
+ // ─── Resolve consumer paths ───
37
40
  const PWD = process.env.executionPath || process.cwd();
38
- console.log(`📁 Using ACTIVITIES_BASE_FOLDER directory: ${process.env.executionPath}`);
39
41
  const ACTIVITIES_BASE_FOLDER = process.env.ACTIVITIES_BASE_FOLDER || 'Activities';
40
- trackName = path.join(PWD, ACTIVITIES_BASE_FOLDER);
42
+ const activitiesRoot = path.join(PWD, ACTIVITIES_BASE_FOLDER);
43
+ const testPath = path.join(activitiesRoot, testName);
44
+
45
+ if (!fs.existsSync(activitiesRoot)) {
46
+ fs.mkdirSync(activitiesRoot, { recursive: true });
47
+ console.log(`📁 Created activities directory: ${activitiesRoot}`);
48
+ }
49
+ console.log(`📁 Activities directory: ${activitiesRoot}`);
50
+
51
+ const fileName = toPascalCase(testName);
52
+ // Random per-activity id avoids window-flag collisions when multiple
53
+ // at-builder activities end up loaded on the same page (deterministic
54
+ // names derived from testName would collide whenever two activities share
55
+ // a name stem). Frozen into analytics.hbs at scaffold time.
56
+ const windowFlagName = `atb_${generateUniqueId(15)}`;
57
+ const templateData = { fileName, windowFlagName };
41
58
 
42
- // Log the activities path for debugging
43
- console.log(`📁 Using activities directory: ${trackName}`);
59
+ const actions = [];
44
60
 
45
- // Ensure the activities directory exists
46
- if (!fs.existsSync(trackName)) {
47
- fs.mkdirSync(trackName, { recursive: true });
48
- console.log(`📁 Created activities directory: ${trackName}`);
61
+ // ─── Variation/experience folder generation ───
62
+ if (isXT) {
63
+ for (let i = 1; i <= numberOfVariations; i++) {
64
+ const expFolder = xtFolderName(i); // Experience-A, B, C...
65
+ if (isMultiPage) {
66
+ pages.forEach(page => addVariationFiles(actions, path.join(testPath, expFolder, page), templateData));
67
+ } else {
68
+ addVariationFiles(actions, path.join(testPath, expFolder), templateData);
69
+ }
70
+ }
71
+ } else if (isMultiPage) {
72
+ // AB multi-page: VControl + Variation-N, each with page subfolders
73
+ pages.forEach(page => addVariationFiles(actions, path.join(testPath, 'VControl', page), templateData));
74
+ for (let i = 1; i <= numberOfVariations; i++) {
75
+ pages.forEach(page => addVariationFiles(actions, path.join(testPath, `Variation-${i}`, page), templateData));
76
+ }
77
+ } else {
78
+ // AB single-page (legacy at-builder layout)
79
+ addVariationFiles(actions, path.join(testPath, 'VControl'), templateData);
80
+ for (let i = 1; i <= numberOfVariations; i++) {
81
+ addVariationFiles(actions, path.join(testPath, `Variation-${i}`), templateData);
82
+ }
49
83
  }
50
84
 
51
- // Convert the name to camel case
52
- fileName = toCamelCase(name);
85
+ // ─── Always: Vanalytics global file ───
86
+ actions.push({
87
+ type: ACTION_ADD,
88
+ path: path.join(testPath, 'Vanalytics', 'analytics.js'),
89
+ templateFile: `${TEMPLATE_DIR}/analytics.hbs`,
90
+ data: templateData,
91
+ skipIfExists: true,
92
+ abortOnFail: true
93
+ });
53
94
 
54
- // Get the number of variations from the user
55
- numberOfVariations = parseInt(variationName);
95
+ // ─── shared/build.config.json (dynamic; matches deploy/sync schema) ───
96
+ const buildConfig = generateBuildConfig({ isXT, isMultiPage, pages, numberOfVariations, testName });
97
+ actions.push({
98
+ type: ACTION_ADD,
99
+ path: path.join(testPath, 'shared', 'build.config.json'),
100
+ template: JSON.stringify(buildConfig, null, 4) + '\n',
101
+ skipIfExists: true,
102
+ abortOnFail: true
103
+ });
56
104
 
57
- let constantMappingFile = null;
105
+ // ─── Persist ACTIVITY_FOLDER_NAME in consumer's .env ───
106
+ updateEnvFile(testName);
58
107
 
59
- // If the user has provided track name and test name and variation name
60
- if (trackName && testName && variationName) {
61
- // Get the current year
62
- testPath = trackName + `/${testName}`;
63
- // Get the constant mapping
64
- constantMappingFile = assignConstants();
65
- // Convert the name to pascal case
66
- fileName = toPascalCase(name);
108
+ // ─── Completion message ───
109
+ const shapeLabel = `${isXT ? 'XT' : 'AB'}${isMultiPage ? ' multi-page' : ''}`;
110
+ const countLabel = isXT
111
+ ? `${numberOfVariations} experience${numberOfVariations === 1 ? '' : 's'}`
112
+ : `${numberOfVariations} variation${numberOfVariations === 1 ? '' : 's'} + control`;
113
+ console.log(`\n🎉 Activity "${testName}" created successfully!`);
114
+ console.log(`📁 Location: ${testPath}`);
115
+ console.log(`🧪 Shape: ${shapeLabel} (${countLabel}${isMultiPage ? `, ${pages.length} page${pages.length === 1 ? '' : 's'}` : ''})`);
116
+ console.log(`🔧 ACTIVITY_FOLDER_NAME updated in .env file`);
117
+ console.log(`\n💡 Next steps:`);
118
+ if (isMultiPage || isXT) {
119
+ console.log(` 1. Set activityInfo.id in shared/build.config.json once the AT activity exists`);
120
+ console.log(` 2. Run "atb sync" to pull pages/locationIds back from Adobe Target`);
121
+ console.log(` 3. Run "atb build" / "atb deploy"`);
122
+ } else {
123
+ console.log(` 1. Run "atb build" to build your activity`);
124
+ console.log(` 2. Run "atb deploy" once activityInfo.id is set in shared/build.config.json`);
67
125
  }
68
126
 
69
- // Create actions for the global files
70
- createActionObject(true);
71
-
127
+ return actions;
128
+ };
72
129
 
130
+ // ─── Helpers ──────────────────────────────────────────────────────────────
73
131
 
74
- // Loop through the number of variations provided by the user
75
- let i = 1;
132
+ function xtFolderName(i) {
133
+ // Experience-A, B, C, ... For i > 26, falls back to Experience-AA, AB, ... (rare).
134
+ if (i <= 26) return `Experience-${String.fromCharCode(64 + i)}`;
135
+ const first = String.fromCharCode(64 + Math.floor((i - 1) / 26));
136
+ const second = String.fromCharCode(65 + ((i - 1) % 26));
137
+ return `Experience-${first}${second}`;
138
+ }
76
139
 
77
- do {
78
- // Get the variation name
79
- let variation = "Variation-" + i;
80
- // Get the folder name
81
- folderName = testPath + "/" + variation;
82
- // Get the constant mapping
83
- constantMappingFile = assignConstants(variation);
84
- // Create actions for the variation files
85
- createActionObject(false);
86
- // Increment the loop counter
87
- i++;
140
+ function addVariationFiles(actions, folderAbs, templateData) {
141
+ const files = [
142
+ { rel: 'index.js', tpl: 'index.hbs' },
143
+ { rel: path.join('scripts', 'app.js'), tpl: 'component.cb.hbs' },
144
+ { rel: path.join('css', 'style.scss'), tpl: 'style.hbs' },
145
+ { rel: path.join('constants', 'index.js'), tpl: 'constants.hbs' }
146
+ ];
147
+ for (const f of files) {
148
+ actions.push({
149
+ type: ACTION_ADD,
150
+ path: path.join(folderAbs, f.rel),
151
+ templateFile: `${TEMPLATE_DIR}/${f.tpl}`,
152
+ data: templateData,
153
+ skipIfExists: true,
154
+ abortOnFail: true
155
+ });
88
156
  }
89
- while (i <= numberOfVariations);
90
-
91
- /**
92
- * Creates an action object for plop
93
- * @param {Boolean} isGlobalFile - If it is a global file or not
94
- */
95
- function createActionObject(isGlobalFile) {
96
- constantMappingFile.forEach(item => {
97
- if ((!item.global && !isGlobalFile) || (item.global && isGlobalFile)) {
98
- let actionObj = {
99
- type: `${ACTION_ADD}`,
100
- path: item.path,
101
- data: {
102
- componentName: toPascalCase(name),
103
- variation: item.variation,
104
- fileName: fileName,
105
- windowFlagName: toPascalCase(testName)
106
- },
107
- skipIfExists: true,
108
- abortOnFail: true
157
+ }
158
+
159
+ /**
160
+ * Build the build.config.json contents for a fresh activity.
161
+ *
162
+ * Schema matches what lib/at-deploy.js / lib/at-sync.js read:
163
+ * - activityType : "ab" | "xt"
164
+ * - pages : { <pageName>: <locationLocalId placeholder> } (multi-page only)
165
+ * - variations : { <folderName>: <ATExperienceName> } (single-page)
166
+ * | { <folderName>: { experienceName, pages: { <pageName>: "<folder>/<page>" } } } (multi-page)
167
+ * - buildFolders : extra top-level folders webpack should also bundle
168
+ *
169
+ * `id` and locationLocalIds are placeholders — `atb sync` populates them
170
+ * from the Adobe Target activity once it exists.
171
+ */
172
+ function generateBuildConfig({ isXT, isMultiPage, pages, numberOfVariations, testName }) {
173
+ const config = {
174
+ activityInfo: {
175
+ id: null,
176
+ activityType: isXT ? 'xt' : 'ab',
177
+ name: testName
178
+ }
179
+ };
180
+
181
+ if (isMultiPage) {
182
+ config.activityInfo.pages = {};
183
+ pages.forEach((page, idx) => {
184
+ config.activityInfo.pages[page] = idx; // placeholder; atb sync overwrites
185
+ });
186
+
187
+ config.activityInfo.variations = {};
188
+
189
+ if (isXT) {
190
+ for (let i = 1; i <= numberOfVariations; i++) {
191
+ const expFolder = xtFolderName(i);
192
+ const expName = `Experience ${expFolder.split('-')[1]}`;
193
+ const expPages = {};
194
+ pages.forEach(page => { expPages[page] = `${expFolder}/${page}`; });
195
+ config.activityInfo.variations[expFolder] = {
196
+ experienceName: expName,
197
+ pages: expPages
109
198
  };
110
- if (item['templateFile']) {
111
- actionObj['templateFile'] = item['templateFile'];
112
- } else {
113
- actionObj['template'] = item['template'];
114
- }
115
- actions.push(actionObj);
116
199
  }
117
- });
200
+ } else {
201
+ // AB multi-page: VControl + Variation-N
202
+ const controlPages = {};
203
+ pages.forEach(page => { controlPages[page] = `VControl/${page}`; });
204
+ config.activityInfo.variations['VControl'] = {
205
+ experienceName: 'Control',
206
+ pages: controlPages
207
+ };
208
+ for (let i = 1; i <= numberOfVariations; i++) {
209
+ const folder = `Variation-${i}`;
210
+ const varPages = {};
211
+ pages.forEach(page => { varPages[page] = `${folder}/${page}`; });
212
+ config.activityInfo.variations[folder] = {
213
+ experienceName: `Variation ${i}`,
214
+ pages: varPages
215
+ };
216
+ }
217
+ }
218
+ } else {
219
+ // Single-page: simple { folder: ATExperienceName } map
220
+ config.activityInfo.variations = {};
221
+ if (isXT) {
222
+ for (let i = 1; i <= numberOfVariations; i++) {
223
+ const expFolder = xtFolderName(i);
224
+ const expName = `Experience ${expFolder.split('-')[1]}`;
225
+ config.activityInfo.variations[expFolder] = expName;
226
+ }
227
+ } else {
228
+ config.activityInfo.variations['VControl'] = 'Control';
229
+ for (let i = 1; i <= numberOfVariations; i++) {
230
+ config.activityInfo.variations[`Variation-${i}`] = `Variation ${i}`;
231
+ }
232
+ }
118
233
  }
119
234
 
120
- // Update .env file with the new activity name
121
- updateEnvFile(testName);
122
-
123
- // Show completion message
124
- console.log(`\n🎉 Activity "${testName}" created successfully!`);
125
- console.log(`📁 Location: ${testPath}`);
126
- console.log(`📋 Number of variations: ${numberOfVariations}`);
127
- console.log(`🔧 ACTIVITY_FOLDER_NAME updated in .env file`);
128
- console.log(`\n💡 Run "atb build" to build your activity`);
129
- console.log(`💡 Run "atb doctor" to check your configuration`);
130
-
131
- // Return the actions
132
- return actions;
235
+ config.activityInfo.buildFolders = ['Vanalytics'];
236
+ return config;
133
237
  }
134
238
 
135
239
  /**
136
- * Updates the .env file with the new activity folder name
137
- * @param {string} activityName - The name of the activity to set in ACTIVITY_FOLDER_NAME
240
+ * Updates the consumer's .env file with the new activity folder name so
241
+ * subsequent `atb build`/`atb deploy` find it without manual editing.
138
242
  */
139
243
  function updateEnvFile(activityName) {
140
244
  const PWD = process.env.executionPath || process.cwd();
141
245
  const envPath = path.join(PWD, '.env');
142
-
246
+
143
247
  if (!fs.existsSync(envPath)) {
144
248
  console.log('⚠️ .env file not found, skipping ACTIVITY_FOLDER_NAME update');
145
249
  console.log('💡 Run "atb init" to create .env file');
146
250
  return;
147
251
  }
148
-
149
252
  if (!activityName || activityName.trim() === '') {
150
253
  console.log('⚠️ Activity name is empty, skipping .env update');
151
254
  return;
152
255
  }
153
-
256
+
154
257
  try {
155
- // Read the current .env file
156
258
  let envContent = fs.readFileSync(envPath, 'utf8');
157
-
158
- // Clean activity name (remove quotes if present)
159
259
  const cleanActivityName = activityName.trim().replace(/^["']|["']$/g, '');
160
-
161
- // Update or add ACTIVITY_FOLDER_NAME
162
260
  const activityFolderRegex = /^ACTIVITY_FOLDER_NAME=.*$/m;
163
261
  const newLine = `ACTIVITY_FOLDER_NAME="${cleanActivityName}"`;
164
-
262
+
165
263
  if (activityFolderRegex.test(envContent)) {
166
- // Update existing line
167
264
  envContent = envContent.replace(activityFolderRegex, newLine);
168
265
  console.log(`✅ Updated ACTIVITY_FOLDER_NAME to "${cleanActivityName}" in .env file`);
169
266
  } else {
170
- // Add new line if it doesn't exist, ensure it ends with newline
171
- if (!envContent.endsWith('\n')) {
172
- envContent += '\n';
173
- }
174
- envContent += newLine;
267
+ if (!envContent.endsWith('\n')) envContent += '\n';
268
+ envContent += newLine + '\n';
175
269
  console.log(`✅ Added ACTIVITY_FOLDER_NAME="${cleanActivityName}" to .env file`);
176
270
  }
177
-
178
- // Write the updated content back to .env file
271
+
179
272
  fs.writeFileSync(envPath, envContent, 'utf8');
180
-
181
273
  } catch (error) {
182
274
  console.error(`❌ Error updating .env file: ${error.message}`);
183
275
  console.log('💡 You may need to manually set ACTIVITY_FOLDER_NAME in your .env file');
184
276
  }
185
277
  }
186
-
@@ -1,31 +1,63 @@
1
- const { PROMPT_TYPE_INPUT } = require("../constants");
1
+ const kleur = require('kleur');
2
+ const { PROMPT_TYPE_INPUT, PROMPT_TYPE_LIST } = require('../constants');
2
3
 
3
- function promptToChooseTrack(inquirer, answers = {}) {
4
+ /**
5
+ * Prompts for `atb new`.
6
+ *
7
+ * Collects:
8
+ * - activityType : "ab" | "xt"
9
+ * - testName : activity folder name (e.g. "TEST-1234 ...")
10
+ * - variationName : count — interpreted as treatment variations (AB) or experiences (XT)
11
+ * - pageNames : optional comma-separated page list. Empty → single-page.
12
+ */
13
+ const ask = (label, hint) => hint
14
+ ? `${kleur.bold(label)} ${kleur.gray(`(${hint})`)}`
15
+ : kleur.bold(label);
4
16
 
5
- const prompts = inquirer.prompt([
17
+ const prompts = function (inquirer) {
18
+ console.log();
19
+ console.log(kleur.cyan().bold(' 🧪 Create a new Adobe Target activity'));
20
+ console.log(kleur.gray(' ─ scaffolds variation folders + shared/build.config.json\n'));
21
+
22
+ return inquirer.prompt([
23
+ {
24
+ type: PROMPT_TYPE_LIST,
25
+ name: 'activityType',
26
+ prefix: kleur.cyan('›'),
27
+ message: ask('Activity type'),
28
+ choices: [
29
+ { name: `A/B Test ${kleur.gray('— control + variations')}`, value: 'ab' },
30
+ { name: `Experience Targeting (XT) ${kleur.gray('— audience-targeted experiences')}`, value: 'xt' }
31
+ ]
32
+ },
6
33
  {
7
34
  type: PROMPT_TYPE_INPUT,
8
35
  name: 'testName',
9
- message: "Enter the test name"
36
+ prefix: kleur.cyan('›'),
37
+ message: ask('Activity name', 'folder name, e.g. TEST-1234 hero-banner'),
38
+ validate: (input) => input && input.trim()
39
+ ? true
40
+ : 'Activity name cannot be empty.'
10
41
  },
11
42
  {
12
43
  type: PROMPT_TYPE_INPUT,
13
44
  name: 'variationName',
14
- message: "How many variations you want?"
45
+ prefix: kleur.cyan('›'),
46
+ message: (answers) => answers.activityType === 'xt'
47
+ ? ask('How many experiences?')
48
+ : ask('How many variations?', 'control is added automatically'),
49
+ validate: (input) => /^[1-9]\d*$/.test(String(input).trim())
50
+ ? true
51
+ : 'Enter a positive integer (1, 2, 3, ...).'
52
+ },
53
+ {
54
+ type: PROMPT_TYPE_INPUT,
55
+ name: 'pageNames',
56
+ prefix: kleur.cyan('›'),
57
+ message: ask('Page names', 'comma-separated for multi-page, blank for single-page'),
58
+ filter: (input) => (input || '').trim()
15
59
  }
16
60
  ]);
17
- prompts.then((newAnswers) => {
18
- Object.assign(newAnswers, answers);
19
- });
20
- return prompts;
21
- }
22
-
23
-
24
- const prompts = function (inquirer) {
25
- const basePrompt = promptToChooseTrack(inquirer);
26
- return basePrompt.then((answers) => {
27
- return basePrompt;
28
- });
29
61
  };
30
62
 
31
- module.exports = prompts;
63
+ module.exports = prompts;
@@ -1,10 +1,24 @@
1
1
  const _camelCase = require('lodash/camelCase');
2
2
  const _startCase = require('lodash/startCase');
3
3
 
4
- exports.toCamelCase = str => {
5
- return _camelCase(str);
6
- };
7
-
8
4
  exports.toPascalCase = str => {
9
5
  return _startCase(_camelCase(str)).replace(/ /g, '');
10
- };
6
+ };
7
+
8
+ /**
9
+ * Generate a random alphanumeric id of the given length.
10
+ *
11
+ * Used for the `windowFlagName` baked into analytics.hbs — picking a fresh id
12
+ * per activity prevents window-flag collisions when two at-builder activities
13
+ * end up loaded on the same page (rare, but real). A deterministic name (e.g.
14
+ * derived from testName) would collide whenever two activities share a name
15
+ * stem.
16
+ */
17
+ exports.generateUniqueId = (length = 15) => {
18
+ const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
19
+ let result = '';
20
+ for (let i = 0; i < length; i++) {
21
+ result += characters.charAt(Math.floor(Math.random() * characters.length));
22
+ }
23
+ return result;
24
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "Slingshot.enableAutomaticCreationOfFilesWithFilenameClick": true,
3
+ "[handlebars]": {
4
+ "editor.formatOnSave": false
5
+ }
6
+ }