at-builder 1.2.9 → 1.4.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.
- package/.claude/settings.local.json +53 -11
- package/.plop/constants/index.js +0 -7
- package/.plop/generators/actions.js +217 -126
- package/.plop/generators/prompts.js +50 -18
- package/.plop/utils/index.js +19 -5
- package/.vscode/settings.json +6 -0
- package/DEVELOPMENT.md +164 -0
- package/README.md +23 -8
- package/at-builder-0.0.2.vsix +0 -0
- package/bin/constants/config.js +169 -167
- package/bin/index.js +494 -182
- package/bin/services/doctor.js +752 -290
- package/bin/services/logger.js +40 -20
- package/lib/at-deploy.js +379 -145
- package/lib/at-sync.js +455 -0
- package/lib/eslint-flat-config-plugin.js +34 -33
- package/lib/install-checks.js +236 -0
- package/lib/postinstall.js +90 -0
- package/package/package.json +86 -0
- package/package.json +18 -11
- package/puppeteer.js +128 -32
- package/src/constants/config.ts +84 -9
- package/src/index.ts +107 -11
- package/src/services/doctor.ts +377 -39
- package/webpack.config.js +228 -39
- package/.plop/templates/build-template.hbs +0 -7
- package/.plop/templates/build.config.hbs +0 -7
- package/.plop/templates/observer.hbs +0 -18
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
{
|
|
2
2
|
"permissions": {
|
|
3
3
|
"allow": [
|
|
4
|
-
"Bash(npm run
|
|
5
|
-
"Bash(
|
|
6
|
-
"Bash(
|
|
7
|
-
"Bash(
|
|
8
|
-
"Bash(
|
|
9
|
-
"Bash(
|
|
10
|
-
"Bash(
|
|
11
|
-
"Bash(
|
|
12
|
-
|
|
13
|
-
|
|
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
|
+
}
|
package/.plop/constants/index.js
CHANGED
|
@@ -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 {
|
|
2
|
-
const {
|
|
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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
console.log(`📁 Using activities directory: ${trackName}`);
|
|
59
|
+
const actions = [];
|
|
44
60
|
|
|
45
|
-
//
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
//
|
|
52
|
-
|
|
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
|
-
//
|
|
55
|
-
|
|
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
|
-
|
|
105
|
+
// ─── Persist ACTIVITY_FOLDER_NAME in consumer's .env ───
|
|
106
|
+
updateEnvFile(testName);
|
|
58
107
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
127
|
+
return actions;
|
|
128
|
+
};
|
|
72
129
|
|
|
130
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
73
131
|
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
171
|
-
|
|
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
|
|
1
|
+
const kleur = require('kleur');
|
|
2
|
+
const { PROMPT_TYPE_INPUT, PROMPT_TYPE_LIST } = require('../constants');
|
|
2
3
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
package/.plop/utils/index.js
CHANGED
|
@@ -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
|
+
};
|