fraim-framework 2.0.30 → 2.0.34
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 +3 -18
- package/dist/src/cli/commands/init.js +29 -2
- package/dist/src/cli/commands/sync.js +82 -70
- package/dist/src/utils/script-sync-utils.js +216 -0
- package/dist/tests/debug-tools.js +6 -5
- package/dist/tests/test-chalk-regression.js +58 -8
- package/dist/tests/test-cli.js +70 -5
- package/dist/tests/test-end-to-end-hybrid-validation.js +328 -0
- package/dist/tests/test-first-run-journey.js +43 -3
- package/dist/tests/test-hybrid-script-execution.js +340 -0
- package/dist/tests/test-mcp-connection.js +2 -2
- package/dist/tests/test-mcp-issue-integration.js +12 -4
- package/dist/tests/test-mcp-lifecycle-methods.js +4 -4
- package/dist/tests/test-node-compatibility.js +24 -2
- package/dist/tests/test-prep-issue.js +4 -1
- package/dist/tests/test-script-location-independence.js +173 -0
- package/dist/tests/test-script-sync.js +557 -0
- package/dist/tests/test-session-rehydration.js +2 -2
- package/dist/tests/test-standalone.js +3 -3
- package/dist/tests/test-sync-version-update.js +1 -1
- package/dist/tests/test-telemetry.js +2 -2
- package/dist/tests/test-user-journey.js +8 -4
- package/dist/tests/test-utils.js +13 -0
- package/dist/tests/test-wizard.js +2 -2
- package/package.json +3 -3
- package/registry/rules/agent-testing-guidelines.md +502 -502
- package/registry/rules/ephemeral-execution.md +37 -27
- package/registry/rules/local-development.md +253 -251
- package/registry/rules/successful-debugging-patterns.md +491 -482
- package/registry/scripts/prep-issue.sh +468 -468
- package/registry/workflows/bootstrap/evaluate-code-quality.md +8 -2
- package/registry/workflows/bootstrap/verify-test-coverage.md +8 -2
- package/registry/workflows/customer-development/thank-customers.md +203 -193
- package/registry/workflows/customer-development/weekly-newsletter.md +366 -362
- package/registry/workflows/performance/analyze-performance.md +65 -63
- package/registry/workflows/product-building/implement.md +6 -2
- package/registry/workflows/product-building/prep-issue.md +11 -24
- package/registry/workflows/product-building/resolve.md +5 -1
- package/registry/workflows/replicate/replicate-discovery.md +336 -0
- package/registry/workflows/replicate/replicate-to-issues.md +319 -0
- package/registry/workflows/reviewer/review-implementation-vs-design-spec.md +632 -632
- package/.windsurf/rules/windsurf-rules.md +0 -7
- package/.windsurf/workflows/resolve-issue.md +0 -6
- package/.windsurf/workflows/retrospect.md +0 -6
- package/.windsurf/workflows/start-design.md +0 -6
- package/.windsurf/workflows/start-impl.md +0 -6
- package/.windsurf/workflows/start-spec.md +0 -6
- package/.windsurf/workflows/start-tests.md +0 -6
- package/registry/scripts/build-scripts-generator.ts +0 -216
- package/registry/scripts/cleanup-branch.ts +0 -303
- package/registry/scripts/fraim-config.ts +0 -63
- package/registry/scripts/generate-engagement-emails.ts +0 -744
- package/registry/scripts/generic-issues-api.ts +0 -110
- package/registry/scripts/newsletter-helpers.ts +0 -874
- package/registry/scripts/openapi-generator.ts +0 -695
- package/registry/scripts/performance/profile-server.ts +0 -370
- package/registry/scripts/run-thank-you-workflow.ts +0 -122
- package/registry/scripts/send-newsletter-simple.ts +0 -104
- package/registry/scripts/send-thank-you-emails.ts +0 -57
- package/registry/workflows/replicate/re-implementation-strategy.md +0 -226
- package/registry/workflows/replicate/use-case-extraction.md +0 -135
- package/registry/workflows/replicate/visual-analysis.md +0 -154
- package/registry/workflows/replicate/website-discovery-analysis.md +0 -231
- package/sample_package.json +0 -18
- /package/registry/scripts/{replicate/comprehensive-explorer.py → comprehensive-explorer.py} +0 -0
- /package/registry/scripts/{replicate/interactive-explorer.py → interactive-explorer.py} +0 -0
- /package/registry/scripts/{replicate/scrape-site.py → scrape-site.py} +0 -0
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const node_child_process_1 = require("node:child_process");
|
|
40
|
+
const test_utils_1 = require("./test-utils");
|
|
41
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
const os_1 = __importDefault(require("os"));
|
|
45
|
+
async function testScriptSyncDuringInit() {
|
|
46
|
+
console.log(' 🚀 Testing Script Sync During Init...');
|
|
47
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-script-sync-test-'));
|
|
48
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-test');
|
|
49
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
50
|
+
console.log(` 📂 Created temp dir: ${tempDir}`);
|
|
51
|
+
console.log(` 🏠 Using test user dir: ${userFraimDir}`);
|
|
52
|
+
try {
|
|
53
|
+
// Clean up any existing test user directory
|
|
54
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
55
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
const platform = process.platform;
|
|
58
|
+
const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
59
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
60
|
+
// Helper to run CLI commands
|
|
61
|
+
const runFraim = (args, cwdOverride) => {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
|
|
64
|
+
cwd: cwdOverride || tempDir,
|
|
65
|
+
env: {
|
|
66
|
+
...process.env,
|
|
67
|
+
TEST_MODE: 'true',
|
|
68
|
+
FRAIM_USER_DIR: userFraimDir // Override user directory for testing
|
|
69
|
+
},
|
|
70
|
+
shell: true
|
|
71
|
+
});
|
|
72
|
+
let stdout = '';
|
|
73
|
+
let stderr = '';
|
|
74
|
+
ps.stdout.on('data', d => stdout += d.toString());
|
|
75
|
+
ps.stderr.on('data', d => stderr += d.toString());
|
|
76
|
+
ps.on('close', (code) => {
|
|
77
|
+
resolve({ stdout, stderr, code });
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
// Set up git repository
|
|
82
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
83
|
+
execSync('git init', { cwd: tempDir });
|
|
84
|
+
execSync('git remote add origin https://github.com/test-owner/test-project.git', { cwd: tempDir });
|
|
85
|
+
// Run fraim init
|
|
86
|
+
console.log(' Testing fraim init with script sync...');
|
|
87
|
+
const initRes = await runFraim(['init'], tempDir);
|
|
88
|
+
node_assert_1.default.strictEqual(initRes.code, 0, 'Init should succeed');
|
|
89
|
+
// Verify user scripts directory was created
|
|
90
|
+
node_assert_1.default.ok(fs_1.default.existsSync(userScriptsDir), 'User scripts directory should be created');
|
|
91
|
+
// Verify scripts were copied
|
|
92
|
+
const expectedScripts = ['prep-issue.sh', 'code-quality-check.sh'];
|
|
93
|
+
for (const scriptName of expectedScripts) {
|
|
94
|
+
const scriptPath = path_1.default.join(userScriptsDir, scriptName);
|
|
95
|
+
node_assert_1.default.ok(fs_1.default.existsSync(scriptPath), `Script ${scriptName} should be copied to user directory`);
|
|
96
|
+
// Verify script is executable on Unix systems
|
|
97
|
+
if (process.platform !== 'win32') {
|
|
98
|
+
const stats = fs_1.default.statSync(scriptPath);
|
|
99
|
+
node_assert_1.default.ok(stats.mode & 0o111, `Script ${scriptName} should be executable`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Verify script content matches registry
|
|
103
|
+
const prepIssueUserPath = path_1.default.join(userScriptsDir, 'prep-issue.sh');
|
|
104
|
+
// Handle both source and compiled execution
|
|
105
|
+
const prepIssueRegistryPath = fs_1.default.existsSync(path_1.default.join(__dirname, '../registry/scripts/prep-issue.sh'))
|
|
106
|
+
? path_1.default.join(__dirname, '../registry/scripts/prep-issue.sh')
|
|
107
|
+
: path_1.default.resolve(__dirname, '../../registry/scripts/prep-issue.sh');
|
|
108
|
+
if (fs_1.default.existsSync(prepIssueRegistryPath)) {
|
|
109
|
+
const userContent = fs_1.default.readFileSync(prepIssueUserPath, 'utf-8');
|
|
110
|
+
const registryContent = fs_1.default.readFileSync(prepIssueRegistryPath, 'utf-8');
|
|
111
|
+
node_assert_1.default.strictEqual(userContent, registryContent, 'User script content should match registry');
|
|
112
|
+
}
|
|
113
|
+
console.log(' ✅ Script sync during init verified!');
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.error(' ❌ Script sync test failed:', error);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
try {
|
|
122
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
123
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
124
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch (e) { }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function testScriptExecutionFromUserDirectory() {
|
|
131
|
+
console.log(' 🚀 Testing Script Execution From User Directory...');
|
|
132
|
+
const tempProjectDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-project-test-'));
|
|
133
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-test');
|
|
134
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
135
|
+
console.log(` 📂 Created temp project dir: ${tempProjectDir}`);
|
|
136
|
+
console.log(` 🏠 Using test user dir: ${userFraimDir}`);
|
|
137
|
+
try {
|
|
138
|
+
// Clean up any existing test user directory
|
|
139
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
140
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
// Create user scripts directory and copy a test script
|
|
143
|
+
fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
|
|
144
|
+
// Create a simple test script that validates working directory behavior
|
|
145
|
+
const testScript = `#!/bin/bash
|
|
146
|
+
set -e
|
|
147
|
+
|
|
148
|
+
# Validate we can find .fraim/config.json in working directory
|
|
149
|
+
if [ ! -f ".fraim/config.json" ]; then
|
|
150
|
+
echo "ERROR: Cannot find .fraim/config.json in working directory"
|
|
151
|
+
exit 1
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
# Output current working directory for validation
|
|
155
|
+
echo "WORKING_DIR=$(pwd)"
|
|
156
|
+
|
|
157
|
+
# Output script location for validation
|
|
158
|
+
echo "SCRIPT_DIR=$(dirname "$0")"
|
|
159
|
+
|
|
160
|
+
# Validate they are different (script in user dir, working in project dir)
|
|
161
|
+
if [ "$(pwd)" = "$(dirname "$0")" ]; then
|
|
162
|
+
echo "ERROR: Working directory should not be same as script directory"
|
|
163
|
+
exit 1
|
|
164
|
+
fi
|
|
165
|
+
|
|
166
|
+
echo "SUCCESS: Script executed from user directory, operating on project directory"
|
|
167
|
+
`;
|
|
168
|
+
const testScriptPath = path_1.default.join(userScriptsDir, 'test-script.sh');
|
|
169
|
+
fs_1.default.writeFileSync(testScriptPath, testScript);
|
|
170
|
+
// Make script executable on Unix systems
|
|
171
|
+
if (process.platform !== 'win32') {
|
|
172
|
+
fs_1.default.chmodSync(testScriptPath, 0o755);
|
|
173
|
+
}
|
|
174
|
+
// Set up project directory with .fraim/config.json
|
|
175
|
+
const projectFraimDir = path_1.default.join(tempProjectDir, '.fraim');
|
|
176
|
+
fs_1.default.mkdirSync(projectFraimDir, { recursive: true });
|
|
177
|
+
const testConfig = {
|
|
178
|
+
project: { name: 'test-project' },
|
|
179
|
+
git: { repoOwner: 'test', repoName: 'test-project' }
|
|
180
|
+
};
|
|
181
|
+
fs_1.default.writeFileSync(path_1.default.join(projectFraimDir, 'config.json'), JSON.stringify(testConfig, null, 2));
|
|
182
|
+
// Execute script from project directory
|
|
183
|
+
console.log(' Executing script from project directory...');
|
|
184
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
185
|
+
// Use Git Bash on Windows, regular bash on Unix
|
|
186
|
+
const bashCommand = process.platform === 'win32'
|
|
187
|
+
? `"C:\\Program Files\\Git\\bin\\bash.exe" "${testScriptPath}"`
|
|
188
|
+
: `"${testScriptPath}"`;
|
|
189
|
+
let output;
|
|
190
|
+
try {
|
|
191
|
+
output = execSync(bashCommand, {
|
|
192
|
+
cwd: tempProjectDir,
|
|
193
|
+
encoding: 'utf-8'
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
// If Git Bash not found on Windows, skip this test
|
|
198
|
+
if (process.platform === 'win32' && error.message.includes('not recognized')) {
|
|
199
|
+
console.log(' ⚠️ Git Bash not found on Windows, skipping script execution test');
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
// Validate output
|
|
205
|
+
node_assert_1.default.ok(output.includes('SUCCESS'), 'Script should execute successfully');
|
|
206
|
+
// On Windows with Git Bash, paths get converted to Unix style
|
|
207
|
+
// Extract the actual working directory from output
|
|
208
|
+
const workingDirMatch = output.match(/WORKING_DIR=(.+)/);
|
|
209
|
+
const scriptDirMatch = output.match(/SCRIPT_DIR=(.+)/);
|
|
210
|
+
if (workingDirMatch && scriptDirMatch) {
|
|
211
|
+
const actualWorkingDir = workingDirMatch[1].trim();
|
|
212
|
+
const actualScriptDir = scriptDirMatch[1].trim();
|
|
213
|
+
// Check that working directory and script directory are different
|
|
214
|
+
node_assert_1.default.notStrictEqual(actualWorkingDir, actualScriptDir, 'Working directory should be different from script directory');
|
|
215
|
+
// Check that script directory contains the expected user scripts path
|
|
216
|
+
const normalizedUserScriptsDir = userScriptsDir.replace(/\\/g, '/');
|
|
217
|
+
const hasCorrectScriptDir = actualScriptDir.includes('.fraim-test/scripts') ||
|
|
218
|
+
actualScriptDir === userScriptsDir ||
|
|
219
|
+
actualScriptDir === normalizedUserScriptsDir;
|
|
220
|
+
node_assert_1.default.ok(hasCorrectScriptDir, `Script directory should be user scripts directory. Got: ${actualScriptDir}`);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
throw new Error('Could not parse working directory and script directory from output');
|
|
224
|
+
}
|
|
225
|
+
console.log(' ✅ Script execution from user directory verified!');
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
console.error(' ❌ Script execution test failed:', error);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
try {
|
|
234
|
+
fs_1.default.rmSync(tempProjectDir, { recursive: true, force: true });
|
|
235
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
236
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (e) { }
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function testScriptSyncDuringSync() {
|
|
243
|
+
console.log(' 🚀 Testing Script Sync During Sync Command...');
|
|
244
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-sync-test-'));
|
|
245
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-test');
|
|
246
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
247
|
+
console.log(` 📂 Created temp dir: ${tempDir}`);
|
|
248
|
+
try {
|
|
249
|
+
// Clean up any existing test user directory
|
|
250
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
251
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
252
|
+
}
|
|
253
|
+
const platform = process.platform;
|
|
254
|
+
const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
255
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
256
|
+
const runFraim = (args) => {
|
|
257
|
+
return new Promise((resolve) => {
|
|
258
|
+
const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
|
|
259
|
+
cwd: tempDir,
|
|
260
|
+
env: {
|
|
261
|
+
...process.env,
|
|
262
|
+
TEST_MODE: 'true',
|
|
263
|
+
FRAIM_USER_DIR: userFraimDir
|
|
264
|
+
},
|
|
265
|
+
shell: true
|
|
266
|
+
});
|
|
267
|
+
let stdout = '';
|
|
268
|
+
let stderr = '';
|
|
269
|
+
ps.stdout.on('data', d => stdout += d.toString());
|
|
270
|
+
ps.stderr.on('data', d => stderr += d.toString());
|
|
271
|
+
ps.on('close', (code) => {
|
|
272
|
+
resolve({ stdout, stderr, code });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
// Set up project
|
|
277
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
278
|
+
execSync('git init', { cwd: tempDir });
|
|
279
|
+
execSync('git remote add origin https://github.com/test-owner/test-project.git', { cwd: tempDir });
|
|
280
|
+
// Initialize project first
|
|
281
|
+
const initRes = await runFraim(['init']);
|
|
282
|
+
node_assert_1.default.strictEqual(initRes.code, 0, 'Init should succeed');
|
|
283
|
+
// Verify initial scripts exist
|
|
284
|
+
node_assert_1.default.ok(fs_1.default.existsSync(userScriptsDir), 'User scripts directory should exist after init');
|
|
285
|
+
// Modify a script to simulate an update
|
|
286
|
+
const testScriptPath = path_1.default.join(userScriptsDir, 'prep-issue.sh');
|
|
287
|
+
const originalContent = fs_1.default.readFileSync(testScriptPath, 'utf-8');
|
|
288
|
+
const modifiedContent = originalContent + '\n# Modified for test';
|
|
289
|
+
fs_1.default.writeFileSync(testScriptPath, modifiedContent);
|
|
290
|
+
// Run sync command
|
|
291
|
+
console.log(' Testing fraim sync with script updates...');
|
|
292
|
+
const syncRes = await runFraim(['sync']);
|
|
293
|
+
// Sync might succeed or warn depending on environment
|
|
294
|
+
if (syncRes.code !== 0) {
|
|
295
|
+
console.log(' ⚠️ Sync returned non-zero (environment dependent)');
|
|
296
|
+
}
|
|
297
|
+
// Verify script was restored to original content (simulating update from registry)
|
|
298
|
+
// Note: In real implementation, this would compare versions and update if needed
|
|
299
|
+
const currentContent = fs_1.default.readFileSync(testScriptPath, 'utf-8');
|
|
300
|
+
// For now, just verify the file still exists and is readable
|
|
301
|
+
node_assert_1.default.ok(currentContent.length > 0, 'Script should have content after sync');
|
|
302
|
+
console.log(' ✅ Script sync during sync command verified!');
|
|
303
|
+
return true;
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error(' ❌ Script sync during sync test failed:', error);
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
try {
|
|
311
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
312
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
313
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
catch (e) { }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function testCrossPlatformScriptExecution() {
|
|
320
|
+
console.log(' 🚀 Testing Cross-Platform Script Execution...');
|
|
321
|
+
const tempProjectDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-cross-platform-test-'));
|
|
322
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-test');
|
|
323
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
324
|
+
try {
|
|
325
|
+
// Clean up any existing test user directory
|
|
326
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
327
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
// Create user scripts directory
|
|
330
|
+
fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
|
|
331
|
+
// Create cross-platform test script
|
|
332
|
+
const crossPlatformScript = `#!/bin/bash
|
|
333
|
+
set -e
|
|
334
|
+
|
|
335
|
+
# Test cross-platform path handling
|
|
336
|
+
echo "Platform: $(uname -s 2>/dev/null || echo "Windows")"
|
|
337
|
+
echo "Working Directory: $(pwd)"
|
|
338
|
+
echo "Script Directory: $(dirname "$0")"
|
|
339
|
+
|
|
340
|
+
# Test config file access
|
|
341
|
+
if [ -f ".fraim/config.json" ]; then
|
|
342
|
+
echo "Config found: YES"
|
|
343
|
+
else
|
|
344
|
+
echo "Config found: NO"
|
|
345
|
+
exit 1
|
|
346
|
+
fi
|
|
347
|
+
|
|
348
|
+
# Test that we can create files in working directory
|
|
349
|
+
echo "test content" > test-output.txt
|
|
350
|
+
if [ -f "test-output.txt" ]; then
|
|
351
|
+
echo "File creation: SUCCESS"
|
|
352
|
+
rm test-output.txt
|
|
353
|
+
else
|
|
354
|
+
echo "File creation: FAILED"
|
|
355
|
+
exit 1
|
|
356
|
+
fi
|
|
357
|
+
|
|
358
|
+
echo "Cross-platform test: SUCCESS"
|
|
359
|
+
`;
|
|
360
|
+
const scriptPath = path_1.default.join(userScriptsDir, 'cross-platform-test.sh');
|
|
361
|
+
fs_1.default.writeFileSync(scriptPath, crossPlatformScript);
|
|
362
|
+
// Set executable permissions on Unix
|
|
363
|
+
if (process.platform !== 'win32') {
|
|
364
|
+
fs_1.default.chmodSync(scriptPath, 0o755);
|
|
365
|
+
}
|
|
366
|
+
// Set up project directory
|
|
367
|
+
const projectFraimDir = path_1.default.join(tempProjectDir, '.fraim');
|
|
368
|
+
fs_1.default.mkdirSync(projectFraimDir, { recursive: true });
|
|
369
|
+
const testConfig = { project: { name: 'cross-platform-test' } };
|
|
370
|
+
fs_1.default.writeFileSync(path_1.default.join(projectFraimDir, 'config.json'), JSON.stringify(testConfig));
|
|
371
|
+
// Execute script
|
|
372
|
+
console.log(' Executing cross-platform script...');
|
|
373
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
374
|
+
// Use Git Bash on Windows, regular bash on Unix
|
|
375
|
+
const bashCommand = process.platform === 'win32'
|
|
376
|
+
? `"C:\\Program Files\\Git\\bin\\bash.exe" "${scriptPath}"`
|
|
377
|
+
: `"${scriptPath}"`;
|
|
378
|
+
let output;
|
|
379
|
+
try {
|
|
380
|
+
output = execSync(bashCommand, {
|
|
381
|
+
cwd: tempProjectDir,
|
|
382
|
+
encoding: 'utf-8'
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// If Git Bash not found on Windows, skip this test
|
|
387
|
+
if (process.platform === 'win32' && error.message.includes('not recognized')) {
|
|
388
|
+
console.log(' ⚠️ Git Bash not found on Windows, skipping cross-platform test');
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
throw error;
|
|
392
|
+
}
|
|
393
|
+
// Validate output
|
|
394
|
+
node_assert_1.default.ok(output.includes('Cross-platform test: SUCCESS'), 'Cross-platform test should succeed');
|
|
395
|
+
node_assert_1.default.ok(output.includes('Config found: YES'), 'Should find config file');
|
|
396
|
+
node_assert_1.default.ok(output.includes('File creation: SUCCESS'), 'Should be able to create files');
|
|
397
|
+
console.log(' ✅ Cross-platform script execution verified!');
|
|
398
|
+
return true;
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
console.error(' ❌ Cross-platform test failed:', error);
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
try {
|
|
406
|
+
fs_1.default.rmSync(tempProjectDir, { recursive: true, force: true });
|
|
407
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
408
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (e) { }
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
async function testNestedDirectoryExecution() {
|
|
415
|
+
console.log(' 🚀 Testing Nested Directory Execution...');
|
|
416
|
+
const tempProjectDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-nested-test-'));
|
|
417
|
+
const userFraimDir = path_1.default.join(os_1.default.homedir(), '.fraim-test');
|
|
418
|
+
const userScriptsDir = path_1.default.join(userFraimDir, 'scripts');
|
|
419
|
+
try {
|
|
420
|
+
// Clean up and setup
|
|
421
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
422
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
423
|
+
}
|
|
424
|
+
fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
|
|
425
|
+
// Create script that finds project root
|
|
426
|
+
const rootFinderScript = `#!/bin/bash
|
|
427
|
+
set -e
|
|
428
|
+
|
|
429
|
+
# Function to find project root (directory containing .fraim/config.json)
|
|
430
|
+
find_project_root() {
|
|
431
|
+
local current_dir="$(pwd)"
|
|
432
|
+
while [ "$current_dir" != "/" ] && [ "$current_dir" != "" ]; do
|
|
433
|
+
if [ -f "$current_dir/.fraim/config.json" ]; then
|
|
434
|
+
echo "$current_dir"
|
|
435
|
+
return 0
|
|
436
|
+
fi
|
|
437
|
+
current_dir="$(dirname "$current_dir")"
|
|
438
|
+
done
|
|
439
|
+
return 1
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
echo "Current working directory: $(pwd)"
|
|
443
|
+
echo "Script directory: $(dirname "$0")"
|
|
444
|
+
|
|
445
|
+
# Find project root
|
|
446
|
+
PROJECT_ROOT=$(find_project_root)
|
|
447
|
+
if [ $? -eq 0 ]; then
|
|
448
|
+
echo "Project root found: $PROJECT_ROOT"
|
|
449
|
+
echo "Config exists: $([ -f "$PROJECT_ROOT/.fraim/config.json" ] && echo "YES" || echo "NO")"
|
|
450
|
+
else
|
|
451
|
+
echo "ERROR: Could not find project root"
|
|
452
|
+
exit 1
|
|
453
|
+
fi
|
|
454
|
+
|
|
455
|
+
echo "Nested execution test: SUCCESS"
|
|
456
|
+
`;
|
|
457
|
+
const scriptPath = path_1.default.join(userScriptsDir, 'nested-test.sh');
|
|
458
|
+
fs_1.default.writeFileSync(scriptPath, rootFinderScript);
|
|
459
|
+
if (process.platform !== 'win32') {
|
|
460
|
+
fs_1.default.chmodSync(scriptPath, 0o755);
|
|
461
|
+
}
|
|
462
|
+
// Set up project with nested directories
|
|
463
|
+
const projectFraimDir = path_1.default.join(tempProjectDir, '.fraim');
|
|
464
|
+
const nestedDir = path_1.default.join(tempProjectDir, 'src', 'components', 'deep');
|
|
465
|
+
fs_1.default.mkdirSync(projectFraimDir, { recursive: true });
|
|
466
|
+
fs_1.default.mkdirSync(nestedDir, { recursive: true });
|
|
467
|
+
const testConfig = { project: { name: 'nested-test' } };
|
|
468
|
+
fs_1.default.writeFileSync(path_1.default.join(projectFraimDir, 'config.json'), JSON.stringify(testConfig));
|
|
469
|
+
// Execute script from nested directory
|
|
470
|
+
console.log(' Executing script from nested directory...');
|
|
471
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
472
|
+
// Use Git Bash on Windows, regular bash on Unix
|
|
473
|
+
const bashCommand = process.platform === 'win32'
|
|
474
|
+
? `"C:\\Program Files\\Git\\bin\\bash.exe" "${scriptPath}"`
|
|
475
|
+
: `"${scriptPath}"`;
|
|
476
|
+
let output;
|
|
477
|
+
try {
|
|
478
|
+
output = execSync(bashCommand, {
|
|
479
|
+
cwd: nestedDir,
|
|
480
|
+
encoding: 'utf-8'
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
catch (error) {
|
|
484
|
+
// If Git Bash not found on Windows, skip this test
|
|
485
|
+
if (process.platform === 'win32' && error.message.includes('not recognized')) {
|
|
486
|
+
console.log(' ⚠️ Git Bash not found on Windows, skipping nested directory test');
|
|
487
|
+
return true;
|
|
488
|
+
}
|
|
489
|
+
throw error;
|
|
490
|
+
}
|
|
491
|
+
// Validate output
|
|
492
|
+
node_assert_1.default.ok(output.includes('Nested execution test: SUCCESS'), 'Nested execution should succeed');
|
|
493
|
+
// Extract the actual project root from output (Git Bash may convert paths)
|
|
494
|
+
const projectRootMatch = output.match(/Project root found: (.+)/);
|
|
495
|
+
if (projectRootMatch) {
|
|
496
|
+
const actualProjectRoot = projectRootMatch[1].trim();
|
|
497
|
+
// Check that the project root contains the expected directory name
|
|
498
|
+
const projectDirName = path_1.default.basename(tempProjectDir);
|
|
499
|
+
node_assert_1.default.ok(actualProjectRoot.includes(projectDirName), `Should find project directory. Got: ${actualProjectRoot}, Expected to contain: ${projectDirName}`);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
throw new Error('Could not parse project root from output');
|
|
503
|
+
}
|
|
504
|
+
node_assert_1.default.ok(output.includes('Config exists: YES'), 'Should find config in project root');
|
|
505
|
+
console.log(' ✅ Nested directory execution verified!');
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
catch (error) {
|
|
509
|
+
console.error(' ❌ Nested directory test failed:', error);
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
finally {
|
|
513
|
+
try {
|
|
514
|
+
fs_1.default.rmSync(tempProjectDir, { recursive: true, force: true });
|
|
515
|
+
if (fs_1.default.existsSync(userFraimDir)) {
|
|
516
|
+
fs_1.default.rmSync(userFraimDir, { recursive: true, force: true });
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch (e) { }
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async function runScriptSyncTest(testCase) {
|
|
523
|
+
return await testCase.testFunction();
|
|
524
|
+
}
|
|
525
|
+
const testCases = [
|
|
526
|
+
{
|
|
527
|
+
name: 'Script Sync During Init',
|
|
528
|
+
description: 'Tests that fraim init copies scripts to user directory with correct permissions',
|
|
529
|
+
testFunction: testScriptSyncDuringInit,
|
|
530
|
+
tags: ['script-sync', 'init']
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
name: 'Script Execution From User Directory',
|
|
534
|
+
description: 'Tests that scripts execute from user directory but operate on project directory',
|
|
535
|
+
testFunction: testScriptExecutionFromUserDirectory,
|
|
536
|
+
tags: ['script-sync', 'execution']
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'Script Sync During Sync Command',
|
|
540
|
+
description: 'Tests that fraim sync updates scripts in user directory',
|
|
541
|
+
testFunction: testScriptSyncDuringSync,
|
|
542
|
+
tags: ['script-sync', 'sync']
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: 'Cross-Platform Script Execution',
|
|
546
|
+
description: 'Tests that scripts work correctly on Windows, macOS, and Linux',
|
|
547
|
+
testFunction: testCrossPlatformScriptExecution,
|
|
548
|
+
tags: ['script-sync', 'cross-platform']
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: 'Nested Directory Execution',
|
|
552
|
+
description: 'Tests that scripts work when executed from nested project directories',
|
|
553
|
+
testFunction: testNestedDirectoryExecution,
|
|
554
|
+
tags: ['script-sync', 'nested']
|
|
555
|
+
}
|
|
556
|
+
];
|
|
557
|
+
(0, test_utils_1.runTests)(testCases, runScriptSyncTest, 'Script Synchronization Tests');
|
|
@@ -21,8 +21,8 @@ async function testRehydrationAndExemptions() {
|
|
|
21
21
|
const startServer = async () => {
|
|
22
22
|
console.log(` Starting server on port ${PORT}...`);
|
|
23
23
|
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
24
|
-
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.
|
|
25
|
-
const proc = (0, node_child_process_1.spawn)(npxCommand, ['
|
|
24
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
25
|
+
const proc = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
|
|
26
26
|
env: {
|
|
27
27
|
...process.env,
|
|
28
28
|
FRAIM_MCP_PORT: PORT.toString(),
|
|
@@ -14,7 +14,7 @@ async function testServerStartsAndResponds() {
|
|
|
14
14
|
console.log(' 🚀 Testing Fraim Standalone Server...');
|
|
15
15
|
let fraimProcess;
|
|
16
16
|
let dbService;
|
|
17
|
-
const PORT =
|
|
17
|
+
const PORT = Math.floor(Math.random() * 1000) + 10000; // Random port to avoid conflicts
|
|
18
18
|
const TEST_API_KEY = 'test-fraim-key-integration';
|
|
19
19
|
const TEST_ADMIN_KEY = 'test-admin-key-integration';
|
|
20
20
|
const TEST_ADMIN_HEADER = { 'x-admin-key': TEST_ADMIN_KEY };
|
|
@@ -35,8 +35,8 @@ async function testServerStartsAndResponds() {
|
|
|
35
35
|
// 2. Start server in standalone mode
|
|
36
36
|
console.log(` Starting server on port ${PORT}...`);
|
|
37
37
|
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
38
|
-
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.
|
|
39
|
-
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['
|
|
38
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
39
|
+
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
|
|
40
40
|
env: {
|
|
41
41
|
...process.env,
|
|
42
42
|
FRAIM_MCP_PORT: PORT.toString(),
|
|
@@ -19,7 +19,7 @@ async function testSyncUpdateVersion() {
|
|
|
19
19
|
try {
|
|
20
20
|
const platform = process.platform;
|
|
21
21
|
const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
22
|
-
const cliScript =
|
|
22
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
23
23
|
// Setup .fraim/config.json with an OLD version
|
|
24
24
|
const fraimDir = path_1.default.join(tempDir, '.fraim');
|
|
25
25
|
fs_1.default.mkdirSync(fraimDir, { recursive: true });
|
|
@@ -39,8 +39,8 @@ async function testTelemetryFlow() {
|
|
|
39
39
|
// 2. Start Server
|
|
40
40
|
console.log(` Starting server on port ${PORT}...`);
|
|
41
41
|
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
42
|
-
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.
|
|
43
|
-
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['
|
|
42
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
43
|
+
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
|
|
44
44
|
env: {
|
|
45
45
|
...process.env,
|
|
46
46
|
FRAIM_MCP_PORT: PORT.toString(),
|
|
@@ -19,7 +19,7 @@ async function testDualDiscoveryJourney() {
|
|
|
19
19
|
console.log(` 📂 Project Context: ${tempDir}`);
|
|
20
20
|
let fraimProcess;
|
|
21
21
|
let dbService;
|
|
22
|
-
const PORT =
|
|
22
|
+
const PORT = Math.floor(Math.random() * 1000) + 10000; // Random port to avoid conflicts
|
|
23
23
|
const BASE_URL = `http://localhost:${PORT}`;
|
|
24
24
|
const TEST_API_KEY = 'fraim-journey-key';
|
|
25
25
|
try {
|
|
@@ -30,11 +30,15 @@ async function testDualDiscoveryJourney() {
|
|
|
30
30
|
await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, { $set: { userId: 'journey-user', orgId: 'journey-org', isActive: true } }, { upsert: true });
|
|
31
31
|
// --- SCENARIO 1: Project Initialization ---
|
|
32
32
|
console.log('\n [1/4] Scenario: User initializes project');
|
|
33
|
+
// Set up git repository (required for new init logic)
|
|
34
|
+
const { execSync } = require('child_process');
|
|
35
|
+
execSync('git init', { cwd: tempDir });
|
|
36
|
+
execSync('git remote add origin https://github.com/test-owner/test-repo.git', { cwd: tempDir });
|
|
33
37
|
// Mock registry in the repo for sync to work
|
|
34
38
|
// We will point the test to use the repo's actual registry folder
|
|
35
39
|
// But for 'init' we just need the script
|
|
36
|
-
const cliScript =
|
|
37
|
-
const tsxCli =
|
|
40
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
41
|
+
const tsxCli = (0, test_utils_1.resolveProjectPath)('node_modules/tsx/dist/cli.mjs');
|
|
38
42
|
const runFraim = async (args) => {
|
|
39
43
|
return new Promise(resolve => {
|
|
40
44
|
const ps = (0, node_child_process_1.spawn)(process.execPath, [tsxCli, cliScript, ...args], {
|
|
@@ -152,7 +156,7 @@ async function testDualDiscoveryJourney() {
|
|
|
152
156
|
fs_1.default.writeFileSync(path_1.default.join(tempDir, 'package.json'), JSON.stringify({
|
|
153
157
|
dependencies: { "@fraim/framework": "1.0.0" }
|
|
154
158
|
}));
|
|
155
|
-
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.
|
|
159
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
156
160
|
fraimProcess = (0, node_child_process_1.spawn)(process.execPath, [tsxCli, serverScript], {
|
|
157
161
|
cwd: tempDir,
|
|
158
162
|
env: { ...process.env, FRAIM_MCP_PORT: PORT.toString(), FRAIM_SKIP_INDEX_ON_START: 'true' },
|