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.
- 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 +16 -1
- 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 +131 -11
- package/src/services/doctor.ts +377 -39
- package/tsconfig.json +1 -1
- 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
package/puppeteer.js
CHANGED
|
@@ -4,15 +4,93 @@ import path from 'path';
|
|
|
4
4
|
import puppeteer from 'puppeteer';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import chokidar from 'chokidar';
|
|
7
|
+
|
|
7
8
|
const executionPath = process.env.executionPath || process.cwd();
|
|
8
|
-
|
|
9
|
+
const envPath = path.join(executionPath, ".env");
|
|
10
|
+
dotenv.config({ path: envPath });
|
|
11
|
+
|
|
12
|
+
// Honor consumer's ACTIVITIES_BASE_FOLDER (falls back to "Activities" to
|
|
13
|
+
// match webpack/at-deploy/at-sync/plop). Hardcoding it here previously caused
|
|
14
|
+
// `atb dev --browser` to silently read from the wrong path when users
|
|
15
|
+
// customized this in their .env.
|
|
16
|
+
const ACTIVITIES_BASE_FOLDER = process.env.ACTIVITIES_BASE_FOLDER || "Activities";
|
|
17
|
+
|
|
18
|
+
// Legacy watch-config.json path. Kept as a fallback source for VARIATION/PAGE
|
|
19
|
+
// so existing projects keep working. New projects (atb init) only get .env;
|
|
20
|
+
// `atb doctor --fix` migrates any existing watch-config.json into .env.
|
|
21
|
+
const watchConfig = path.join(executionPath, "watch-config.json");
|
|
22
|
+
|
|
23
|
+
let legacyWarningShown = false;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Resolve the active dev-server selection (VARIATION + PAGE).
|
|
27
|
+
*
|
|
28
|
+
* Precedence:
|
|
29
|
+
* 1. watch-config.json (if it exists) — preserves existing user behavior
|
|
30
|
+
* where this file was the canonical override. A one-time deprecation
|
|
31
|
+
* warning nudges the user to migrate via `atb doctor --fix`.
|
|
32
|
+
* 2. process.env (loaded from .env) — the new canonical source.
|
|
33
|
+
*
|
|
34
|
+
* Returns { VARIATION, PAGE } as plain strings (PAGE may be empty for
|
|
35
|
+
* single-page activities).
|
|
36
|
+
*/
|
|
37
|
+
function readDevSelection() {
|
|
38
|
+
if (fs.existsSync(watchConfig)) {
|
|
39
|
+
try {
|
|
40
|
+
const cfg = JSON.parse(fs.readFileSync(watchConfig, 'utf-8'));
|
|
41
|
+
if (!legacyWarningShown) {
|
|
42
|
+
console.warn(
|
|
43
|
+
'\x1b[33m⚠️ watch-config.json is deprecated — VARIATION/PAGE now live in .env.\x1b[0m\n' +
|
|
44
|
+
'\x1b[33m Run "atb doctor --fix" to migrate values into .env and remove this file.\x1b[0m'
|
|
45
|
+
);
|
|
46
|
+
legacyWarningShown = true;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
VARIATION: cfg.VARIATION || process.env.VARIATION || '',
|
|
50
|
+
PAGE: (cfg.PAGE || process.env.PAGE || '').trim()
|
|
51
|
+
};
|
|
52
|
+
} catch (err) {
|
|
53
|
+
console.error(`Failed to read watch-config.json (${err.message}); falling back to .env.`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
VARIATION: process.env.VARIATION || '',
|
|
58
|
+
PAGE: (process.env.PAGE || '').trim()
|
|
59
|
+
};
|
|
60
|
+
}
|
|
9
61
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Compose the dist folder for the currently-selected variation, page-aware.
|
|
64
|
+
*
|
|
65
|
+
* Single-page (PAGE empty/missing): <Activities>/<activity>/<VARIATION>/dist
|
|
66
|
+
* Multi-page (PAGE set): <Activities>/<activity>/<VARIATION>/<PAGE>/dist
|
|
67
|
+
*
|
|
68
|
+
* Editing .env (or the legacy watch-config.json, if still present) mid-session
|
|
69
|
+
* hot-swaps which variation/page is previewed without restarting the dev server.
|
|
70
|
+
*/
|
|
71
|
+
function variationDistFolder(sel) {
|
|
72
|
+
const segments = [
|
|
73
|
+
executionPath,
|
|
74
|
+
ACTIVITIES_BASE_FOLDER,
|
|
75
|
+
process.env.ACTIVITY_FOLDER_NAME,
|
|
76
|
+
sel.VARIATION
|
|
77
|
+
];
|
|
78
|
+
if (sel.PAGE && sel.PAGE.trim()) {
|
|
79
|
+
segments.push(sel.PAGE.trim());
|
|
80
|
+
}
|
|
81
|
+
segments.push("dist");
|
|
82
|
+
return path.join(...segments);
|
|
13
83
|
}
|
|
14
|
-
|
|
15
|
-
|
|
84
|
+
|
|
85
|
+
// Validate VARIATION up-front so we fail fast with a clear message instead of
|
|
86
|
+
// reading from a nonsense path inside the puppeteer event loop.
|
|
87
|
+
{
|
|
88
|
+
const initial = readDevSelection();
|
|
89
|
+
if (!initial.VARIATION) {
|
|
90
|
+
console.error('\x1b[31m❌ VARIATION is not set.\x1b[0m');
|
|
91
|
+
console.error('\x1b[33m💡 Add VARIATION="Variation-1" (and optional PAGE="Global" for multi-page) to .env, or run "atb doctor --fix".\x1b[0m');
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
16
94
|
}
|
|
17
95
|
|
|
18
96
|
|
|
@@ -54,39 +132,64 @@ const watcher = chokidar.watch("file", {
|
|
|
54
132
|
// inject script
|
|
55
133
|
this.injectCodeSnippet();
|
|
56
134
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
watcher.unwatch(distFolder);
|
|
135
|
+
// Tracks the dist folder we're currently watching so we can unwatch
|
|
136
|
+
// the *old* one when the user edits .env (or legacy watch-config.json).
|
|
137
|
+
this.currentWatchedFolder = null;
|
|
61
138
|
|
|
62
|
-
|
|
63
|
-
|
|
139
|
+
// Initial watches: build folder for the active variation/page,
|
|
140
|
+
// plus the config sources (.env always; watch-config.json if legacy).
|
|
141
|
+
this.watchBuildFolder();
|
|
142
|
+
watcher.add(envPath);
|
|
143
|
+
if (fs.existsSync(watchConfig)) {
|
|
144
|
+
watcher.add(watchConfig);
|
|
145
|
+
}
|
|
64
146
|
|
|
65
|
-
|
|
66
|
-
|
|
147
|
+
// Single change listener for the whole session. Branches on path:
|
|
148
|
+
// .env or watch-config.json → re-resolve VARIATION/PAGE, re-target
|
|
149
|
+
// anything under currentWatchedFolder → reload (webpack rebuilt)
|
|
150
|
+
// Registered once (instead of inside .add(...).on(...)) so we don't
|
|
151
|
+
// accumulate listeners on every variation/page switch.
|
|
152
|
+
const envPathAbs = path.resolve(envPath);
|
|
153
|
+
const watchConfigAbs = path.resolve(watchConfig);
|
|
154
|
+
watcher.on('change', async (changedPath) => {
|
|
155
|
+
try {
|
|
156
|
+
const changedAbs = path.resolve(changedPath);
|
|
157
|
+
const isConfigChange = changedAbs === envPathAbs || changedAbs === watchConfigAbs;
|
|
158
|
+
|
|
159
|
+
if (isConfigChange) {
|
|
160
|
+
// .env change requires re-loading dotenv with override so
|
|
161
|
+
// process.env actually reflects the edits. watch-config.json
|
|
162
|
+
// is re-read each time via readDevSelection().
|
|
163
|
+
if (changedAbs === envPathAbs) {
|
|
164
|
+
dotenv.config({ path: envPath, override: true });
|
|
165
|
+
}
|
|
166
|
+
if (this.currentWatchedFolder) {
|
|
167
|
+
watcher.unwatch(this.currentWatchedFolder);
|
|
168
|
+
}
|
|
169
|
+
this.watchBuildFolder();
|
|
170
|
+
await this.page.reload();
|
|
171
|
+
this.injectCodeSnippet();
|
|
172
|
+
} else if (this.currentWatchedFolder && changedPath.startsWith(this.currentWatchedFolder)) {
|
|
173
|
+
await this.page.reload();
|
|
174
|
+
this.injectCodeSnippet();
|
|
175
|
+
}
|
|
67
176
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
68
177
|
} catch (error) {
|
|
69
|
-
|
|
178
|
+
// Swallow transient FS races; next change event will retry.
|
|
70
179
|
}
|
|
71
180
|
});
|
|
72
181
|
|
|
73
|
-
this.watchBuildFolder();
|
|
74
182
|
return this.trackNavigation();
|
|
75
183
|
}
|
|
76
184
|
|
|
77
185
|
get buildFolderPath() {
|
|
78
|
-
|
|
79
|
-
const distFolder = path.join(process.env.executionPath, "Activities", process.env.ACTIVITY_FOLDER_NAME, env.VARIATION, "dist");
|
|
80
|
-
return distFolder;
|
|
186
|
+
return variationDistFolder(readDevSelection());
|
|
81
187
|
}
|
|
82
188
|
|
|
83
189
|
watchBuildFolder() {
|
|
84
190
|
const distFolder = this.buildFolderPath;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
await this.page.reload();
|
|
88
|
-
this.injectCodeSnippet();
|
|
89
|
-
});
|
|
191
|
+
watcher.add(distFolder);
|
|
192
|
+
this.currentWatchedFolder = distFolder;
|
|
90
193
|
}
|
|
91
194
|
|
|
92
195
|
async trackNavigation() {
|
|
@@ -102,15 +205,8 @@ const watcher = chokidar.watch("file", {
|
|
|
102
205
|
this.trackNavigation();
|
|
103
206
|
}
|
|
104
207
|
|
|
105
|
-
get getEnvConfig() {
|
|
106
|
-
const env = JSON.parse(fs.readFileSync(watchConfig));
|
|
107
|
-
return env;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
208
|
get getScript() {
|
|
111
|
-
const
|
|
112
|
-
// console.log("Env", env, "Process Env", process.env);
|
|
113
|
-
var filePath = path.join(process.env.executionPath, "Activities", process.env.ACTIVITY_FOLDER_NAME, env.VARIATION, "dist", "build.js");
|
|
209
|
+
const filePath = path.join(variationDistFolder(readDevSelection()), "build.js");
|
|
114
210
|
const scriptContent = fs.readFileSync(filePath, 'utf-8');
|
|
115
211
|
return scriptContent;
|
|
116
212
|
}
|
package/src/constants/config.ts
CHANGED
|
@@ -71,8 +71,12 @@ ${formatText("COMMANDS", 'yellow', true)}
|
|
|
71
71
|
${formatText("dev --browser", 'cyan', true)} Start development server and open in browser
|
|
72
72
|
${formatText("deploy", 'cyan', true)} Deploy activity to Adobe Target
|
|
73
73
|
${formatText("deploy --dry-run", 'cyan', true)} Deploy in dry-run mode without actual deployment
|
|
74
|
+
${formatText("deploy --force", 'cyan', true)} Override the 60s post-deploy cooldown lock
|
|
75
|
+
${formatText("sync", 'cyan', true)} Sync build.config.json with the AT activity (pages, experiences, names)
|
|
76
|
+
${formatText("sync --scaffold", 'cyan', true)} Sync and auto-create any missing variation folders with boilerplate
|
|
74
77
|
${formatText("doctor", 'cyan', true)} Diagnose and fix project configuration issues
|
|
75
78
|
${formatText("doctor --fix", 'cyan', true)} Automatically fix detected configuration issues
|
|
79
|
+
${formatText("install-extension", 'cyan', true)} Install the bundled at-builder VSCode extension (.vsix)
|
|
76
80
|
|
|
77
81
|
${formatText("GLOBAL OPTIONS", 'yellow', true)}
|
|
78
82
|
${formatText("-v, --verbose", 'green')} Enable detailed logging
|
|
@@ -100,7 +104,13 @@ ${formatText("EXAMPLES", 'yellow', true)}
|
|
|
100
104
|
|
|
101
105
|
${formatText("# Deploy in dry-run mode", 'gray')}
|
|
102
106
|
${formatText("atb deploy --dry-run", 'white')}
|
|
103
|
-
|
|
107
|
+
|
|
108
|
+
${formatText("# Sync build.config.json from Adobe Target", 'gray')}
|
|
109
|
+
${formatText("atb sync", 'white')}
|
|
110
|
+
|
|
111
|
+
${formatText("# Sync and scaffold missing variation folders", 'gray')}
|
|
112
|
+
${formatText("atb sync --scaffold", 'white')}
|
|
113
|
+
|
|
104
114
|
${formatText("# Check project configuration", 'gray')}
|
|
105
115
|
${formatText("atb doctor", 'white')}
|
|
106
116
|
|
|
@@ -136,11 +146,19 @@ ACTIVITY_FOLDER_NAME=""
|
|
|
136
146
|
PUPPETEER_LANDING_PAGE=""
|
|
137
147
|
TARGET_URL=""
|
|
138
148
|
LOGIN_URL=""
|
|
149
|
+
|
|
150
|
+
# Dev-server selection (used by \`atb dev --browser\`).
|
|
151
|
+
# Edit and save while puppeteer is running to hot-swap the previewed bundle.
|
|
152
|
+
# PAGE is only meaningful for multi-page activities — leave empty otherwise.
|
|
139
153
|
VARIATION="Variation-1"
|
|
154
|
+
PAGE=""
|
|
155
|
+
|
|
140
156
|
NODE_ENV="development"
|
|
141
157
|
VERBOSE=false
|
|
142
158
|
|
|
143
159
|
# Adobe Target Deployment Configuration
|
|
160
|
+
# ADOBE_TENANT is your AT tenant slug — find it in the AT URL after "mc.adobe.io/".
|
|
161
|
+
ADOBE_TENANT=""
|
|
144
162
|
ADOBE_CLIENT_ID=""
|
|
145
163
|
ADOBE_CLIENT_SECRET=""
|
|
146
164
|
`;
|
|
@@ -150,8 +168,55 @@ ADOBE_CLIENT_SECRET=""
|
|
|
150
168
|
} else {
|
|
151
169
|
console.log('.env file already exists!');
|
|
152
170
|
}
|
|
153
|
-
|
|
171
|
+
// watch-config.json is no longer scaffolded by `atb init` — VARIATION/PAGE
|
|
172
|
+
// now live in .env. Existing projects with a watch-config.json keep working
|
|
173
|
+
// (puppeteer.js prefers it with a deprecation warning); `atb doctor --fix`
|
|
174
|
+
// migrates the values into .env and deletes the legacy file.
|
|
154
175
|
createAdobeConfig(basePath);
|
|
176
|
+
createGitignore(basePath);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Default .gitignore content for at-builder consumer projects.
|
|
181
|
+
*
|
|
182
|
+
* Shared between `atb init` (via setupEnv) and `atb doctor --fix` so the two
|
|
183
|
+
* paths stay in lockstep. Covers secrets, deploy-runtime artifacts, build
|
|
184
|
+
* output, and OS noise. Does NOT ignore the Activities folder — those are the
|
|
185
|
+
* user's source files and should be committed.
|
|
186
|
+
*/
|
|
187
|
+
export const GITIGNORE_TEMPLATE = `# Dependencies
|
|
188
|
+
node_modules/
|
|
189
|
+
|
|
190
|
+
# Environment / secrets — never commit
|
|
191
|
+
.env
|
|
192
|
+
.env.local
|
|
193
|
+
|
|
194
|
+
# Adobe Target deploy runtime (cooldown lock written by \`atb deploy\`)
|
|
195
|
+
.deploy-lock
|
|
196
|
+
|
|
197
|
+
# Build output (regenerated by \`atb build\`)
|
|
198
|
+
dist/
|
|
199
|
+
|
|
200
|
+
# OS noise
|
|
201
|
+
.DS_Store
|
|
202
|
+
|
|
203
|
+
# Editor / local-history
|
|
204
|
+
.history/
|
|
205
|
+
|
|
206
|
+
# Logs
|
|
207
|
+
*.log
|
|
208
|
+
npm-debug.log*
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const createGitignore = (basePath) => {
|
|
212
|
+
const gitignorePath = path.join(basePath, '.gitignore');
|
|
213
|
+
|
|
214
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
215
|
+
fs.writeFileSync(gitignorePath, GITIGNORE_TEMPLATE, 'utf8');
|
|
216
|
+
console.log('.gitignore file created successfully!');
|
|
217
|
+
} else {
|
|
218
|
+
console.log('.gitignore file already exists!');
|
|
219
|
+
}
|
|
155
220
|
}
|
|
156
221
|
|
|
157
222
|
|
|
@@ -160,9 +225,14 @@ const createWatchConfig = (basePath) => {
|
|
|
160
225
|
// Path to the watch-config.json file
|
|
161
226
|
const watchConfigPath = path.join(basePath, 'watch-config.json');
|
|
162
227
|
|
|
163
|
-
// Default content for watch-config.json
|
|
228
|
+
// Default content for watch-config.json.
|
|
229
|
+
// VARIATION : which variation/experience to preview in `atb dev --browser`
|
|
230
|
+
// PAGE : leave empty for single-page activities; set to the page
|
|
231
|
+
// subfolder name (e.g. "Global", "Cart") for multi-page.
|
|
232
|
+
// Editing this file mid-session hot-swaps the preview.
|
|
164
233
|
const watchConfigContent = {
|
|
165
|
-
"VARIATION": "Variation-1"
|
|
234
|
+
"VARIATION": "Variation-1",
|
|
235
|
+
"PAGE": ""
|
|
166
236
|
};
|
|
167
237
|
|
|
168
238
|
// Check if the watch-config.json file already exists
|
|
@@ -181,14 +251,19 @@ const createAdobeConfig = (basePath) => {
|
|
|
181
251
|
// Default content for adobe.config.js
|
|
182
252
|
const adobeConfigContent = `/**
|
|
183
253
|
* Adobe Target API Configuration
|
|
184
|
-
*
|
|
185
|
-
*
|
|
186
|
-
*
|
|
254
|
+
*
|
|
255
|
+
* Used by at-sync.js and at-deploy.js. BASE_URL is the activities root —
|
|
256
|
+
* callers append \`\${activityType}/\${activityId}\` (e.g. ab/12345, xt/67890).
|
|
257
|
+
*
|
|
258
|
+
* ADOBE_TENANT comes from the consumer .env. Both at-sync and at-deploy load
|
|
259
|
+
* dotenv before requiring this file, so process.env is populated by the time
|
|
260
|
+
* BASE_URL is built.
|
|
187
261
|
*/
|
|
188
262
|
|
|
263
|
+
const TENANT = process.env.ADOBE_TENANT || 'YOUR_TENANT';
|
|
264
|
+
|
|
189
265
|
module.exports = {
|
|
190
|
-
|
|
191
|
-
BASE_URL: 'https://mc.adobe.io/ups/target/activities/ab/',
|
|
266
|
+
BASE_URL: \`https://mc.adobe.io/\${TENANT}/target/activities/\`,
|
|
192
267
|
IMS_TOKEN_URL: 'https://ims-na1.adobelogin.com/ims/token/v3',
|
|
193
268
|
IMS_SCOPE: 'openid,AdobeID,target_sdk,additional_info.projectedProductContext'
|
|
194
269
|
};`;
|
package/src/index.ts
CHANGED
|
@@ -85,10 +85,21 @@ const setupCommander = async () => {
|
|
|
85
85
|
.command('deploy')
|
|
86
86
|
.description('Deploy activity to Adobe Target using at-deploy.js')
|
|
87
87
|
.option('--dry-run', 'Run deployment in dry-run mode without actual deployment')
|
|
88
|
+
.option('--force', 'Override the 60s post-deploy cooldown lock')
|
|
88
89
|
.action(async (options, command) => {
|
|
89
90
|
const globalOpts = command.parent.opts();
|
|
90
91
|
checkEnvFile();
|
|
91
|
-
await handleDeploy(options.dryRun, globalOpts.verbose);
|
|
92
|
+
await handleDeploy(options.dryRun, options.force, globalOpts.verbose);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
program
|
|
96
|
+
.command('sync')
|
|
97
|
+
.description('Sync build.config.json with the Adobe Target activity definition')
|
|
98
|
+
.option('--scaffold', 'Auto-create missing variation folders with boilerplate')
|
|
99
|
+
.action(async (options, command) => {
|
|
100
|
+
const globalOpts = command.parent.opts();
|
|
101
|
+
checkEnvFile();
|
|
102
|
+
await handleSync(options.scaffold, globalOpts.verbose);
|
|
92
103
|
});
|
|
93
104
|
|
|
94
105
|
program
|
|
@@ -100,6 +111,15 @@ const setupCommander = async () => {
|
|
|
100
111
|
await handleDoctor(options.fix, globalOpts.verbose);
|
|
101
112
|
});
|
|
102
113
|
|
|
114
|
+
program
|
|
115
|
+
.command('install-extension')
|
|
116
|
+
.description('Install the bundled at-builder VSCode extension (.vsix)')
|
|
117
|
+
.option('--editor <bin>', 'Editor CLI to use (e.g. code, cursor, codium)', 'code')
|
|
118
|
+
.action(async (options, command) => {
|
|
119
|
+
const globalOpts = command.parent.opts();
|
|
120
|
+
await handleInstallExtension(options.editor, globalOpts.verbose);
|
|
121
|
+
});
|
|
122
|
+
|
|
103
123
|
await program.parseAsync(process.argv);
|
|
104
124
|
|
|
105
125
|
logger.info("setupCommander", "Commander setup complete");
|
|
@@ -110,16 +130,22 @@ const setupCommander = async () => {
|
|
|
110
130
|
/**
|
|
111
131
|
* Spawn a command using `npm` with the given command array and environment object.
|
|
112
132
|
*
|
|
133
|
+
* Any entries in `scriptArgs` are forwarded to the underlying script via npm's
|
|
134
|
+
* `--` passthrough (e.g. `npm run foo -- --dry-run`).
|
|
135
|
+
*
|
|
113
136
|
* @param commandArr - The array of command strings to pass to `npm`.
|
|
114
137
|
* @param env - The environment object to pass to the spawned process.
|
|
138
|
+
* @param scriptArgs - Optional flags/args to forward to the npm script itself.
|
|
115
139
|
*/
|
|
116
|
-
const runCommand = (commandArr: string[], env: NodeJS.ProcessEnv): void => {
|
|
117
|
-
|
|
140
|
+
const runCommand = (commandArr: string[], env: NodeJS.ProcessEnv, scriptArgs: string[] = []): void => {
|
|
141
|
+
const fullArgs = scriptArgs.length > 0
|
|
142
|
+
? [...commandArr, "--", ...scriptArgs]
|
|
143
|
+
: commandArr;
|
|
144
|
+
|
|
145
|
+
logger.info("runCommand", `Running npm command [${fullArgs.join(", ")}]`);
|
|
118
146
|
logger.info("runCommand", `Current working directory: ${process.cwd()}`);
|
|
119
|
-
// logger.log(`Environment variables: ${JSON.stringify(env, null, 2)}`);
|
|
120
147
|
|
|
121
|
-
|
|
122
|
-
spawn("npm", commandArr, {
|
|
148
|
+
spawn("npm", fullArgs, {
|
|
123
149
|
cwd: path.join(__dirname, "../"),
|
|
124
150
|
env,
|
|
125
151
|
shell: true,
|
|
@@ -191,12 +217,106 @@ const handleDev = async (browser: boolean, verbose: boolean): Promise<void> => {
|
|
|
191
217
|
/**
|
|
192
218
|
* Handles the deploy command
|
|
193
219
|
*/
|
|
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
|
-
|
|
220
|
+
const handleDeploy = async (dryRun: boolean, force: boolean, verbose: boolean): Promise<void> => {
|
|
221
|
+
if (verbose) logger.info("verbose", `Deploying to Adobe Target with dry-run=${dryRun} force=${force}`);
|
|
222
|
+
|
|
197
223
|
logger.info("handleDeploy", "Running Adobe Target deployment");
|
|
198
|
-
|
|
199
|
-
|
|
224
|
+
|
|
225
|
+
const scriptArgs: string[] = [];
|
|
226
|
+
if (dryRun) scriptArgs.push('--dry-run');
|
|
227
|
+
if (force) scriptArgs.push('--force');
|
|
228
|
+
|
|
229
|
+
runCommand(['run', 'atb:build:deploy'], productionEnv, scriptArgs);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Handles the sync command
|
|
234
|
+
*/
|
|
235
|
+
const handleSync = async (scaffold: boolean, verbose: boolean): Promise<void> => {
|
|
236
|
+
if (verbose) logger.info("verbose", `Syncing build.config.json with scaffold=${scaffold}`);
|
|
237
|
+
|
|
238
|
+
logger.info("handleSync", "Running Adobe Target sync");
|
|
239
|
+
|
|
240
|
+
const scriptArgs: string[] = [];
|
|
241
|
+
if (scaffold) scriptArgs.push('--scaffold');
|
|
242
|
+
|
|
243
|
+
runCommand(['run', 'atb:build:sync'], productionEnv, scriptArgs);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Handles the install-extension command.
|
|
248
|
+
*
|
|
249
|
+
* Locates the bundled at-builder-*.vsix in at-builder's install directory
|
|
250
|
+
* (one level up from this compiled file) and installs it via the editor's
|
|
251
|
+
* CLI. Defaults to `code`; users on Cursor/VSCodium can pass --editor.
|
|
252
|
+
*
|
|
253
|
+
* The .vsix lives inside the at-builder package itself, NOT the consumer's
|
|
254
|
+
* project — so it resolves via __dirname, not PWD.
|
|
255
|
+
*/
|
|
256
|
+
const handleInstallExtension = async (editor: string, verbose: boolean): Promise<void> => {
|
|
257
|
+
if (verbose) logger.info("verbose", `Installing VSCode extension via ${editor}`);
|
|
258
|
+
|
|
259
|
+
const installRoot = path.join(__dirname, "..");
|
|
260
|
+
|
|
261
|
+
let candidates: string[];
|
|
262
|
+
try {
|
|
263
|
+
candidates = fs.readdirSync(installRoot)
|
|
264
|
+
.filter(f => /^at-builder-.+\.vsix$/.test(f))
|
|
265
|
+
.sort()
|
|
266
|
+
.reverse(); // latest version first by lexicographic sort
|
|
267
|
+
} catch (error) {
|
|
268
|
+
console.error(`❌ Failed to scan at-builder directory: ${error.message}`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (candidates.length === 0) {
|
|
273
|
+
console.error("❌ No at-builder VSCode extension (.vsix) found bundled with this install.");
|
|
274
|
+
console.error(`💡 Expected at: ${installRoot}/at-builder-*.vsix`);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const vsixPath = path.join(installRoot, candidates[0]);
|
|
279
|
+
console.log(`📦 Installing ${candidates[0]} via "${editor}"...`);
|
|
280
|
+
|
|
281
|
+
const result = spawn(editor, ["--install-extension", vsixPath], {
|
|
282
|
+
stdio: "inherit",
|
|
283
|
+
shell: true
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
result.on("error", (err) => {
|
|
287
|
+
// ENOENT here means the editor binary isn't on PATH.
|
|
288
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
289
|
+
console.error(`❌ "${editor}" CLI not found in PATH.`);
|
|
290
|
+
if (editor === "code") {
|
|
291
|
+
console.error("💡 Open VSCode and run Command Palette → \"Shell Command: Install 'code' command in PATH\".");
|
|
292
|
+
} else {
|
|
293
|
+
console.error(`💡 Make sure the "${editor}" CLI is installed and on your PATH, or pass a different --editor.`);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
console.error(`❌ Failed to launch "${editor}": ${err.message}`);
|
|
297
|
+
}
|
|
298
|
+
process.exit(1);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
result.on("exit", (code) => {
|
|
302
|
+
if (code === 0) {
|
|
303
|
+
console.log("✅ Extension installed. Reload your editor window to activate.");
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
// 127 = shell's "command not found". With shell:true the spawn 'error'
|
|
307
|
+
// event never fires for missing CLIs, so we surface the same hint here.
|
|
308
|
+
if (code === 127) {
|
|
309
|
+
console.error(`❌ "${editor}" CLI not found in PATH.`);
|
|
310
|
+
if (editor === "code") {
|
|
311
|
+
console.error("💡 Open VSCode and run Command Palette → \"Shell Command: Install 'code' command in PATH\".");
|
|
312
|
+
} else {
|
|
313
|
+
console.error(`💡 Make sure the "${editor}" CLI is installed and on your PATH, or pass a different --editor.`);
|
|
314
|
+
}
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
console.error(`❌ "${editor} --install-extension" exited with code ${code}.`);
|
|
318
|
+
process.exit(code ?? 1);
|
|
319
|
+
});
|
|
200
320
|
};
|
|
201
321
|
|
|
202
322
|
/**
|