fraim-framework 2.0.55 ā 2.0.57
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/CHANGELOG.md +10 -0
- package/dist/src/cli/commands/init-project.js +10 -4
- package/dist/src/cli/setup/mcp-config-generator.js +23 -15
- package/dist/src/local-mcp-server/stdio-server.js +207 -0
- package/dist/src/utils/validate-workflows.js +101 -0
- package/dist/src/utils/workflow-parser.js +81 -0
- package/package.json +16 -11
- package/registry/scripts/pdf-styles.css +172 -0
- package/registry/scripts/prep-issue.sh +46 -4
- package/registry/scripts/profile-server.ts +131 -130
- package/registry/stubs/workflows/customer-development/user-survey-dispatch.md +1 -1
- package/registry/stubs/workflows/customer-development/users-to-target.md +1 -1
- package/registry/stubs/workflows/product-building/design.md +1 -1
- package/registry/stubs/workflows/product-building/implement.md +1 -1
- package/Claude.md +0 -1
- package/dist/registry/ai-manager-rules/design-phases/design-completeness-review.md +0 -73
- package/dist/registry/ai-manager-rules/design-phases/design-design.md +0 -145
- package/dist/registry/ai-manager-rules/implement-phases/implement-code.md +0 -283
- package/dist/registry/ai-manager-rules/implement-phases/implement-completeness-review.md +0 -120
- package/dist/registry/ai-manager-rules/implement-phases/implement-regression.md +0 -173
- package/dist/registry/ai-manager-rules/implement-phases/implement-repro.md +0 -104
- package/dist/registry/ai-manager-rules/implement-phases/implement-scoping.md +0 -100
- package/dist/registry/ai-manager-rules/implement-phases/implement-smoke.md +0 -237
- package/dist/registry/ai-manager-rules/implement-phases/implement-spike.md +0 -121
- package/dist/registry/ai-manager-rules/implement-phases/implement-validate.md +0 -375
- package/dist/registry/ai-manager-rules/retrospective.md +0 -116
- package/dist/registry/ai-manager-rules/shared-phases/address-pr-feedback.md +0 -188
- package/dist/registry/ai-manager-rules/shared-phases/submit-pr.md +0 -202
- package/dist/registry/ai-manager-rules/shared-phases/wait-for-pr-review.md +0 -170
- package/dist/registry/ai-manager-rules/spec-phases/spec-competitor-analysis.md +0 -105
- package/dist/registry/ai-manager-rules/spec-phases/spec-completeness-review.md +0 -66
- package/dist/registry/ai-manager-rules/spec-phases/spec-spec.md +0 -139
- package/dist/registry/providers/ado.json +0 -19
- package/dist/registry/providers/github.json +0 -19
- package/dist/registry/scripts/cleanup-branch.js +0 -287
- package/dist/registry/scripts/evaluate-code-quality.js +0 -66
- package/dist/registry/scripts/exec-with-timeout.js +0 -142
- package/dist/registry/scripts/generate-engagement-emails.js +0 -705
- package/dist/registry/scripts/newsletter-helpers.js +0 -671
- package/dist/registry/scripts/profile-server.js +0 -388
- package/dist/registry/scripts/run-thank-you-workflow.js +0 -92
- package/dist/registry/scripts/send-newsletter-simple.js +0 -85
- package/dist/registry/scripts/send-thank-you-emails.js +0 -54
- package/dist/registry/scripts/validate-openapi-limits.js +0 -311
- package/dist/registry/scripts/validate-test-coverage.js +0 -262
- package/dist/registry/scripts/verify-test-coverage.js +0 -66
- package/dist/scripts/build-stub-registry.js +0 -108
- package/dist/src/ai-manager/ai-manager.js +0 -482
- package/dist/src/ai-manager/phase-flow.js +0 -357
- package/dist/src/ai-manager/types.js +0 -5
- package/dist/src/fraim-mcp-server.js +0 -1885
- package/dist/tests/debug-tools.js +0 -80
- package/dist/tests/shared-server-utils.js +0 -57
- package/dist/tests/test-add-ide.js +0 -283
- package/dist/tests/test-ai-coach-edge-cases.js +0 -420
- package/dist/tests/test-ai-coach-mcp-integration.js +0 -450
- package/dist/tests/test-ai-coach-performance.js +0 -328
- package/dist/tests/test-ai-coach-phase-content.js +0 -264
- package/dist/tests/test-ai-coach-workflows.js +0 -514
- package/dist/tests/test-cli.js +0 -228
- package/dist/tests/test-client-scripts-validation.js +0 -167
- package/dist/tests/test-complete-setup-flow.js +0 -110
- package/dist/tests/test-config-system.js +0 -279
- package/dist/tests/test-debug-session.js +0 -134
- package/dist/tests/test-end-to-end-hybrid-validation.js +0 -328
- package/dist/tests/test-enhanced-session-init.js +0 -188
- package/dist/tests/test-first-run-journey.js +0 -368
- package/dist/tests/test-fraim-issues.js +0 -59
- package/dist/tests/test-genericization.js +0 -44
- package/dist/tests/test-hybrid-script-execution.js +0 -340
- package/dist/tests/test-ide-detector.js +0 -46
- package/dist/tests/test-improved-setup.js +0 -121
- package/dist/tests/test-mcp-config-generator.js +0 -99
- package/dist/tests/test-mcp-connection.js +0 -107
- package/dist/tests/test-mcp-issue-integration.js +0 -156
- package/dist/tests/test-mcp-lifecycle-methods.js +0 -240
- package/dist/tests/test-mcp-shared-server.js +0 -308
- package/dist/tests/test-mcp-template-processing.js +0 -160
- package/dist/tests/test-modular-issue-tracking.js +0 -165
- package/dist/tests/test-node-compatibility.js +0 -95
- package/dist/tests/test-npm-install.js +0 -68
- package/dist/tests/test-package-size.js +0 -108
- package/dist/tests/test-pr-review-workflow.js +0 -307
- package/dist/tests/test-prep-issue.js +0 -129
- package/dist/tests/test-productivity-integration.js +0 -157
- package/dist/tests/test-script-location-independence.js +0 -198
- package/dist/tests/test-script-sync.js +0 -557
- package/dist/tests/test-server-utils.js +0 -32
- package/dist/tests/test-session-rehydration.js +0 -148
- package/dist/tests/test-setup-integration.js +0 -98
- package/dist/tests/test-setup-scenarios.js +0 -322
- package/dist/tests/test-standalone.js +0 -143
- package/dist/tests/test-stub-registry.js +0 -136
- package/dist/tests/test-sync-stubs.js +0 -143
- package/dist/tests/test-sync-version-update.js +0 -93
- package/dist/tests/test-telemetry.js +0 -193
- package/dist/tests/test-token-validator.js +0 -30
- package/dist/tests/test-user-journey.js +0 -236
- package/dist/tests/test-users-to-target-workflow.js +0 -253
- package/dist/tests/test-utils.js +0 -109
- package/dist/tests/test-wizard.js +0 -71
- package/dist/tests/test-workflow-discovery.js +0 -242
- package/labels.json +0 -52
- package/registry/agent-guardrails.md +0 -63
- package/registry/fraim.md +0 -48
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-customer-profiling.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase1-survey-scoping.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-platform-discovery.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase2-survey-build-linkedin.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-prospect-qualification.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase3-survey-build-reddit.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-inventory-compilation.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase4-survey-build-x.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase5-survey-build-facebook.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase6-survey-build-custom.md +0 -11
- package/registry/stubs/workflows/customer-development/ai-coach-phases/phase7-survey-dispatch.md +0 -11
- package/registry/stubs/workflows/customer-development/templates/customer-persona-template.md +0 -11
- package/registry/stubs/workflows/customer-development/templates/search-strategy-template.md +0 -11
- package/setup.js +0 -171
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/* Professional PDF Styling for Business Documents */
|
|
2
|
+
|
|
3
|
+
body {
|
|
4
|
+
font-family: 'Times New Roman', 'Georgia', serif;
|
|
5
|
+
font-size: 11pt;
|
|
6
|
+
line-height: 1.4;
|
|
7
|
+
color: #333;
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Headers */
|
|
13
|
+
h1 {
|
|
14
|
+
font-size: 18pt;
|
|
15
|
+
font-weight: bold;
|
|
16
|
+
color: #2c3e50;
|
|
17
|
+
margin: 20pt 0 12pt 0;
|
|
18
|
+
border-bottom: 2px solid #3498db;
|
|
19
|
+
padding-bottom: 6pt;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
h2 {
|
|
23
|
+
font-size: 14pt;
|
|
24
|
+
font-weight: bold;
|
|
25
|
+
color: #34495e;
|
|
26
|
+
margin: 16pt 0 8pt 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
h3 {
|
|
30
|
+
font-size: 12pt;
|
|
31
|
+
font-weight: bold;
|
|
32
|
+
color: #34495e;
|
|
33
|
+
margin: 12pt 0 6pt 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Paragraphs */
|
|
37
|
+
p {
|
|
38
|
+
margin: 6pt 0;
|
|
39
|
+
text-align: justify;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Lists */
|
|
43
|
+
ul, ol {
|
|
44
|
+
margin: 6pt 0;
|
|
45
|
+
padding-left: 20pt;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
li {
|
|
49
|
+
margin: 3pt 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Tables */
|
|
53
|
+
table {
|
|
54
|
+
border-collapse: collapse;
|
|
55
|
+
width: 100%;
|
|
56
|
+
margin: 12pt 0;
|
|
57
|
+
font-size: 10pt;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
th, td {
|
|
61
|
+
border: 1px solid #ddd;
|
|
62
|
+
padding: 6pt 8pt;
|
|
63
|
+
text-align: left;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
th {
|
|
67
|
+
background-color: #f8f9fa;
|
|
68
|
+
font-weight: bold;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Code blocks */
|
|
72
|
+
code {
|
|
73
|
+
font-family: 'Courier New', monospace;
|
|
74
|
+
font-size: 9pt;
|
|
75
|
+
background-color: #f8f9fa;
|
|
76
|
+
padding: 2pt 4pt;
|
|
77
|
+
border: 1px solid #e9ecef;
|
|
78
|
+
border-radius: 2pt;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pre {
|
|
82
|
+
font-family: 'Courier New', monospace;
|
|
83
|
+
font-size: 9pt;
|
|
84
|
+
background-color: #f8f9fa;
|
|
85
|
+
padding: 8pt;
|
|
86
|
+
border: 1px solid #e9ecef;
|
|
87
|
+
border-radius: 4pt;
|
|
88
|
+
overflow-x: auto;
|
|
89
|
+
margin: 8pt 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* Blockquotes */
|
|
93
|
+
blockquote {
|
|
94
|
+
margin: 8pt 0;
|
|
95
|
+
padding: 8pt 12pt;
|
|
96
|
+
border-left: 4pt solid #3498db;
|
|
97
|
+
background-color: #f8f9fa;
|
|
98
|
+
font-style: italic;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Horizontal rules */
|
|
102
|
+
hr {
|
|
103
|
+
border: none;
|
|
104
|
+
border-top: 1px solid #bdc3c7;
|
|
105
|
+
margin: 16pt 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Strong and emphasis */
|
|
109
|
+
strong, b {
|
|
110
|
+
font-weight: bold;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
em, i {
|
|
114
|
+
font-style: italic;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Page settings */
|
|
118
|
+
@page {
|
|
119
|
+
size: A4;
|
|
120
|
+
margin: 1in;
|
|
121
|
+
|
|
122
|
+
@top-center {
|
|
123
|
+
content: "";
|
|
124
|
+
font-size: 9pt;
|
|
125
|
+
color: #7f8c8d;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@bottom-center {
|
|
129
|
+
content: "Page " counter(page) " of " counter(pages);
|
|
130
|
+
font-size: 9pt;
|
|
131
|
+
color: #7f8c8d;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Print-specific adjustments */
|
|
136
|
+
@media print {
|
|
137
|
+
body {
|
|
138
|
+
font-size: 10pt;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
h1 {
|
|
142
|
+
font-size: 16pt;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
h2 {
|
|
146
|
+
font-size: 13pt;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
h3 {
|
|
150
|
+
font-size: 11pt;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/* Signature sections */
|
|
155
|
+
.signature-section {
|
|
156
|
+
margin-top: 24pt;
|
|
157
|
+
page-break-inside: avoid;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Contact information */
|
|
161
|
+
.contact-info {
|
|
162
|
+
font-size: 10pt;
|
|
163
|
+
margin-top: 16pt;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Legal text formatting */
|
|
167
|
+
.legal-text {
|
|
168
|
+
font-size: 10pt;
|
|
169
|
+
text-transform: uppercase;
|
|
170
|
+
font-weight: bold;
|
|
171
|
+
margin: 12pt 0;
|
|
172
|
+
}
|
|
@@ -153,21 +153,63 @@ try {
|
|
|
153
153
|
} else {
|
|
154
154
|
// Extract defaultBranch from repository config
|
|
155
155
|
defaultBranch = repo.defaultBranch || 'master';
|
|
156
|
+
// Ensure URL is present - construct it if missing
|
|
157
|
+
if (!repo.url && repo.owner && repo.name) {
|
|
158
|
+
repo.url = \`https://github.com/\${repo.owner}/\${repo.name}.git\`;
|
|
159
|
+
}
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
if (!repo || !repo.owner || !repo.name
|
|
162
|
+
if (!repo || !repo.owner || !repo.name) {
|
|
163
|
+
console.error('ā Config validation failed: Missing required repository fields');
|
|
164
|
+
console.error(' Required: repository.owner, repository.name');
|
|
165
|
+
console.error(' Found in config:', JSON.stringify(repo, null, 2));
|
|
159
166
|
process.exit(1);
|
|
160
167
|
}
|
|
168
|
+
|
|
169
|
+
if (!repo.url) {
|
|
170
|
+
console.error('ā Config validation failed: Missing repository.url field');
|
|
171
|
+
console.error(' This field is required for cloning the repository');
|
|
172
|
+
console.error(' Expected format: https://github.com/owner/repo.git');
|
|
173
|
+
console.error(' Current config:', JSON.stringify(repo, null, 2));
|
|
174
|
+
console.error('');
|
|
175
|
+
console.error('š” To fix this:');
|
|
176
|
+
console.error(' 1. Add \"url\": \"https://github.com/\${repo.owner}/\${repo.name}.git\" to your .fraim/config.json');
|
|
177
|
+
console.error(' 2. Or run: fraim init-project (if using FRAIM CLI)');
|
|
178
|
+
process.exit(1);
|
|
179
|
+
}
|
|
180
|
+
|
|
161
181
|
console.log(\`\${repo.owner}|\${repo.name}|\${repo.url}|\${defaultBranch}\`);
|
|
162
182
|
} catch (e) {
|
|
183
|
+
console.error('ā Failed to parse .fraim/config.json');
|
|
184
|
+
console.error(' Error:', e.message);
|
|
185
|
+
console.error('');
|
|
186
|
+
console.error('š” Common issues:');
|
|
187
|
+
console.error(' ⢠Invalid JSON syntax');
|
|
188
|
+
console.error(' ⢠Missing required fields: repository.owner, repository.name, repository.url');
|
|
189
|
+
console.error(' ⢠File encoding issues');
|
|
190
|
+
console.error('');
|
|
191
|
+
console.error('š Expected config format:');
|
|
192
|
+
console.error('{');
|
|
193
|
+
console.error(' \"repository\": {');
|
|
194
|
+
console.error(' \"provider\": \"github\",');
|
|
195
|
+
console.error(' \"owner\": \"your-username\",');
|
|
196
|
+
console.error(' \"name\": \"your-repo\",');
|
|
197
|
+
console.error(' \"url\": \"https://github.com/your-username/your-repo.git\",');
|
|
198
|
+
console.error(' \"defaultBranch\": \"main\"');
|
|
199
|
+
console.error(' }');
|
|
200
|
+
console.error('}');
|
|
163
201
|
process.exit(1);
|
|
164
202
|
}
|
|
165
203
|
"
|
|
166
204
|
|
|
167
|
-
REPO_INFO=$(cat "$CONFIG_FILE" | node -e "$NODE_SCRIPT")
|
|
205
|
+
REPO_INFO=$(cat "$CONFIG_FILE" | node -e "$NODE_SCRIPT" 2>&1)
|
|
168
206
|
if [ $? -ne 0 ]; then
|
|
169
|
-
echo "
|
|
170
|
-
echo "
|
|
207
|
+
echo "$REPO_INFO" >&2
|
|
208
|
+
echo "" >&2
|
|
209
|
+
echo "š§ To fix this issue:" >&2
|
|
210
|
+
echo " 1. Check your .fraim/config.json file" >&2
|
|
211
|
+
echo " 2. Ensure it has the required repository fields" >&2
|
|
212
|
+
echo " 3. Run 'fraim init-project' to regenerate the config" >&2
|
|
171
213
|
exit 1
|
|
172
214
|
fi
|
|
173
215
|
|
|
@@ -45,9 +45,9 @@ function getEnvOr(keys: string[], fallback: string): string {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
interface ProfilingConfig {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
env: 'prod' | 'preprod' | 'local';
|
|
49
|
+
appName: string;
|
|
50
|
+
resourceGroup: string;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function checkAzureCLI(): boolean {
|
|
@@ -71,7 +71,7 @@ function checkAzureAuth(): boolean {
|
|
|
71
71
|
async function getAppServicePlanMetrics(appName: string, resourceGroup: string): Promise<void> {
|
|
72
72
|
try {
|
|
73
73
|
console.log('\n--- āļø App Service Plan Metrics ---');
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
// Get App Service Plan ID
|
|
76
76
|
const planIdCmd = `az webapp show --name ${appName} --resource-group ${resourceGroup} --query "appServicePlanId" -o tsv`;
|
|
77
77
|
const planId = execSync(planIdCmd, { encoding: 'utf8' }).trim();
|
|
@@ -81,10 +81,10 @@ async function getAppServicePlanMetrics(appName: string, resourceGroup: string):
|
|
|
81
81
|
// Get CPU and Memory metrics for last 5 minutes
|
|
82
82
|
console.log('š Fetching CPU and Memory metrics (last 5 minutes)...');
|
|
83
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
|
-
|
|
84
|
+
|
|
85
85
|
const metricsOutput = execSync(metricsCmd, { encoding: 'utf8' });
|
|
86
86
|
const metrics = JSON.parse(metricsOutput);
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
for (const metric of metrics) {
|
|
89
89
|
const latestData = metric.data[metric.data.length - 1];
|
|
90
90
|
const value = latestData?.average || latestData?.total || 'N/A';
|
|
@@ -98,24 +98,25 @@ async function getAppServicePlanMetrics(appName: string, resourceGroup: string):
|
|
|
98
98
|
async function getAppServiceMetrics(appName: string, resourceGroup: string): Promise<void> {
|
|
99
99
|
try {
|
|
100
100
|
console.log('\n--- š„ļø App Service Metrics ---');
|
|
101
|
-
|
|
101
|
+
|
|
102
102
|
// Get subscription ID
|
|
103
103
|
const subscriptionId = execSync('az account show --query id -o tsv', { encoding: 'utf8' }).trim();
|
|
104
|
-
|
|
104
|
+
|
|
105
105
|
// Get app-level metrics using Azure CLI
|
|
106
106
|
console.log('š Fetching App Service metrics (last 5 minutes)...');
|
|
107
|
-
const
|
|
108
|
-
|
|
107
|
+
const root = '/';
|
|
108
|
+
const appResourceId = `${root}subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Web/sites/${appName}`;
|
|
109
|
+
|
|
109
110
|
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`;
|
|
110
|
-
|
|
111
|
+
|
|
111
112
|
const appMetricsOutput = execSync(appMetricsCmd, { encoding: 'utf8' });
|
|
112
113
|
const appMetrics = JSON.parse(appMetricsOutput);
|
|
113
|
-
|
|
114
|
+
|
|
114
115
|
for (const metric of appMetrics) {
|
|
115
116
|
const latestData = metric.data[metric.data.length - 1];
|
|
116
117
|
const value = latestData?.total || latestData?.average || 'N/A';
|
|
117
118
|
let displayValue = value;
|
|
118
|
-
|
|
119
|
+
|
|
119
120
|
if (typeof value === 'number') {
|
|
120
121
|
if (metric.name === 'CpuTime') {
|
|
121
122
|
displayValue = `${value.toFixed(2)}s`;
|
|
@@ -123,7 +124,7 @@ async function getAppServiceMetrics(appName: string, resourceGroup: string): Pro
|
|
|
123
124
|
displayValue = value.toString();
|
|
124
125
|
}
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
+
|
|
127
128
|
console.log(` ${metric.name}: ${displayValue}`);
|
|
128
129
|
}
|
|
129
130
|
} catch (error: any) {
|
|
@@ -134,41 +135,41 @@ async function getAppServiceMetrics(appName: string, resourceGroup: string): Pro
|
|
|
134
135
|
async function getProcessInformation(appName: string): Promise<void> {
|
|
135
136
|
try {
|
|
136
137
|
console.log('\n--- āļø Process Information (Kudu API) ---');
|
|
137
|
-
|
|
138
|
+
|
|
138
139
|
// Get access token for Kudu API
|
|
139
140
|
const token = execSync('az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv', { encoding: 'utf8' }).trim();
|
|
140
|
-
|
|
141
|
+
|
|
141
142
|
// Import axios dynamically
|
|
142
143
|
const axios = (await import('axios')).default;
|
|
143
|
-
|
|
144
|
+
|
|
144
145
|
// Get process list from Kudu
|
|
145
146
|
console.log('š Fetching running processes...');
|
|
146
147
|
const processResponse = await axios.get(`https://${appName}.scm.azurewebsites.net/api/processes`, {
|
|
147
148
|
headers: { Authorization: `Bearer ${token}` },
|
|
148
149
|
timeout: 10000
|
|
149
150
|
});
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
const processes = processResponse.data;
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
// Sort by CPU usage and show top processes
|
|
154
155
|
const sortedProcesses = processes
|
|
155
156
|
.filter((p: any) => p.cpu_usage !== undefined)
|
|
156
157
|
.sort((a: any, b: any) => (b.cpu_usage || 0) - (a.cpu_usage || 0))
|
|
157
158
|
.slice(0, 10);
|
|
158
|
-
|
|
159
|
+
|
|
159
160
|
console.log('\nš„ Top 10 Processes by CPU Usage:');
|
|
160
161
|
console.log('PID\tCPU%\tMemory(MB)\tName');
|
|
161
162
|
console.log('---\t----\t---------\t----');
|
|
162
|
-
|
|
163
|
+
|
|
163
164
|
for (const process of sortedProcesses) {
|
|
164
165
|
const pid = process.id || 'N/A';
|
|
165
166
|
const cpu = process.cpu_usage ? process.cpu_usage.toFixed(2) : '0.00';
|
|
166
167
|
const memory = process.working_set ? (process.working_set / 1024 / 1024).toFixed(1) : 'N/A';
|
|
167
168
|
const name = process.name || 'Unknown';
|
|
168
|
-
|
|
169
|
+
|
|
169
170
|
console.log(`${pid}\t${cpu}\t${memory}\t\t${name.substring(0, 30)}`);
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
} catch (error: any) {
|
|
173
174
|
console.log(`ā ļø Could not fetch process information: ${error.message.split('\n')[0]}`);
|
|
174
175
|
console.log(' This might be due to authentication issues or Kudu API being unavailable.');
|
|
@@ -178,39 +179,39 @@ async function getProcessInformation(appName: string): Promise<void> {
|
|
|
178
179
|
async function getSystemInformation(appName: string): Promise<void> {
|
|
179
180
|
try {
|
|
180
181
|
console.log('\n--- š„ļø System Information ---');
|
|
181
|
-
|
|
182
|
+
|
|
182
183
|
const token = execSync('az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv', { encoding: 'utf8' }).trim();
|
|
183
184
|
const axios = (await import('axios')).default;
|
|
184
|
-
|
|
185
|
+
|
|
185
186
|
// Get system info using Kudu command API
|
|
186
187
|
console.log('š Fetching system information...');
|
|
187
|
-
|
|
188
|
+
|
|
188
189
|
const commands = [
|
|
189
190
|
{ name: 'Memory Info', cmd: 'cat /proc/meminfo | head -10' },
|
|
190
191
|
{ name: 'CPU Info', cmd: 'cat /proc/cpuinfo | grep "model name" | head -1' },
|
|
191
192
|
{ name: 'Disk Usage', cmd: 'df -h | head -5' },
|
|
192
193
|
{ name: 'Load Average', cmd: 'uptime' }
|
|
193
194
|
];
|
|
194
|
-
|
|
195
|
+
|
|
195
196
|
for (const command of commands) {
|
|
196
197
|
try {
|
|
197
198
|
const response = await axios.post(`https://${appName}.scm.azurewebsites.net/api/command`, {
|
|
198
199
|
command: command.cmd,
|
|
199
|
-
dir: path.join(
|
|
200
|
+
dir: path.join(path.sep, 'home')
|
|
200
201
|
}, {
|
|
201
202
|
headers: { Authorization: `Bearer ${token}` },
|
|
202
203
|
timeout: 5000
|
|
203
204
|
});
|
|
204
|
-
|
|
205
|
+
|
|
205
206
|
console.log(`\n${command.name}:`);
|
|
206
207
|
const output = response.data.Output || response.data.Error || 'No output';
|
|
207
208
|
console.log(output.split('\n').map((line: string) => ` ${line}`).join('\n'));
|
|
208
|
-
|
|
209
|
+
|
|
209
210
|
} catch (cmdError: any) {
|
|
210
211
|
console.log(`\n${command.name}: Could not retrieve (${cmdError.message.split('\n')[0]})`);
|
|
211
212
|
}
|
|
212
213
|
}
|
|
213
|
-
|
|
214
|
+
|
|
214
215
|
} catch (error: any) {
|
|
215
216
|
console.log(`ā ļø Could not fetch system information: ${error.message.split('\n')[0]}`);
|
|
216
217
|
}
|
|
@@ -219,11 +220,11 @@ async function getSystemInformation(appName: string): Promise<void> {
|
|
|
219
220
|
async function getApplicationLogs(appName: string, resourceGroup: string): Promise<void> {
|
|
220
221
|
try {
|
|
221
222
|
console.log('\n--- š Recent Application Logs ---');
|
|
222
|
-
|
|
223
|
+
|
|
223
224
|
// Get recent logs using Azure CLI
|
|
224
225
|
console.log('š Fetching recent application logs...');
|
|
225
226
|
const logsCmd = `az webapp log tail --name ${appName} --resource-group ${resourceGroup} --provider application --timeout 5`;
|
|
226
|
-
|
|
227
|
+
|
|
227
228
|
try {
|
|
228
229
|
const logs = execSync(logsCmd, { encoding: 'utf8', timeout: 10000 });
|
|
229
230
|
console.log('Recent logs:');
|
|
@@ -232,7 +233,7 @@ async function getApplicationLogs(appName: string, resourceGroup: string): Promi
|
|
|
232
233
|
console.log('ā ļø Could not fetch logs - they may not be enabled or available');
|
|
233
234
|
console.log(' Enable application logging in Azure portal for better diagnostics');
|
|
234
235
|
}
|
|
235
|
-
|
|
236
|
+
|
|
236
237
|
} catch (error: any) {
|
|
237
238
|
console.log(`ā ļø Could not fetch application logs: ${error.message.split('\n')[0]}`);
|
|
238
239
|
}
|
|
@@ -240,11 +241,11 @@ async function getApplicationLogs(appName: string, resourceGroup: string): Promi
|
|
|
240
241
|
|
|
241
242
|
async function profileLocalEnvironment(): Promise<void> {
|
|
242
243
|
console.log('\n--- š Local Environment Profiling ---');
|
|
243
|
-
|
|
244
|
+
|
|
244
245
|
try {
|
|
245
246
|
// Basic system information
|
|
246
247
|
console.log('š» System Information:');
|
|
247
|
-
|
|
248
|
+
|
|
248
249
|
if (process.platform === 'win32') {
|
|
249
250
|
try {
|
|
250
251
|
const systemInfo = execSync('systeminfo | findstr /C:"Total Physical Memory" /C:"Available Physical Memory" /C:"Processor"', { encoding: 'utf8' });
|
|
@@ -264,7 +265,7 @@ async function profileLocalEnvironment(): Promise<void> {
|
|
|
264
265
|
console.log(' Could not retrieve Linux system info');
|
|
265
266
|
}
|
|
266
267
|
}
|
|
267
|
-
|
|
268
|
+
|
|
268
269
|
// Node.js process information
|
|
269
270
|
console.log('\nš¢ Node.js Process Information:');
|
|
270
271
|
console.log(` Node Version: ${process.version}`);
|
|
@@ -276,49 +277,49 @@ async function profileLocalEnvironment(): Promise<void> {
|
|
|
276
277
|
console.log(` Heap Used: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)} MB`);
|
|
277
278
|
console.log(` Heap Total: ${(memUsage.heapTotal / 1024 / 1024).toFixed(2)} MB`);
|
|
278
279
|
console.log(` External: ${(memUsage.external / 1024 / 1024).toFixed(2)} MB`);
|
|
279
|
-
|
|
280
|
+
|
|
280
281
|
} catch (error: any) {
|
|
281
282
|
console.log(`ā ļø Error profiling local environment: ${error.message}`);
|
|
282
283
|
}
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
async function main() {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
287
|
+
const config = loadClientConfig();
|
|
288
|
+
|
|
289
|
+
// Azure configuration with environment variable fallbacks
|
|
290
|
+
const azureConfig = {
|
|
291
|
+
prodAppName: config.azure?.prodAppName || getEnvOr(['FRAIM_AZURE_PROD_APP_NAME'], 'fraim-app-prod'),
|
|
292
|
+
prodResourceGroup: config.azure?.prodResourceGroup || getEnvOr(['FRAIM_AZURE_PROD_RESOURCE_GROUP'], 'fraim-prod-rg'),
|
|
293
|
+
preprodAppName: config.azure?.preprodAppName || getEnvOr(['FRAIM_AZURE_PREPROD_APP_NAME'], 'fraim-app-pre-prod'),
|
|
294
|
+
preprodResourceGroup: config.azure?.preprodResourceGroup || getEnvOr(['FRAIM_AZURE_PREPROD_RESOURCE_GROUP'], 'fraim-pre-prod-rg'),
|
|
295
|
+
localAppName: config.azure?.localAppName || getEnvOr(['FRAIM_AZURE_LOCAL_APP_NAME'], 'local'),
|
|
296
|
+
localResourceGroup: config.azure?.localResourceGroup || getEnvOr(['FRAIM_AZURE_LOCAL_RESOURCE_GROUP'], 'local')
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const CONFIGS: Record<string, ProfilingConfig> = {
|
|
300
|
+
prod: {
|
|
301
|
+
env: 'prod',
|
|
302
|
+
appName: azureConfig.prodAppName,
|
|
303
|
+
resourceGroup: azureConfig.prodResourceGroup
|
|
304
|
+
},
|
|
305
|
+
preprod: {
|
|
306
|
+
env: 'preprod',
|
|
307
|
+
appName: azureConfig.preprodAppName,
|
|
308
|
+
resourceGroup: azureConfig.preprodResourceGroup
|
|
309
|
+
},
|
|
310
|
+
local: {
|
|
311
|
+
env: 'local',
|
|
312
|
+
appName: azureConfig.localAppName,
|
|
313
|
+
resourceGroup: azureConfig.localResourceGroup
|
|
314
|
+
}
|
|
315
|
+
};
|
|
315
316
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
const args = process.argv.slice(2);
|
|
318
|
+
let profileConfig: ProfilingConfig = CONFIGS.local;
|
|
319
|
+
const includeLogs = args.includes('--logs');
|
|
319
320
|
|
|
320
|
-
|
|
321
|
-
|
|
321
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
322
|
+
console.log(`
|
|
322
323
|
š Azure App Service Profiler
|
|
323
324
|
|
|
324
325
|
Usage:
|
|
@@ -353,72 +354,72 @@ Configuration:
|
|
|
353
354
|
- FRAIM_AZURE_PREPROD_APP_NAME
|
|
354
355
|
- FRAIM_AZURE_PREPROD_RESOURCE_GROUP
|
|
355
356
|
`);
|
|
356
|
-
|
|
357
|
-
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
358
359
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
360
|
+
if (args.includes('--prod')) profileConfig = CONFIGS.prod;
|
|
361
|
+
else if (args.includes('--preprod')) profileConfig = CONFIGS.preprod;
|
|
362
|
+
else if (args.includes('--local')) profileConfig = CONFIGS.local;
|
|
362
363
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
364
|
+
console.log(`\nš Starting Server Profiling`);
|
|
365
|
+
console.log(`š Environment: ${profileConfig.env.toUpperCase()}`);
|
|
366
|
+
console.log(`š·ļø App: ${profileConfig.appName}`);
|
|
367
|
+
console.log(`š Resource Group: ${profileConfig.resourceGroup}`);
|
|
367
368
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
369
|
+
if (profileConfig.env === 'local') {
|
|
370
|
+
await profileLocalEnvironment();
|
|
371
|
+
console.log('\nā
Local profiling completed');
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
373
374
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if (!checkAzureCLI()) {
|
|
378
|
-
console.error('ā Azure CLI not found. Please install Azure CLI first.');
|
|
379
|
-
console.error(' Download from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli');
|
|
380
|
-
process.exit(1);
|
|
381
|
-
}
|
|
382
|
-
console.log('ā
Azure CLI found');
|
|
375
|
+
// Check prerequisites for Azure profiling
|
|
376
|
+
console.log('\nš Checking prerequisites...');
|
|
383
377
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
378
|
+
if (!checkAzureCLI()) {
|
|
379
|
+
console.error('ā Azure CLI not found. Please install Azure CLI first.');
|
|
380
|
+
console.error(' Download from: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli');
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
console.log('ā
Azure CLI found');
|
|
389
384
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
// Run profiling analysis
|
|
397
|
-
await getAppServicePlanMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
398
|
-
await getAppServiceMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
399
|
-
await getProcessInformation(profileConfig.appName);
|
|
400
|
-
await getSystemInformation(profileConfig.appName);
|
|
401
|
-
|
|
402
|
-
if (includeLogs) {
|
|
403
|
-
await getApplicationLogs(profileConfig.appName, profileConfig.resourceGroup);
|
|
404
|
-
}
|
|
385
|
+
if (!checkAzureAuth()) {
|
|
386
|
+
console.error('ā Not authenticated with Azure. Please run: az login');
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
console.log('ā
Azure authentication verified');
|
|
405
390
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
process.exit(1);
|
|
391
|
+
try {
|
|
392
|
+
// Verify app exists
|
|
393
|
+
console.log(`\nš Verifying App Service: ${profileConfig.appName}`);
|
|
394
|
+
execSync(`az webapp show --name ${profileConfig.appName} --resource-group ${profileConfig.resourceGroup} --query name -o tsv`, { stdio: 'pipe' });
|
|
395
|
+
console.log('ā
App Service found');
|
|
396
|
+
|
|
397
|
+
// Run profiling analysis
|
|
398
|
+
await getAppServicePlanMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
399
|
+
await getAppServiceMetrics(profileConfig.appName, profileConfig.resourceGroup);
|
|
400
|
+
await getProcessInformation(profileConfig.appName);
|
|
401
|
+
await getSystemInformation(profileConfig.appName);
|
|
402
|
+
|
|
403
|
+
if (includeLogs) {
|
|
404
|
+
await getApplicationLogs(profileConfig.appName, profileConfig.resourceGroup);
|
|
421
405
|
}
|
|
406
|
+
|
|
407
|
+
console.log('\nā
Server profiling completed successfully');
|
|
408
|
+
console.log('\nš” Tips:');
|
|
409
|
+
console.log(' - Use --logs flag to include application logs');
|
|
410
|
+
console.log(' - High CPU usage may indicate performance bottlenecks');
|
|
411
|
+
console.log(' - High memory usage may indicate memory leaks');
|
|
412
|
+
console.log(' - Check process list for unexpected or resource-heavy processes');
|
|
413
|
+
|
|
414
|
+
} catch (error: any) {
|
|
415
|
+
console.error(`\nā Profiling failed: ${error.message}`);
|
|
416
|
+
console.error('\nTroubleshooting:');
|
|
417
|
+
console.error(' - Verify app name and resource group are correct');
|
|
418
|
+
console.error(' - Ensure you have proper permissions (Contributor role)');
|
|
419
|
+
console.error(' - Check if the App Service is running');
|
|
420
|
+
console.error(' - Try: az webapp list --query "[].{name:name, resourceGroup:resourceGroup}"');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
// Run the main function
|