fraim-framework 2.0.35 โ 2.0.36
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/bin/fraim.js +52 -5
- package/dist/registry/scripts/cleanup-branch.js +62 -33
- package/dist/registry/scripts/generate-engagement-emails.js +119 -44
- package/dist/registry/scripts/newsletter-helpers.js +208 -268
- package/dist/registry/scripts/profile-server.js +387 -0
- package/dist/tests/test-chalk-regression.js +18 -2
- package/dist/tests/test-client-scripts-validation.js +133 -0
- package/dist/tests/test-prep-issue.js +1 -34
- package/dist/tests/test-script-location-independence.js +76 -28
- package/package.json +2 -2
- package/registry/agent-guardrails.md +62 -62
- package/registry/rules/communication.md +121 -121
- package/registry/rules/continuous-learning.md +54 -54
- package/registry/rules/hitl-ppe-record-analysis.md +302 -302
- package/registry/rules/software-development-lifecycle.md +104 -104
- package/registry/scripts/cleanup-branch.ts +341 -0
- package/registry/scripts/code-quality-check.sh +559 -559
- package/registry/scripts/detect-tautological-tests.sh +38 -38
- package/registry/scripts/generate-engagement-emails.ts +830 -0
- package/registry/scripts/markdown-to-pdf.js +7 -3
- package/registry/scripts/newsletter-helpers.ts +777 -0
- package/registry/scripts/prep-issue.sh +30 -61
- package/registry/scripts/profile-server.ts +424 -0
- package/registry/scripts/run-thank-you-workflow.ts +122 -0
- package/registry/scripts/send-newsletter-simple.ts +102 -0
- package/registry/scripts/send-thank-you-emails.ts +57 -0
- package/registry/scripts/validate-openapi-limits.ts +366 -366
- package/registry/scripts/validate-test-coverage.ts +280 -280
- package/registry/scripts/verify-pr-comments.sh +70 -70
- package/registry/templates/bootstrap/ARCHITECTURE-TEMPLATE.md +53 -53
- package/registry/templates/evidence/Implementation-BugEvidence.md +85 -85
- package/registry/templates/evidence/Implementation-FeatureEvidence.md +120 -120
- package/registry/workflows/customer-development/insight-analysis.md +156 -156
- package/registry/workflows/customer-development/interview-preparation.md +421 -421
- package/registry/workflows/customer-development/strategic-brainstorming.md +146 -146
- package/registry/workflows/quality-assurance/iterative-improvement-cycle.md +562 -562
- package/registry/workflows/reviewer/review-implementation-vs-feature-spec.md +669 -669
- package/dist/registry/scripts/build-scripts-generator.js +0 -205
- package/dist/registry/scripts/fraim-config.js +0 -61
- package/dist/registry/scripts/generic-issues-api.js +0 -100
- package/dist/registry/scripts/openapi-generator.js +0 -664
- package/dist/registry/scripts/performance/profile-server.js +0 -390
- package/dist/test-utils.js +0 -96
- package/dist/tests/esm-compat.js +0 -11
- package/dist/tests/test-chalk-esm-issue.js +0 -159
- package/dist/tests/test-chalk-real-world.js +0 -265
- package/dist/tests/test-chalk-resolution-issue.js +0 -304
- package/dist/tests/test-fraim-install-chalk-issue.js +0 -254
- package/dist/tests/test-npm-resolution-diagnostic.js +0 -140
- package/registry/templates/marketing/STORYTELLING-TEMPLATE.md +0 -130
- package/registry/workflows/marketing/storytelling.md +0 -65
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const child_process_1 = require("child_process");
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const path_1 = require("path");
|
|
40
|
+
function loadClientConfig() {
|
|
41
|
+
const configPath = (0, path_1.join)(process.cwd(), '.fraim', 'config.json');
|
|
42
|
+
if (!(0, fs_1.existsSync)(configPath)) {
|
|
43
|
+
throw new Error('.fraim/config.json not found. Run fraim init first.');
|
|
44
|
+
}
|
|
45
|
+
return JSON.parse((0, fs_1.readFileSync)(configPath, 'utf-8'));
|
|
46
|
+
}
|
|
47
|
+
function getEnvOr(keys, fallback) {
|
|
48
|
+
for (const key of keys) {
|
|
49
|
+
const value = process.env[key];
|
|
50
|
+
if (value && value.length)
|
|
51
|
+
return value;
|
|
52
|
+
}
|
|
53
|
+
return fallback;
|
|
54
|
+
}
|
|
55
|
+
function checkAzureCLI() {
|
|
56
|
+
try {
|
|
57
|
+
(0, child_process_1.execSync)('az --version', { stdio: 'pipe' });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function checkAzureAuth() {
|
|
65
|
+
try {
|
|
66
|
+
(0, child_process_1.execSync)('az account show', { stdio: 'pipe' });
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function getAppServicePlanMetrics(appName, resourceGroup) {
|
|
74
|
+
try {
|
|
75
|
+
console.log('\n--- โ๏ธ App Service Plan Metrics ---');
|
|
76
|
+
// Get App Service Plan ID
|
|
77
|
+
const planIdCmd = `az webapp show --name ${appName} --resource-group ${resourceGroup} --query "appServicePlanId" -o tsv`;
|
|
78
|
+
const planId = (0, child_process_1.execSync)(planIdCmd, { encoding: 'utf8' }).trim();
|
|
79
|
+
const planName = planId.split('/').pop();
|
|
80
|
+
console.log(`๐ Plan: ${planName}`);
|
|
81
|
+
// Get CPU and Memory metrics for last 5 minutes
|
|
82
|
+
console.log('๐ Fetching CPU and Memory metrics (last 5 minutes)...');
|
|
83
|
+
const metricsCmd = `az monitor metrics list --resource "${planId}" --metrics CpuPercentage MemoryPercentage --interval PT1M --query "value[].{name:name.value, data:timeseries[0].data}" -o json`;
|
|
84
|
+
const metricsOutput = (0, child_process_1.execSync)(metricsCmd, { encoding: 'utf8' });
|
|
85
|
+
const metrics = JSON.parse(metricsOutput);
|
|
86
|
+
for (const metric of metrics) {
|
|
87
|
+
const latestData = metric.data[metric.data.length - 1];
|
|
88
|
+
const value = latestData?.average || latestData?.total || 'N/A';
|
|
89
|
+
console.log(` ${metric.name}: ${typeof value === 'number' ? value.toFixed(2) + '%' : value}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.log(`โ ๏ธ Could not fetch App Service Plan metrics: ${error.message.split('\n')[0]}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function getAppServiceMetrics(appName, resourceGroup) {
|
|
97
|
+
try {
|
|
98
|
+
console.log('\n--- ๐ฅ๏ธ App Service Metrics ---');
|
|
99
|
+
// Get subscription ID
|
|
100
|
+
const subscriptionId = (0, child_process_1.execSync)('az account show --query id -o tsv', { encoding: 'utf8' }).trim();
|
|
101
|
+
// Get app-level metrics using Azure CLI
|
|
102
|
+
console.log('๐ Fetching App Service metrics (last 5 minutes)...');
|
|
103
|
+
const appResourceId = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Web/sites/${appName}`;
|
|
104
|
+
const appMetricsCmd = `az monitor metrics list --resource "${appResourceId}" --metrics CpuTime Requests Http2xx Http4xx Http5xx --interval PT1M --query "value[].{name:name.value, data:timeseries[0].data}" -o json`;
|
|
105
|
+
const appMetricsOutput = (0, child_process_1.execSync)(appMetricsCmd, { encoding: 'utf8' });
|
|
106
|
+
const appMetrics = JSON.parse(appMetricsOutput);
|
|
107
|
+
for (const metric of appMetrics) {
|
|
108
|
+
const latestData = metric.data[metric.data.length - 1];
|
|
109
|
+
const value = latestData?.total || latestData?.average || 'N/A';
|
|
110
|
+
let displayValue = value;
|
|
111
|
+
if (typeof value === 'number') {
|
|
112
|
+
if (metric.name === 'CpuTime') {
|
|
113
|
+
displayValue = `${value.toFixed(2)}s`;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
displayValue = value.toString();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
console.log(` ${metric.name}: ${displayValue}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.log(`โ ๏ธ Could not fetch App Service metrics: ${error.message.split('\n')[0]}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function getProcessInformation(appName) {
|
|
127
|
+
try {
|
|
128
|
+
console.log('\n--- โ๏ธ Process Information (Kudu API) ---');
|
|
129
|
+
// Get access token for Kudu API
|
|
130
|
+
const token = (0, child_process_1.execSync)('az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv', { encoding: 'utf8' }).trim();
|
|
131
|
+
// Import axios dynamically
|
|
132
|
+
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
133
|
+
// Get process list from Kudu
|
|
134
|
+
console.log('๐ Fetching running processes...');
|
|
135
|
+
const processResponse = await axios.get(`https://${appName}.scm.azurewebsites.net/api/processes`, {
|
|
136
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
137
|
+
timeout: 10000
|
|
138
|
+
});
|
|
139
|
+
const processes = processResponse.data;
|
|
140
|
+
// Sort by CPU usage and show top processes
|
|
141
|
+
const sortedProcesses = processes
|
|
142
|
+
.filter((p) => p.cpu_usage !== undefined)
|
|
143
|
+
.sort((a, b) => (b.cpu_usage || 0) - (a.cpu_usage || 0))
|
|
144
|
+
.slice(0, 10);
|
|
145
|
+
console.log('\n๐ฅ Top 10 Processes by CPU Usage:');
|
|
146
|
+
console.log('PID\tCPU%\tMemory(MB)\tName');
|
|
147
|
+
console.log('---\t----\t---------\t----');
|
|
148
|
+
for (const process of sortedProcesses) {
|
|
149
|
+
const pid = process.id || 'N/A';
|
|
150
|
+
const cpu = process.cpu_usage ? process.cpu_usage.toFixed(2) : '0.00';
|
|
151
|
+
const memory = process.working_set ? (process.working_set / 1024 / 1024).toFixed(1) : 'N/A';
|
|
152
|
+
const name = process.name || 'Unknown';
|
|
153
|
+
console.log(`${pid}\t${cpu}\t${memory}\t\t${name.substring(0, 30)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
console.log(`โ ๏ธ Could not fetch process information: ${error.message.split('\n')[0]}`);
|
|
158
|
+
console.log(' This might be due to authentication issues or Kudu API being unavailable.');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function getSystemInformation(appName) {
|
|
162
|
+
try {
|
|
163
|
+
console.log('\n--- ๐ฅ๏ธ System Information ---');
|
|
164
|
+
const token = (0, child_process_1.execSync)('az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv', { encoding: 'utf8' }).trim();
|
|
165
|
+
const axios = (await Promise.resolve().then(() => __importStar(require('axios')))).default;
|
|
166
|
+
// Get system info using Kudu command API
|
|
167
|
+
console.log('๐ Fetching system information...');
|
|
168
|
+
const commands = [
|
|
169
|
+
{ name: 'Memory Info', cmd: 'cat /proc/meminfo | head -10' },
|
|
170
|
+
{ name: 'CPU Info', cmd: 'cat /proc/cpuinfo | grep "model name" | head -1' },
|
|
171
|
+
{ name: 'Disk Usage', cmd: 'df -h | head -5' },
|
|
172
|
+
{ name: 'Load Average', cmd: 'uptime' }
|
|
173
|
+
];
|
|
174
|
+
for (const command of commands) {
|
|
175
|
+
try {
|
|
176
|
+
const response = await axios.post(`https://${appName}.scm.azurewebsites.net/api/command`, {
|
|
177
|
+
command: command.cmd,
|
|
178
|
+
dir: '/home'
|
|
179
|
+
}, {
|
|
180
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
181
|
+
timeout: 5000
|
|
182
|
+
});
|
|
183
|
+
console.log(`\n${command.name}:`);
|
|
184
|
+
const output = response.data.Output || response.data.Error || 'No output';
|
|
185
|
+
console.log(output.split('\n').map((line) => ` ${line}`).join('\n'));
|
|
186
|
+
}
|
|
187
|
+
catch (cmdError) {
|
|
188
|
+
console.log(`\n${command.name}: Could not retrieve (${cmdError.message.split('\n')[0]})`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
console.log(`โ ๏ธ Could not fetch system information: ${error.message.split('\n')[0]}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function getApplicationLogs(appName, resourceGroup) {
|
|
197
|
+
try {
|
|
198
|
+
console.log('\n--- ๐ Recent Application Logs ---');
|
|
199
|
+
// Get recent logs using Azure CLI
|
|
200
|
+
console.log('๐ Fetching recent application logs...');
|
|
201
|
+
const logsCmd = `az webapp log tail --name ${appName} --resource-group ${resourceGroup} --provider application --timeout 5`;
|
|
202
|
+
try {
|
|
203
|
+
const logs = (0, child_process_1.execSync)(logsCmd, { encoding: 'utf8', timeout: 10000 });
|
|
204
|
+
console.log('Recent logs:');
|
|
205
|
+
console.log(logs.split('\n').slice(-20).map(line => ` ${line}`).join('\n'));
|
|
206
|
+
}
|
|
207
|
+
catch (logError) {
|
|
208
|
+
console.log('โ ๏ธ Could not fetch logs - they may not be enabled or available');
|
|
209
|
+
console.log(' Enable application logging in Azure portal for better diagnostics');
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
console.log(`โ ๏ธ Could not fetch application logs: ${error.message.split('\n')[0]}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function profileLocalEnvironment() {
|
|
217
|
+
console.log('\n--- ๐ Local Environment Profiling ---');
|
|
218
|
+
try {
|
|
219
|
+
// Basic system information
|
|
220
|
+
console.log('๐ป System Information:');
|
|
221
|
+
if (process.platform === 'win32') {
|
|
222
|
+
try {
|
|
223
|
+
const systemInfo = (0, child_process_1.execSync)('systeminfo | findstr /C:"Total Physical Memory" /C:"Available Physical Memory" /C:"Processor"', { encoding: 'utf8' });
|
|
224
|
+
console.log(systemInfo.split('\n').map(line => ` ${line.trim()}`).filter(line => line).join('\n'));
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
console.log(' Could not retrieve Windows system info');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
try {
|
|
232
|
+
const memInfo = (0, child_process_1.execSync)('free -h', { encoding: 'utf8' });
|
|
233
|
+
const cpuInfo = (0, child_process_1.execSync)('cat /proc/cpuinfo | grep "model name" | head -1', { encoding: 'utf8' });
|
|
234
|
+
console.log(' Memory:');
|
|
235
|
+
console.log(memInfo.split('\n').map(line => ` ${line}`).join('\n'));
|
|
236
|
+
console.log(' CPU:');
|
|
237
|
+
console.log(` ${cpuInfo.trim()}`);
|
|
238
|
+
}
|
|
239
|
+
catch {
|
|
240
|
+
console.log(' Could not retrieve Linux system info');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Node.js process information
|
|
244
|
+
console.log('\n๐ข Node.js Process Information:');
|
|
245
|
+
console.log(` Node Version: ${process.version}`);
|
|
246
|
+
console.log(` Platform: ${process.platform}`);
|
|
247
|
+
console.log(` Architecture: ${process.arch}`);
|
|
248
|
+
console.log(` Memory Usage:`);
|
|
249
|
+
const memUsage = process.memoryUsage();
|
|
250
|
+
console.log(` RSS: ${(memUsage.rss / 1024 / 1024).toFixed(2)} MB`);
|
|
251
|
+
console.log(` Heap Used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
252
|
+
console.log(` Heap Total: ${(memUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`);
|
|
253
|
+
console.log(` External: ${(memUsage.external / 1024 / 1024).toFixed(2)} MB`);
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
console.log(`โ ๏ธ Error profiling local environment: ${error.message}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function main() {
|
|
260
|
+
const config = loadClientConfig();
|
|
261
|
+
// Azure configuration with environment variable fallbacks
|
|
262
|
+
const azureConfig = {
|
|
263
|
+
prodAppName: config.azure?.prodAppName || getEnvOr(['FRAIM_AZURE_PROD_APP_NAME'], 'fraim-app-prod'),
|
|
264
|
+
prodResourceGroup: config.azure?.prodResourceGroup || getEnvOr(['FRAIM_AZURE_PROD_RESOURCE_GROUP'], 'fraim-prod-rg'),
|
|
265
|
+
preprodAppName: config.azure?.preprodAppName || getEnvOr(['FRAIM_AZURE_PREPROD_APP_NAME'], 'fraim-app-pre-prod'),
|
|
266
|
+
preprodResourceGroup: config.azure?.preprodResourceGroup || getEnvOr(['FRAIM_AZURE_PREPROD_RESOURCE_GROUP'], 'fraim-pre-prod-rg'),
|
|
267
|
+
localAppName: config.azure?.localAppName || getEnvOr(['FRAIM_AZURE_LOCAL_APP_NAME'], 'local'),
|
|
268
|
+
localResourceGroup: config.azure?.localResourceGroup || getEnvOr(['FRAIM_AZURE_LOCAL_RESOURCE_GROUP'], 'local')
|
|
269
|
+
};
|
|
270
|
+
const CONFIGS = {
|
|
271
|
+
prod: {
|
|
272
|
+
env: 'prod',
|
|
273
|
+
appName: azureConfig.prodAppName,
|
|
274
|
+
resourceGroup: azureConfig.prodResourceGroup
|
|
275
|
+
},
|
|
276
|
+
preprod: {
|
|
277
|
+
env: 'preprod',
|
|
278
|
+
appName: azureConfig.preprodAppName,
|
|
279
|
+
resourceGroup: azureConfig.preprodResourceGroup
|
|
280
|
+
},
|
|
281
|
+
local: {
|
|
282
|
+
env: 'local',
|
|
283
|
+
appName: azureConfig.localAppName,
|
|
284
|
+
resourceGroup: azureConfig.localResourceGroup
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
const args = process.argv.slice(2);
|
|
288
|
+
let profileConfig = CONFIGS.local;
|
|
289
|
+
const includeLogs = args.includes('--logs');
|
|
290
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
291
|
+
console.log(`
|
|
292
|
+
๐ Azure App Service Profiler
|
|
293
|
+
|
|
294
|
+
Usage:
|
|
295
|
+
npx tsx profile-server.ts [options]
|
|
296
|
+
|
|
297
|
+
Options:
|
|
298
|
+
--prod Profile production environment
|
|
299
|
+
--preprod Profile pre-production environment
|
|
300
|
+
--local Profile local environment (default)
|
|
301
|
+
--logs Include application logs in analysis
|
|
302
|
+
--help, -h Show this help message
|
|
303
|
+
|
|
304
|
+
Examples:
|
|
305
|
+
# Profile production server
|
|
306
|
+
npx tsx profile-server.ts --prod
|
|
307
|
+
|
|
308
|
+
# Profile with logs
|
|
309
|
+
npx tsx profile-server.ts --prod --logs
|
|
310
|
+
|
|
311
|
+
# Profile local environment
|
|
312
|
+
npx tsx profile-server.ts --local
|
|
313
|
+
|
|
314
|
+
Requirements:
|
|
315
|
+
- Azure CLI (az) installed and authenticated
|
|
316
|
+
- Proper permissions to access App Service resources
|
|
317
|
+
- For Kudu API access: Contributor role on the App Service
|
|
318
|
+
|
|
319
|
+
Configuration:
|
|
320
|
+
Reads Azure settings from .fraim/config.json or environment variables:
|
|
321
|
+
- FRAIM_AZURE_PROD_APP_NAME
|
|
322
|
+
- FRAIM_AZURE_PROD_RESOURCE_GROUP
|
|
323
|
+
- FRAIM_AZURE_PREPROD_APP_NAME
|
|
324
|
+
- FRAIM_AZURE_PREPROD_RESOURCE_GROUP
|
|
325
|
+
`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (args.includes('--prod'))
|
|
329
|
+
profileConfig = CONFIGS.prod;
|
|
330
|
+
else if (args.includes('--preprod'))
|
|
331
|
+
profileConfig = CONFIGS.preprod;
|
|
332
|
+
else if (args.includes('--local'))
|
|
333
|
+
profileConfig = CONFIGS.local;
|
|
334
|
+
console.log(`\n๐ Starting Server Profiling`);
|
|
335
|
+
console.log(`๐ Environment: ${profileConfig.env.toUpperCase()}`);
|
|
336
|
+
console.log(`๐ท๏ธ App: ${profileConfig.appName}`);
|
|
337
|
+
console.log(`๐ Resource Group: ${profileConfig.resourceGroup}`);
|
|
338
|
+
if (profileConfig.env === 'local') {
|
|
339
|
+
await profileLocalEnvironment();
|
|
340
|
+
console.log('\nโ
Local profiling completed');
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
// Check prerequisites for Azure profiling
|
|
344
|
+
console.log('\n๐ Checking prerequisites...');
|
|
345
|
+
if (!checkAzureCLI()) {
|
|
346
|
+
console.error('โ Azure CLI not found. Please install Azure CLI first.');
|
|
347
|
+
console.error(' Download from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli');
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
console.log('โ
Azure CLI found');
|
|
351
|
+
if (!checkAzureAuth()) {
|
|
352
|
+
console.error('โ Not authenticated with Azure. Please run: az login');
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
console.log('โ
Azure authentication verified');
|
|
356
|
+
try {
|
|
357
|
+
// Verify app exists
|
|
358
|
+
console.log(`\n๐ Verifying App Service: ${profileConfig.appName}`);
|
|
359
|
+
(0, child_process_1.execSync)(`az webapp show --name ${profileConfig.appName} --resource-group ${profileConfig.resourceGroup} --query name -o tsv`, { stdio: 'pipe' });
|
|
360
|
+
console.log('โ
App Service found');
|
|
361
|
+
// Run profiling analysis
|
|
362
|
+
await getAppServicePlanMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
363
|
+
await getAppServiceMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
364
|
+
await getProcessInformation(profileConfig.appName);
|
|
365
|
+
await getSystemInformation(profileConfig.appName);
|
|
366
|
+
if (includeLogs) {
|
|
367
|
+
await getApplicationLogs(profileConfig.appName, profileConfig.resourceGroup);
|
|
368
|
+
}
|
|
369
|
+
console.log('\nโ
Server profiling completed successfully');
|
|
370
|
+
console.log('\n๐ก Tips:');
|
|
371
|
+
console.log(' - Use --logs flag to include application logs');
|
|
372
|
+
console.log(' - High CPU usage may indicate performance bottlenecks');
|
|
373
|
+
console.log(' - High memory usage may indicate memory leaks');
|
|
374
|
+
console.log(' - Check process list for unexpected or resource-heavy processes');
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
console.error(`\nโ Profiling failed: ${error.message}`);
|
|
378
|
+
console.error('\nTroubleshooting:');
|
|
379
|
+
console.error(' - Verify app name and resource group are correct');
|
|
380
|
+
console.error(' - Ensure you have proper permissions (Contributor role)');
|
|
381
|
+
console.error(' - Check if the App Service is running');
|
|
382
|
+
console.error(' - Try: az webapp list --query "[].{name:name, resourceGroup:resourceGroup}"');
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
// Run the main function
|
|
387
|
+
main().catch(console.error);
|
|
@@ -124,9 +124,17 @@ try {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
const
|
|
127
|
+
const fraimBin = require('./node_modules/fraim-framework/bin/fraim.js');
|
|
128
128
|
console.log('โ FRAIM CLI loaded successfully');
|
|
129
129
|
console.log('โ No ERR_REQUIRE_ESM error');
|
|
130
|
+
|
|
131
|
+
// Test that we can access the loadCli function
|
|
132
|
+
if (fraimBin && typeof fraimBin.loadCli === 'function') {
|
|
133
|
+
console.log('โ loadCli function available');
|
|
134
|
+
} else {
|
|
135
|
+
console.log('โ ๏ธ loadCli function not found, but require succeeded');
|
|
136
|
+
}
|
|
137
|
+
|
|
130
138
|
process.exit(0);
|
|
131
139
|
} catch (error) {
|
|
132
140
|
if (error.code === 'ERR_REQUIRE_ESM') {
|
|
@@ -279,8 +287,16 @@ async function testFreshInstall() {
|
|
|
279
287
|
console.log(' ๐ Testing if fraim CLI can be required...');
|
|
280
288
|
const testScript = `
|
|
281
289
|
try {
|
|
282
|
-
const
|
|
290
|
+
const fraimBin = require('./node_modules/fraim-framework/bin/fraim.js');
|
|
283
291
|
console.log('โ FRAIM CLI loaded successfully');
|
|
292
|
+
|
|
293
|
+
// Test that we can access the loadCli function
|
|
294
|
+
if (fraimBin && typeof fraimBin.loadCli === 'function') {
|
|
295
|
+
console.log('โ loadCli function available');
|
|
296
|
+
} else {
|
|
297
|
+
console.log('โ ๏ธ loadCli function not found, but require succeeded');
|
|
298
|
+
}
|
|
299
|
+
|
|
284
300
|
process.exit(0);
|
|
285
301
|
} catch (error) {
|
|
286
302
|
if (error.code === 'ERR_REQUIRE_ESM') {
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const test_utils_1 = require("./test-utils");
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = require("fs");
|
|
9
|
+
const path_1 = require("path");
|
|
10
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
11
|
+
async function testNoFraimInternalImports() {
|
|
12
|
+
console.log(' ๐งช Testing registry scripts do not import FRAIM internals...');
|
|
13
|
+
try {
|
|
14
|
+
const scriptsDir = (0, path_1.join)(process.cwd(), 'registry', 'scripts');
|
|
15
|
+
const scriptFiles = ['cleanup-branch.ts', 'generate-engagement-emails.ts', 'newsletter-helpers.ts'];
|
|
16
|
+
for (const scriptFile of scriptFiles) {
|
|
17
|
+
const scriptPath = (0, path_1.join)(scriptsDir, scriptFile);
|
|
18
|
+
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
19
|
+
const content = (0, fs_1.readFileSync)(scriptPath, 'utf-8');
|
|
20
|
+
// Should not import from ../../src/
|
|
21
|
+
(0, node_assert_1.default)(!content.includes('../../src/'), `${scriptFile} should not import from ../../src/`);
|
|
22
|
+
// Should not import fraim-config
|
|
23
|
+
(0, node_assert_1.default)(!content.includes('./fraim-config'), `${scriptFile} should not import fraim-config`);
|
|
24
|
+
// Should have self-contained config loading
|
|
25
|
+
(0, node_assert_1.default)(content.includes('.fraim/config.json') || content.includes('loadClientConfig'), `${scriptFile} should read config directly`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.error(' โ FRAIM internal imports check failed:', e);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function testInlineUtilityFunctions() {
|
|
36
|
+
console.log(' ๐งช Testing scripts have inline utility functions...');
|
|
37
|
+
try {
|
|
38
|
+
const scriptsDir = (0, path_1.join)(process.cwd(), 'registry', 'scripts');
|
|
39
|
+
const scriptPath = (0, path_1.join)(scriptsDir, 'cleanup-branch.ts');
|
|
40
|
+
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
41
|
+
const content = (0, fs_1.readFileSync)(scriptPath, 'utf-8');
|
|
42
|
+
// Should have inline extractIssueNumber function
|
|
43
|
+
(0, node_assert_1.default)(content.includes('function extractIssueNumber'), 'cleanup-branch.ts should have inline extractIssueNumber function');
|
|
44
|
+
// Should have inline getCurrentGitBranch function
|
|
45
|
+
(0, node_assert_1.default)(content.includes('function getCurrentGitBranch'), 'cleanup-branch.ts should have inline getCurrentGitBranch function');
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
console.error(' โ Inline utility functions check failed:', e);
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function testConfigLoadingFromJson() {
|
|
55
|
+
console.log(' ๐งช Testing scripts load config from .fraim/config.json...');
|
|
56
|
+
try {
|
|
57
|
+
const scriptsDir = (0, path_1.join)(process.cwd(), 'registry', 'scripts');
|
|
58
|
+
const scriptPath = (0, path_1.join)(scriptsDir, 'generate-engagement-emails.ts');
|
|
59
|
+
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
60
|
+
const content = (0, fs_1.readFileSync)(scriptPath, 'utf-8');
|
|
61
|
+
// Should have loadClientConfig function
|
|
62
|
+
(0, node_assert_1.default)(content.includes('function loadClientConfig'), 'generate-engagement-emails.ts should have loadClientConfig function');
|
|
63
|
+
// Should read .fraim/config.json
|
|
64
|
+
(0, node_assert_1.default)(content.includes('.fraim/config.json'), 'generate-engagement-emails.ts should read .fraim/config.json');
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
console.error(' โ Config loading check failed:', e);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function testRegistryPathValidator() {
|
|
74
|
+
console.log(' ๐งช Testing registry path validator passes...');
|
|
75
|
+
try {
|
|
76
|
+
(0, child_process_1.execSync)('npm run validate:registry', { stdio: 'pipe' });
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(' โ Registry validation failed:', error.message);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function testScriptExecutability() {
|
|
85
|
+
console.log(' ๐งช Testing scripts are executable without import errors...');
|
|
86
|
+
try {
|
|
87
|
+
const scriptsDir = (0, path_1.join)(process.cwd(), 'registry', 'scripts');
|
|
88
|
+
const scriptPath = (0, path_1.join)(scriptsDir, 'cleanup-branch.ts');
|
|
89
|
+
if ((0, fs_1.existsSync)(scriptPath)) {
|
|
90
|
+
// Test that the script can be parsed without import errors
|
|
91
|
+
const result = (0, child_process_1.execSync)(`npx tsx ${scriptPath} --help`, {
|
|
92
|
+
stdio: 'pipe',
|
|
93
|
+
encoding: 'utf-8'
|
|
94
|
+
});
|
|
95
|
+
// Should show help without errors
|
|
96
|
+
(0, node_assert_1.default)(result.includes('Branch Cleanup Script'), 'cleanup-branch.ts should show help without import errors');
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
// Check if it's an import error
|
|
102
|
+
if (error.message.includes('Cannot find module') || error.message.includes('../../src/')) {
|
|
103
|
+
console.error(' โ Script has import errors:', error.message);
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
// Other errors (like missing git repo) are acceptable for this test
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const testCases = [
|
|
111
|
+
{
|
|
112
|
+
name: 'Registry scripts should not import FRAIM internals',
|
|
113
|
+
testFunction: testNoFraimInternalImports
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: 'Scripts should have inline utility functions',
|
|
117
|
+
testFunction: testInlineUtilityFunctions
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'Scripts should load config from .fraim/config.json',
|
|
121
|
+
testFunction: testConfigLoadingFromJson
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
name: 'Registry path validator should pass',
|
|
125
|
+
testFunction: testRegistryPathValidator
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'Scripts should be executable without import errors',
|
|
129
|
+
testFunction: testScriptExecutability
|
|
130
|
+
}
|
|
131
|
+
];
|
|
132
|
+
// Run the tests using the standard test runner
|
|
133
|
+
(0, test_utils_1.runTests)(testCases, async (testCase) => testCase.testFunction(), 'Client-Side Script Validation');
|
|
@@ -57,7 +57,7 @@ async function verifyPrepIssueConfigParsing() {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
const output = runParsingLogic(nodeScript, validConfig);
|
|
60
|
-
assert_1.default.strictEqual(output, 'test-owner
|
|
60
|
+
assert_1.default.strictEqual(output, 'test-owner:test-repo:https://github.com/test-owner/test-repo.git');
|
|
61
61
|
console.log(' โ
Valid config parsed correctly');
|
|
62
62
|
// 4. Test Missing Config
|
|
63
63
|
assert_1.default.throws(() => {
|
|
@@ -69,39 +69,6 @@ async function verifyPrepIssueConfigParsing() {
|
|
|
69
69
|
runParsingLogic(nodeScript, { repository: { owner: 'test-owner' } });
|
|
70
70
|
}, /Script failed/);
|
|
71
71
|
console.log(' โ
Incomplete config correctly failed');
|
|
72
|
-
// 6. Test Default Branch Extraction (git schema)
|
|
73
|
-
const gitConfig = {
|
|
74
|
-
git: {
|
|
75
|
-
repoOwner: 'test-owner',
|
|
76
|
-
repoName: 'test-repo',
|
|
77
|
-
defaultBranch: 'master'
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
const gitOutput = runParsingLogic(nodeScript, gitConfig);
|
|
81
|
-
assert_1.default.strictEqual(gitOutput, 'test-owner|test-repo|https://github.com/test-owner/test-repo.git|master');
|
|
82
|
-
console.log(' โ
Git config with defaultBranch parsed correctly');
|
|
83
|
-
// 7. Test Default Branch Extraction (repository schema)
|
|
84
|
-
const repoConfig = {
|
|
85
|
-
repository: {
|
|
86
|
-
owner: 'test-owner',
|
|
87
|
-
name: 'test-repo',
|
|
88
|
-
url: 'https://github.com/test-owner/test-repo.git',
|
|
89
|
-
defaultBranch: 'main'
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
const repoOutput = runParsingLogic(nodeScript, repoConfig);
|
|
93
|
-
assert_1.default.strictEqual(repoOutput, 'test-owner|test-repo|https://github.com/test-owner/test-repo.git|main');
|
|
94
|
-
console.log(' โ
Repository config with defaultBranch parsed correctly');
|
|
95
|
-
// 8. Test Default Branch Fallback
|
|
96
|
-
const noDefaultConfig = {
|
|
97
|
-
git: {
|
|
98
|
-
repoOwner: 'test-owner',
|
|
99
|
-
repoName: 'test-repo'
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
const fallbackOutput = runParsingLogic(nodeScript, noDefaultConfig);
|
|
103
|
-
assert_1.default.strictEqual(fallbackOutput, 'test-owner|test-repo|https://github.com/test-owner/test-repo.git|master');
|
|
104
|
-
console.log(' โ
Default branch fallback to master works correctly');
|
|
105
72
|
return true;
|
|
106
73
|
}
|
|
107
74
|
catch (error) {
|