at-builder 1.2.2 → 1.2.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.
@@ -0,0 +1,383 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import logger from "./logger";
4
+
5
+ export interface DiagnosticIssue {
6
+ id: string;
7
+ severity: 'error' | 'warning';
8
+ message: string;
9
+ suggestion?: string;
10
+ fixable: boolean;
11
+ file?: string;
12
+ }
13
+
14
+ /**
15
+ * Run comprehensive diagnostics on the project
16
+ */
17
+ export const runDiagnostics = async (projectPath: string, verbose: boolean = false): Promise<DiagnosticIssue[]> => {
18
+ const issues: DiagnosticIssue[] = [];
19
+
20
+ if (verbose) logger.info("runDiagnostics", `Running diagnostics on ${projectPath}`);
21
+
22
+ // Check for required files
23
+ await checkEnvFile(projectPath, issues);
24
+ await checkAdobeConfig(projectPath, issues);
25
+ await checkWatchConfig(projectPath, issues);
26
+ await checkPackageJson(projectPath, issues);
27
+ await checkActivitiesFolder(projectPath, issues);
28
+
29
+ // Check environment variables
30
+ await checkEnvVariables(projectPath, issues);
31
+
32
+ // Check dependencies
33
+ await checkDependencies(projectPath, issues);
34
+
35
+ return issues;
36
+ };
37
+
38
+ /**
39
+ * Attempt to fix the provided issues
40
+ */
41
+ export const fixIssues = async (issues: DiagnosticIssue[], projectPath: string, verbose: boolean = false): Promise<number> => {
42
+ let fixedCount = 0;
43
+
44
+ for (const issue of issues) {
45
+ if (!issue.fixable) continue;
46
+
47
+ try {
48
+ if (verbose) logger.info("fixIssues", `Attempting to fix: ${issue.id}`);
49
+
50
+ const success = await fixIssue(issue, projectPath, verbose);
51
+ if (success) {
52
+ console.log(`✅ Fixed: ${issue.message}`);
53
+ fixedCount++;
54
+ } else {
55
+ console.log(`❌ Could not fix: ${issue.message}`);
56
+ }
57
+ } catch (error) {
58
+ console.log(`❌ Error fixing ${issue.id}: ${error.message}`);
59
+ }
60
+ }
61
+
62
+ return fixedCount;
63
+ };
64
+
65
+ /**
66
+ * Fix a specific issue
67
+ */
68
+ const fixIssue = async (issue: DiagnosticIssue, projectPath: string, verbose: boolean): Promise<boolean> => {
69
+ switch (issue.id) {
70
+ case 'missing-env':
71
+ return await createEnvFile(projectPath);
72
+ case 'missing-adobe-config':
73
+ return await createAdobeConfig(projectPath);
74
+ case 'missing-watch-config':
75
+ return await createWatchConfig(projectPath);
76
+ case 'missing-activities-folder':
77
+ return await createActivitiesFolder(projectPath);
78
+ case 'missing-package-json':
79
+ return await createPackageJson(projectPath);
80
+ default:
81
+ return false;
82
+ }
83
+ };
84
+
85
+ /**
86
+ * Check if .env file exists and has required variables
87
+ */
88
+ const checkEnvFile = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
89
+ const envPath = path.join(projectPath, '.env');
90
+
91
+ if (!fs.existsSync(envPath)) {
92
+ issues.push({
93
+ id: 'missing-env',
94
+ severity: 'error',
95
+ message: '.env file is missing',
96
+ suggestion: 'Create .env file with required environment variables',
97
+ fixable: true,
98
+ file: '.env'
99
+ });
100
+ return;
101
+ }
102
+
103
+ // Check if .env has required variables
104
+ const envContent = fs.readFileSync(envPath, 'utf8');
105
+ const requiredVars = [
106
+ 'ACTIVITIES_BASE_FOLDER',
107
+ 'ACTIVITY_FOLDER_NAME',
108
+ 'ADOBE_CLIENT_ID',
109
+ 'ADOBE_CLIENT_SECRET'
110
+ ];
111
+
112
+ const missingVars = requiredVars.filter(varName => !envContent.includes(varName));
113
+
114
+ if (missingVars.length > 0) {
115
+ issues.push({
116
+ id: 'incomplete-env',
117
+ severity: 'warning',
118
+ message: `.env file missing variables: ${missingVars.join(', ')}`,
119
+ suggestion: 'Add missing environment variables to .env file',
120
+ fixable: true,
121
+ file: '.env'
122
+ });
123
+ }
124
+ };
125
+
126
+ /**
127
+ * Check if adobe.config.js exists
128
+ */
129
+ const checkAdobeConfig = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
130
+ const configPath = path.join(projectPath, 'adobe.config.js');
131
+
132
+ if (!fs.existsSync(configPath)) {
133
+ issues.push({
134
+ id: 'missing-adobe-config',
135
+ severity: 'error',
136
+ message: 'adobe.config.js file is missing',
137
+ suggestion: 'Create Adobe Target API configuration file',
138
+ fixable: true,
139
+ file: 'adobe.config.js'
140
+ });
141
+ }
142
+ };
143
+
144
+ /**
145
+ * Check if watch-config.json exists
146
+ */
147
+ const checkWatchConfig = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
148
+ const configPath = path.join(projectPath, 'watch-config.json');
149
+
150
+ if (!fs.existsSync(configPath)) {
151
+ issues.push({
152
+ id: 'missing-watch-config',
153
+ severity: 'warning',
154
+ message: 'watch-config.json file is missing',
155
+ suggestion: 'Create build configuration file',
156
+ fixable: true,
157
+ file: 'watch-config.json'
158
+ });
159
+ }
160
+ };
161
+
162
+ /**
163
+ * Check if package.json exists
164
+ */
165
+ const checkPackageJson = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
166
+ const packagePath = path.join(projectPath, 'package.json');
167
+
168
+ if (!fs.existsSync(packagePath)) {
169
+ issues.push({
170
+ id: 'missing-package-json',
171
+ severity: 'error',
172
+ message: 'package.json file is missing',
173
+ suggestion: 'Initialize npm project with package.json',
174
+ fixable: true,
175
+ file: 'package.json'
176
+ });
177
+ }
178
+ };
179
+
180
+ /**
181
+ * Check if Activities folder exists
182
+ */
183
+ const checkActivitiesFolder = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
184
+ // Try to read ACTIVITIES_BASE_FOLDER from .env, fallback to default
185
+ let activitiesFolder = 'Activities';
186
+ const envPath = path.join(projectPath, '.env');
187
+
188
+ if (fs.existsSync(envPath)) {
189
+ const envContent = fs.readFileSync(envPath, 'utf8');
190
+ const match = envContent.match(/ACTIVITIES_BASE_FOLDER=["']?([^"'\n\r]+)["']?/);
191
+ if (match) {
192
+ activitiesFolder = match[1];
193
+ }
194
+ }
195
+
196
+ const activitiesPath = path.join(projectPath, activitiesFolder);
197
+
198
+ if (!fs.existsSync(activitiesPath)) {
199
+ issues.push({
200
+ id: 'missing-activities-folder',
201
+ severity: 'warning',
202
+ message: `${activitiesFolder} folder is missing`,
203
+ suggestion: `Create ${activitiesFolder} folder for your activities`,
204
+ fixable: true,
205
+ file: activitiesFolder
206
+ });
207
+ }
208
+ };
209
+
210
+ /**
211
+ * Check environment variables content
212
+ */
213
+ const checkEnvVariables = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
214
+ const envPath = path.join(projectPath, '.env');
215
+ if (!fs.existsSync(envPath)) return;
216
+
217
+ const envContent = fs.readFileSync(envPath, 'utf8');
218
+
219
+ // Check if ACTIVITY_FOLDER_NAME is empty
220
+ if (envContent.includes('ACTIVITY_FOLDER_NAME=""') || envContent.includes('ACTIVITY_FOLDER_NAME=\'\'')) {
221
+ issues.push({
222
+ id: 'empty-activity-folder-name',
223
+ severity: 'warning',
224
+ message: 'ACTIVITY_FOLDER_NAME is empty',
225
+ suggestion: 'Set ACTIVITY_FOLDER_NAME to your activity folder name',
226
+ fixable: false
227
+ });
228
+ }
229
+
230
+ // Check if Adobe credentials are empty
231
+ if (envContent.includes('ADOBE_CLIENT_ID=""') || envContent.includes('ADOBE_CLIENT_ID=\'\'')) {
232
+ issues.push({
233
+ id: 'empty-adobe-client-id',
234
+ severity: 'warning',
235
+ message: 'ADOBE_CLIENT_ID is empty',
236
+ suggestion: 'Set your Adobe Target API client ID',
237
+ fixable: false
238
+ });
239
+ }
240
+
241
+ if (envContent.includes('ADOBE_CLIENT_SECRET=""') || envContent.includes('ADOBE_CLIENT_SECRET=\'\'')) {
242
+ issues.push({
243
+ id: 'empty-adobe-client-secret',
244
+ severity: 'warning',
245
+ message: 'ADOBE_CLIENT_SECRET is empty',
246
+ suggestion: 'Set your Adobe Target API client secret',
247
+ fixable: false
248
+ });
249
+ }
250
+ };
251
+
252
+ /**
253
+ * Check dependencies
254
+ */
255
+ const checkDependencies = async (projectPath: string, issues: DiagnosticIssue[]): Promise<void> => {
256
+ const packagePath = path.join(projectPath, 'package.json');
257
+ if (!fs.existsSync(packagePath)) return;
258
+
259
+ const nodeModulesPath = path.join(projectPath, 'node_modules');
260
+
261
+ if (!fs.existsSync(nodeModulesPath)) {
262
+ issues.push({
263
+ id: 'missing-node-modules',
264
+ severity: 'warning',
265
+ message: 'node_modules folder is missing',
266
+ suggestion: 'Run "npm install" to install dependencies',
267
+ fixable: false
268
+ });
269
+ }
270
+ };
271
+
272
+ // Fix functions
273
+
274
+ const createEnvFile = async (projectPath: string): Promise<boolean> => {
275
+ const envPath = path.join(projectPath, '.env');
276
+ const envContent = `ACTIVITIES_BASE_FOLDER="Activities"
277
+ ACTIVITY_FOLDER_NAME=""
278
+ PUPPETEER_LANDING_PAGE=""
279
+ TARGET_URL=""
280
+ LOGIN_URL=""
281
+ VARIATION="Variation-1"
282
+ NODE_ENV="development"
283
+ VERBOSE=false
284
+
285
+ # Adobe Target Deployment Configuration
286
+ ADOBE_CLIENT_ID=""
287
+ ADOBE_CLIENT_SECRET=""`;
288
+
289
+ try {
290
+ fs.writeFileSync(envPath, envContent, 'utf8');
291
+ return true;
292
+ } catch (error) {
293
+ return false;
294
+ }
295
+ };
296
+
297
+ const createAdobeConfig = async (projectPath: string): Promise<boolean> => {
298
+ const configPath = path.join(projectPath, 'adobe.config.js');
299
+ const configContent = `/**
300
+ * Adobe Target API Configuration
301
+ *
302
+ * Configuration constants for Adobe Target API integration.
303
+ * These values are used by the deployment script to connect to Adobe Target.
304
+ */
305
+
306
+ module.exports = {
307
+ // Adobe Target API base URL
308
+ BASE_URL: 'https://mc.adobe.io/target/activities/',
309
+
310
+ // Adobe IMS (Identity Management Services) token endpoint
311
+ IMS_TOKEN_URL: 'https://ims-na1.adobelogin.com/ims/token/v1',
312
+
313
+ // Required scopes for Adobe Target API access
314
+ IMS_SCOPE: 'openid,target_sdk'
315
+ };`;
316
+
317
+ try {
318
+ fs.writeFileSync(configPath, configContent, 'utf8');
319
+ return true;
320
+ } catch (error) {
321
+ return false;
322
+ }
323
+ };
324
+
325
+ const createWatchConfig = async (projectPath: string): Promise<boolean> => {
326
+ const configPath = path.join(projectPath, 'watch-config.json');
327
+ const configContent = {
328
+ "VARIATION": "Variation-1"
329
+ };
330
+
331
+ try {
332
+ fs.writeFileSync(configPath, JSON.stringify(configContent, null, 2), 'utf8');
333
+ return true;
334
+ } catch (error) {
335
+ return false;
336
+ }
337
+ };
338
+
339
+ const createActivitiesFolder = async (projectPath: string): Promise<boolean> => {
340
+ // Try to read ACTIVITIES_BASE_FOLDER from .env, fallback to default
341
+ let activitiesFolder = 'Activities';
342
+ const envPath = path.join(projectPath, '.env');
343
+
344
+ if (fs.existsSync(envPath)) {
345
+ const envContent = fs.readFileSync(envPath, 'utf8');
346
+ const match = envContent.match(/ACTIVITIES_BASE_FOLDER=["']?([^"'\n\r]+)["']?/);
347
+ if (match) {
348
+ activitiesFolder = match[1];
349
+ }
350
+ }
351
+
352
+ const activitiesPath = path.join(projectPath, activitiesFolder);
353
+
354
+ try {
355
+ fs.mkdirSync(activitiesPath, { recursive: true });
356
+ return true;
357
+ } catch (error) {
358
+ return false;
359
+ }
360
+ };
361
+
362
+ const createPackageJson = async (projectPath: string): Promise<boolean> => {
363
+ const packagePath = path.join(projectPath, 'package.json');
364
+ const packageContent = {
365
+ "name": path.basename(projectPath),
366
+ "version": "1.0.0",
367
+ "description": "",
368
+ "main": "index.js",
369
+ "scripts": {
370
+ "test": "echo \"Error: no test specified\" && exit 1"
371
+ },
372
+ "keywords": [],
373
+ "author": "",
374
+ "license": "ISC"
375
+ };
376
+
377
+ try {
378
+ fs.writeFileSync(packagePath, JSON.stringify(packageContent, null, 2), 'utf8');
379
+ return true;
380
+ } catch (error) {
381
+ return false;
382
+ }
383
+ };
@@ -0,0 +1,21 @@
1
+ // Test validation logic
2
+ const { spawn } = require('child_process');
3
+ const path = require('path');
4
+
5
+ console.log('Testing build command without .env file...');
6
+
7
+ // Test without .env file
8
+ const testDir = path.join(__dirname, 'test-empty');
9
+ require('fs').mkdirSync(testDir, { recursive: true });
10
+
11
+ const webpack = spawn('node', [path.join(__dirname, 'bin/index.js'), 'build'], {
12
+ cwd: testDir,
13
+ env: { ...process.env, ACTIVITY_FOLDER_NAME: '' },
14
+ stdio: 'inherit'
15
+ });
16
+
17
+ webpack.on('exit', (code) => {
18
+ console.log(`Build process exited with code: ${code}`);
19
+ // Clean up
20
+ require('fs').rmSync(testDir, { recursive: true, force: true });
21
+ });
package/tsconfig.json CHANGED
@@ -22,7 +22,7 @@
22
22
  "experimentalDecorators": true,
23
23
  "skipLibCheck": true,
24
24
  "esModuleInterop": true,
25
- "resolveJsonModule": true,
25
+ "resolveJsonModule": true
26
26
  },
27
27
  "include": [
28
28
  "src/**/*",
package/webpack.config.js CHANGED
@@ -74,7 +74,40 @@ var traversePath = function (dir) {
74
74
 
75
75
  // console.log("\x1b[33m%s\x1b[0m", `${process.env.ACTIVITY_FOLDER_NAME}`);
76
76
 
77
- var files = traversePath(path.join(PWD, 'Activities', process.env.ACTIVITY_FOLDER_NAME).toString());
77
+ // Validate required environment variables
78
+ if (!process.env.ACTIVITY_FOLDER_NAME || process.env.ACTIVITY_FOLDER_NAME.trim() === '') {
79
+ console.error('\x1b[31m%s\x1b[0m', '❌ Error: ACTIVITY_FOLDER_NAME environment variable is required but not set.');
80
+ console.error('\x1b[33m%s\x1b[0m', '💡 Please set ACTIVITY_FOLDER_NAME in your .env file or run "atb doctor --fix" to check your configuration.');
81
+ process.exit(1);
82
+ }
83
+
84
+ // Use configurable activities folder name from environment
85
+ const ACTIVITIES_BASE_FOLDER = process.env.ACTIVITIES_BASE_FOLDER || 'Activities';
86
+ const ACTIVITY_FOLDER = process.env.ACTIVITY_FOLDER_NAME.trim();
87
+
88
+ console.log('\x1b[36m%s\x1b[0m', `📦 Building activity: ${ACTIVITY_FOLDER}`);
89
+ console.log('\x1b[36m%s\x1b[0m', `📁 Activities folder: ${ACTIVITIES_BASE_FOLDER}`);
90
+
91
+ // Check if the activity folder exists
92
+ const activityPath = path.join(PWD, ACTIVITIES_BASE_FOLDER, ACTIVITY_FOLDER);
93
+ if (!fs.existsSync(activityPath)) {
94
+ console.error('\x1b[31m%s\x1b[0m', `❌ Error: Activity folder not found: ${activityPath}`);
95
+ console.error('\x1b[33m%s\x1b[0m', '💡 Please check your ACTIVITY_FOLDER_NAME or create the activity folder first.');
96
+ console.error('\x1b[33m%s\x1b[0m', '💡 Run "atb new" to create a new activity or "atb doctor" to diagnose issues.');
97
+ process.exit(1);
98
+ }
99
+
100
+ var files = traversePath(activityPath.toString());
101
+
102
+ // Check if we found any files to build
103
+ if (!files || files.length === 0) {
104
+ console.error('\x1b[31m%s\x1b[0m', `❌ Error: No buildable files found in ${activityPath}`);
105
+ console.error('\x1b[33m%s\x1b[0m', '💡 Please ensure your activity folder contains variations (folders starting with "V") with .js or .ts files.');
106
+ console.error('\x1b[33m%s\x1b[0m', '💡 Expected structure: Activities/your-activity/Variation-1/index.js');
107
+ process.exit(1);
108
+ }
109
+
110
+ console.log('\x1b[36m%s\x1b[0m', `🔍 Found ${files.length} file(s) to build`);
78
111
 
79
112
  //Create entry object which is needed for webpack config
80
113
  var entries = {};