fraim-framework 2.0.30 → 2.0.33
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/dist/src/cli/commands/init.js +29 -2
- package/dist/src/cli/commands/sync.js +18 -1
- package/dist/src/utils/script-sync-utils.js +218 -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 +349 -0
- package/dist/tests/test-first-run-journey.js +43 -3
- package/dist/tests/test-hybrid-script-execution.js +369 -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/bin/fraim.js +0 -23
- 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
|
@@ -13,6 +13,7 @@ const sync_1 = require("./sync");
|
|
|
13
13
|
const types_1 = require("../../fraim/types");
|
|
14
14
|
const git_utils_1 = require("../../utils/git-utils");
|
|
15
15
|
const version_utils_1 = require("../../utils/version-utils");
|
|
16
|
+
const script_sync_utils_1 = require("../../utils/script-sync-utils");
|
|
16
17
|
const runInit = async () => {
|
|
17
18
|
const projectRoot = process.cwd();
|
|
18
19
|
const fraimDir = path_1.default.join(projectRoot, '.fraim');
|
|
@@ -27,17 +28,23 @@ const runInit = async () => {
|
|
|
27
28
|
}
|
|
28
29
|
if (!fs_1.default.existsSync(configPath)) {
|
|
29
30
|
const remoteInfo = (0, git_utils_1.getGitRemoteInfo)();
|
|
31
|
+
if (!remoteInfo.repo) {
|
|
32
|
+
console.log(chalk_1.default.red('❌ Error: No git remote found. FRAIM requires a git repository with remote origin.'));
|
|
33
|
+
console.log(chalk_1.default.yellow('💡 Make sure you\'re in a git repository and have set up a remote origin:'));
|
|
34
|
+
console.log(chalk_1.default.gray(' git remote add origin <your-repo-url>'));
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
30
37
|
const config = {
|
|
31
38
|
...types_1.DEFAULT_FRAIM_CONFIG,
|
|
32
39
|
version: (0, version_utils_1.getFraimVersion)(),
|
|
33
40
|
project: {
|
|
34
41
|
...types_1.DEFAULT_FRAIM_CONFIG.project,
|
|
35
|
-
name:
|
|
42
|
+
name: remoteInfo.repo
|
|
36
43
|
},
|
|
37
44
|
git: {
|
|
38
45
|
...types_1.DEFAULT_FRAIM_CONFIG.git,
|
|
39
46
|
repoOwner: remoteInfo.owner || types_1.DEFAULT_FRAIM_CONFIG.git.repoOwner,
|
|
40
|
-
repoName: remoteInfo.repo
|
|
47
|
+
repoName: remoteInfo.repo
|
|
41
48
|
}
|
|
42
49
|
};
|
|
43
50
|
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
@@ -54,6 +61,26 @@ const runInit = async () => {
|
|
|
54
61
|
console.log(chalk_1.default.blue('\n🎉 FRAIM initialized successfully!'));
|
|
55
62
|
// Sync workflows from registry
|
|
56
63
|
await (0, sync_1.runSync)({});
|
|
64
|
+
// Sync scripts to user directory
|
|
65
|
+
console.log(chalk_1.default.blue('🔄 Syncing FRAIM scripts to user directory...'));
|
|
66
|
+
// Find registry path (same logic as sync command)
|
|
67
|
+
let registryPath = path_1.default.join(__dirname, '../../../../registry');
|
|
68
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
69
|
+
registryPath = path_1.default.join(__dirname, '../../../registry');
|
|
70
|
+
}
|
|
71
|
+
if (!fs_1.default.existsSync(registryPath)) {
|
|
72
|
+
registryPath = path_1.default.join(projectRoot, 'registry');
|
|
73
|
+
}
|
|
74
|
+
if (fs_1.default.existsSync(registryPath)) {
|
|
75
|
+
const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
|
|
76
|
+
console.log(chalk_1.default.green(`✅ Synced ${syncResult.synced} self-contained scripts to user directory.`));
|
|
77
|
+
if (syncResult.ephemeral > 0) {
|
|
78
|
+
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log(chalk_1.default.yellow('⚠️ Registry not found, skipping script sync.'));
|
|
83
|
+
}
|
|
57
84
|
// Trigger First Run Experience
|
|
58
85
|
await (0, first_run_1.runFirstRunExperience)();
|
|
59
86
|
process.exit(0);
|
|
@@ -12,6 +12,7 @@ const digest_utils_1 = require("../../utils/digest-utils");
|
|
|
12
12
|
const stub_generator_1 = require("../../utils/stub-generator");
|
|
13
13
|
const config_loader_1 = require("../../fraim/config-loader");
|
|
14
14
|
const version_utils_1 = require("../../utils/version-utils");
|
|
15
|
+
const script_sync_utils_1 = require("../../utils/script-sync-utils");
|
|
15
16
|
const runSync = async (options) => {
|
|
16
17
|
const projectRoot = process.cwd();
|
|
17
18
|
const config = (0, config_loader_1.loadFraimConfig)();
|
|
@@ -134,7 +135,23 @@ const runSync = async (options) => {
|
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
fs_1.default.writeFileSync(digestPath, currentDigest);
|
|
137
|
-
console.log(chalk_1.default.green(`\n✅
|
|
138
|
+
console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Generated ${generatedStubs.length} stubs.`));
|
|
139
|
+
// Sync scripts to user directory
|
|
140
|
+
console.log(chalk_1.default.blue('\n🔄 Syncing FRAIM scripts to user directory...'));
|
|
141
|
+
const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
|
|
142
|
+
const cleanedScriptCount = (0, script_sync_utils_1.cleanupObsoleteUserScripts)(registryPath);
|
|
143
|
+
if (syncResult.synced > 0 || cleanedScriptCount > 0) {
|
|
144
|
+
console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced} self-contained scripts, removed ${cleanedScriptCount} obsolete scripts.`));
|
|
145
|
+
if (syncResult.ephemeral > 0) {
|
|
146
|
+
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
console.log(chalk_1.default.green('✅ Scripts are already in sync.'));
|
|
151
|
+
if (syncResult.ephemeral > 0) {
|
|
152
|
+
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
138
155
|
};
|
|
139
156
|
exports.runSync = runSync;
|
|
140
157
|
exports.syncCommand = new commander_1.Command('sync')
|
|
@@ -0,0 +1,218 @@
|
|
|
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
|
+
exports.getUserFraimDir = getUserFraimDir;
|
|
7
|
+
exports.getUserScriptsDir = getUserScriptsDir;
|
|
8
|
+
exports.ensureUserFraimDirectories = ensureUserFraimDirectories;
|
|
9
|
+
exports.isScriptSelfContained = isScriptSelfContained;
|
|
10
|
+
exports.getRegistryScripts = getRegistryScripts;
|
|
11
|
+
exports.copyScriptToUserDirectory = copyScriptToUserDirectory;
|
|
12
|
+
exports.syncScriptsToUserDirectory = syncScriptsToUserDirectory;
|
|
13
|
+
exports.cleanupObsoleteUserScripts = cleanupObsoleteUserScripts;
|
|
14
|
+
exports.getUserScriptPath = getUserScriptPath;
|
|
15
|
+
exports.userScriptExists = userScriptExists;
|
|
16
|
+
exports.getDependentRegistryScripts = getDependentRegistryScripts;
|
|
17
|
+
exports.validateUserScripts = validateUserScripts;
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
const os_1 = __importDefault(require("os"));
|
|
21
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
22
|
+
/**
|
|
23
|
+
* Get the user-level FRAIM directory path
|
|
24
|
+
* Can be overridden with FRAIM_USER_DIR environment variable for testing
|
|
25
|
+
*/
|
|
26
|
+
function getUserFraimDir() {
|
|
27
|
+
return process.env.FRAIM_USER_DIR || path_1.default.join(os_1.default.homedir(), '.fraim');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the user-level scripts directory path
|
|
31
|
+
*/
|
|
32
|
+
function getUserScriptsDir() {
|
|
33
|
+
return path_1.default.join(getUserFraimDir(), 'scripts');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ensure the user-level FRAIM directories exist
|
|
37
|
+
*/
|
|
38
|
+
function ensureUserFraimDirectories() {
|
|
39
|
+
const userFraimDir = getUserFraimDir();
|
|
40
|
+
const userScriptsDir = getUserScriptsDir();
|
|
41
|
+
if (!fs_1.default.existsSync(userFraimDir)) {
|
|
42
|
+
fs_1.default.mkdirSync(userFraimDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
if (!fs_1.default.existsSync(userScriptsDir)) {
|
|
45
|
+
fs_1.default.mkdirSync(userScriptsDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a script is self-contained (no relative imports to FRAIM source)
|
|
50
|
+
*/
|
|
51
|
+
function isScriptSelfContained(scriptPath) {
|
|
52
|
+
// Shell scripts are always considered self-contained
|
|
53
|
+
if (scriptPath.endsWith('.sh')) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// For TypeScript/JavaScript files, check for relative imports to src/
|
|
57
|
+
if (scriptPath.endsWith('.ts') || scriptPath.endsWith('.js')) {
|
|
58
|
+
try {
|
|
59
|
+
const content = fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
60
|
+
// Look for imports that reference ../../src/ or similar patterns
|
|
61
|
+
const hasRelativeImports = /import.*['"]\.\.\/.*src\//.test(content) ||
|
|
62
|
+
/require\(['"]\.\.\/.*src\//.test(content) ||
|
|
63
|
+
/from ['"]\.\.\/.*src\//.test(content);
|
|
64
|
+
return !hasRelativeImports;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
// If we can't read the file, assume it's not self-contained
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all self-contained script files from the registry
|
|
75
|
+
*/
|
|
76
|
+
function getRegistryScripts(registryPath) {
|
|
77
|
+
const scriptsPath = path_1.default.join(registryPath, 'scripts');
|
|
78
|
+
if (!fs_1.default.existsSync(scriptsPath)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const entries = fs_1.default.readdirSync(scriptsPath, { withFileTypes: true });
|
|
82
|
+
const allScripts = entries
|
|
83
|
+
.filter(entry => entry.isFile() && (entry.name.endsWith('.sh') || entry.name.endsWith('.ts') || entry.name.endsWith('.js')))
|
|
84
|
+
.map(entry => path_1.default.join(scriptsPath, entry.name));
|
|
85
|
+
// Only return self-contained scripts
|
|
86
|
+
return allScripts.filter(scriptPath => isScriptSelfContained(scriptPath));
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Copy a script file to the user scripts directory with proper permissions
|
|
90
|
+
*/
|
|
91
|
+
function copyScriptToUserDirectory(sourcePath, scriptName) {
|
|
92
|
+
const userScriptsDir = getUserScriptsDir();
|
|
93
|
+
const targetPath = path_1.default.join(userScriptsDir, scriptName);
|
|
94
|
+
// Copy the file
|
|
95
|
+
fs_1.default.copyFileSync(sourcePath, targetPath);
|
|
96
|
+
// Set executable permissions on Unix systems
|
|
97
|
+
if (process.platform !== 'win32' && scriptName.endsWith('.sh')) {
|
|
98
|
+
try {
|
|
99
|
+
fs_1.default.chmodSync(targetPath, 0o755);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${scriptName}`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Sync all scripts from registry to user directory
|
|
108
|
+
*/
|
|
109
|
+
function syncScriptsToUserDirectory(registryPath) {
|
|
110
|
+
ensureUserFraimDirectories();
|
|
111
|
+
const selfContainedScripts = getRegistryScripts(registryPath);
|
|
112
|
+
const dependentScripts = getDependentRegistryScripts(registryPath);
|
|
113
|
+
let syncedCount = 0;
|
|
114
|
+
console.log(chalk_1.default.gray(` Self-contained scripts (will be synced to ~/.fraim/scripts/):`));
|
|
115
|
+
for (const scriptPath of selfContainedScripts) {
|
|
116
|
+
const scriptName = path_1.default.basename(scriptPath);
|
|
117
|
+
const userScriptPath = path_1.default.join(getUserScriptsDir(), scriptName);
|
|
118
|
+
try {
|
|
119
|
+
// Check if script needs updating
|
|
120
|
+
let needsUpdate = true;
|
|
121
|
+
if (fs_1.default.existsSync(userScriptPath)) {
|
|
122
|
+
const registryContent = fs_1.default.readFileSync(scriptPath, 'utf-8');
|
|
123
|
+
const userContent = fs_1.default.readFileSync(userScriptPath, 'utf-8');
|
|
124
|
+
needsUpdate = registryContent !== userContent;
|
|
125
|
+
}
|
|
126
|
+
if (needsUpdate) {
|
|
127
|
+
copyScriptToUserDirectory(scriptPath, scriptName);
|
|
128
|
+
syncedCount++;
|
|
129
|
+
console.log(chalk_1.default.gray(` + ${scriptName}`));
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
console.log(chalk_1.default.gray(` = ${scriptName} (up to date)`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${scriptName}: ${error}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (dependentScripts.length > 0) {
|
|
140
|
+
console.log(chalk_1.default.gray(` Dependent scripts (will use ephemeral execution):`));
|
|
141
|
+
for (const scriptPath of dependentScripts) {
|
|
142
|
+
const scriptName = path_1.default.basename(scriptPath);
|
|
143
|
+
console.log(chalk_1.default.gray(` ~ ${scriptName} (has FRAIM dependencies)`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { synced: syncedCount, ephemeral: dependentScripts.length };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Clean up scripts in user directory that no longer exist in registry
|
|
150
|
+
*/
|
|
151
|
+
function cleanupObsoleteUserScripts(registryPath) {
|
|
152
|
+
const userScriptsDir = getUserScriptsDir();
|
|
153
|
+
if (!fs_1.default.existsSync(userScriptsDir)) {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
const registryScripts = getRegistryScripts(registryPath);
|
|
157
|
+
const registryScriptNames = registryScripts.map(scriptPath => path_1.default.basename(scriptPath));
|
|
158
|
+
const userScripts = fs_1.default.readdirSync(userScriptsDir)
|
|
159
|
+
.filter(file => file.endsWith('.sh') || file.endsWith('.ts') || file.endsWith('.js'));
|
|
160
|
+
let cleanedCount = 0;
|
|
161
|
+
for (const userScript of userScripts) {
|
|
162
|
+
if (!registryScriptNames.includes(userScript)) {
|
|
163
|
+
try {
|
|
164
|
+
const userScriptPath = path_1.default.join(userScriptsDir, userScript);
|
|
165
|
+
fs_1.default.unlinkSync(userScriptPath);
|
|
166
|
+
cleanedCount++;
|
|
167
|
+
console.log(chalk_1.default.yellow(` - ${userScript} (removed from registry)`));
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${userScript}: ${error}`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return cleanedCount;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Get the path to a script in the user directory
|
|
178
|
+
*/
|
|
179
|
+
function getUserScriptPath(scriptName) {
|
|
180
|
+
return path_1.default.join(getUserScriptsDir(), scriptName);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if a script exists in the user directory
|
|
184
|
+
*/
|
|
185
|
+
function userScriptExists(scriptName) {
|
|
186
|
+
return fs_1.default.existsSync(getUserScriptPath(scriptName));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get all dependent script files from the registry (those with FRAIM source dependencies)
|
|
190
|
+
*/
|
|
191
|
+
function getDependentRegistryScripts(registryPath) {
|
|
192
|
+
const scriptsPath = path_1.default.join(registryPath, 'scripts');
|
|
193
|
+
if (!fs_1.default.existsSync(scriptsPath)) {
|
|
194
|
+
return [];
|
|
195
|
+
}
|
|
196
|
+
const entries = fs_1.default.readdirSync(scriptsPath, { withFileTypes: true });
|
|
197
|
+
const allScripts = entries
|
|
198
|
+
.filter(entry => entry.isFile() && (entry.name.endsWith('.sh') || entry.name.endsWith('.ts') || entry.name.endsWith('.js')))
|
|
199
|
+
.map(entry => path_1.default.join(scriptsPath, entry.name));
|
|
200
|
+
// Only return scripts that have dependencies
|
|
201
|
+
return allScripts.filter(scriptPath => !isScriptSelfContained(scriptPath));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Validate that all expected scripts are present in user directory
|
|
205
|
+
*/
|
|
206
|
+
function validateUserScripts(expectedScripts) {
|
|
207
|
+
const missing = [];
|
|
208
|
+
const present = [];
|
|
209
|
+
for (const scriptName of expectedScripts) {
|
|
210
|
+
if (userScriptExists(scriptName)) {
|
|
211
|
+
present.push(scriptName);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
missing.push(scriptName);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { missing, present };
|
|
218
|
+
}
|
|
@@ -13,7 +13,7 @@ async function debugListTools() {
|
|
|
13
13
|
console.log(' 🔍 Debugging Available Tools...');
|
|
14
14
|
let fraimProcess;
|
|
15
15
|
let dbService;
|
|
16
|
-
const PORT =
|
|
16
|
+
const PORT = Math.floor(Math.random() * 1000) + 10000; // Random port to avoid conflicts
|
|
17
17
|
const TEST_API_KEY = 'debug-tools-key';
|
|
18
18
|
const BASE_URL = `http://localhost:${PORT}`;
|
|
19
19
|
try {
|
|
@@ -24,8 +24,8 @@ async function debugListTools() {
|
|
|
24
24
|
await db.collection('fraim_api_keys').updateOne({ key: TEST_API_KEY }, { $set: { userId: 'debug@test.com', orgId: 'debug-org', isActive: true, createdAt: new Date() } }, { upsert: true });
|
|
25
25
|
// 2. Start server
|
|
26
26
|
const npxCommand = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
27
|
-
const serverScript = path_1.default.resolve(__dirname, '../src/fraim-mcp-server.
|
|
28
|
-
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['
|
|
27
|
+
const serverScript = path_1.default.resolve(__dirname, '../dist/src/fraim-mcp-server.js');
|
|
28
|
+
fraimProcess = (0, node_child_process_1.spawn)(npxCommand, ['node', `"${serverScript}"`], {
|
|
29
29
|
env: { ...process.env, FRAIM_MCP_PORT: PORT.toString(), FRAIM_SKIP_INDEX_ON_START: 'true' },
|
|
30
30
|
shell: true
|
|
31
31
|
});
|
|
@@ -65,8 +65,9 @@ async function debugListTools() {
|
|
|
65
65
|
finally {
|
|
66
66
|
if (dbService)
|
|
67
67
|
await dbService.close().catch(() => { });
|
|
68
|
-
if (fraimProcess && fraimProcess.pid)
|
|
69
|
-
(0, tree_kill_1.default)(fraimProcess.pid);
|
|
68
|
+
if (fraimProcess && fraimProcess.pid) {
|
|
69
|
+
await new Promise((resolve) => (0, tree_kill_1.default)(fraimProcess.pid, 'SIGKILL', () => resolve()));
|
|
70
|
+
}
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
73
|
async function runTest(testCase) {
|
|
@@ -41,10 +41,21 @@ async function testChalkConflictScenario() {
|
|
|
41
41
|
console.log(' This ensures pinned chalk v4 works even with chalk v5 present\n');
|
|
42
42
|
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'chalk-regression-'));
|
|
43
43
|
console.log(` 📂 Temp dir: ${tempDir}`);
|
|
44
|
+
let tarballPath = null;
|
|
44
45
|
try {
|
|
45
46
|
// 1. Pack fraim-framework
|
|
46
47
|
console.log(' 📦 Packing fraim-framework...');
|
|
47
48
|
const projectRoot = process.cwd();
|
|
49
|
+
// Clean up any existing tarballs first
|
|
50
|
+
const existingTarballs = fs_1.default.readdirSync(projectRoot).filter(f => f.startsWith('fraim-framework-') && f.endsWith('.tgz'));
|
|
51
|
+
for (const tarball of existingTarballs) {
|
|
52
|
+
try {
|
|
53
|
+
fs_1.default.unlinkSync(path_1.default.join(projectRoot, tarball));
|
|
54
|
+
}
|
|
55
|
+
catch (e) {
|
|
56
|
+
// Ignore cleanup errors
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
const packResult = (0, node_child_process_1.execSync)('npm pack', {
|
|
49
60
|
cwd: projectRoot,
|
|
50
61
|
encoding: 'utf-8'
|
|
@@ -54,7 +65,7 @@ async function testChalkConflictScenario() {
|
|
|
54
65
|
console.log(' ❌ Failed to create tarball');
|
|
55
66
|
return false;
|
|
56
67
|
}
|
|
57
|
-
|
|
68
|
+
tarballPath = path_1.default.join(projectRoot, tarballName);
|
|
58
69
|
console.log(` ✅ Created: ${tarballName}`);
|
|
59
70
|
// 2. Create test project with chalk v5
|
|
60
71
|
console.log(' 📝 Creating test project with chalk v5...');
|
|
@@ -161,7 +172,9 @@ try {
|
|
|
161
172
|
console.log(' ⚠️ This might cause issues in some environments');
|
|
162
173
|
console.log(' ⚠️ But CLI loaded successfully, so test passes');
|
|
163
174
|
}
|
|
164
|
-
fs_1.default.
|
|
175
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
176
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
177
|
+
}
|
|
165
178
|
return true;
|
|
166
179
|
}
|
|
167
180
|
else if (hasERR_REQUIRE_ESM) {
|
|
@@ -173,19 +186,31 @@ try {
|
|
|
173
186
|
else {
|
|
174
187
|
console.log(' ❌ fraim-framework has chalk v4 but still got error (unexpected)');
|
|
175
188
|
}
|
|
176
|
-
fs_1.default.
|
|
189
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
190
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
191
|
+
}
|
|
177
192
|
return false;
|
|
178
193
|
}
|
|
179
194
|
else {
|
|
180
195
|
console.log(' ❌ Got unexpected error (exit code: ' + testResult.code + ')');
|
|
181
196
|
console.log(' ❌ Check the error output above');
|
|
182
|
-
fs_1.default.
|
|
197
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
198
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
199
|
+
}
|
|
183
200
|
return false;
|
|
184
201
|
}
|
|
185
202
|
}
|
|
186
203
|
catch (error) {
|
|
187
204
|
console.error(' ❌ Conflict scenario test failed with exception:', error);
|
|
188
205
|
console.error(' ❌ This is likely a test infrastructure issue, not a chalk issue');
|
|
206
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
207
|
+
try {
|
|
208
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
// Ignore cleanup errors
|
|
212
|
+
}
|
|
213
|
+
}
|
|
189
214
|
return false;
|
|
190
215
|
}
|
|
191
216
|
finally {
|
|
@@ -203,10 +228,21 @@ async function testFreshInstall() {
|
|
|
203
228
|
console.log(' This simulates: npx fraim-framework init\n');
|
|
204
229
|
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'chalk-fresh-'));
|
|
205
230
|
console.log(` 📂 Temp dir: ${tempDir}`);
|
|
231
|
+
let tarballPath = null;
|
|
206
232
|
try {
|
|
207
233
|
// 1. Pack fraim-framework
|
|
208
234
|
console.log(' 📦 Packing fraim-framework...');
|
|
209
235
|
const projectRoot = process.cwd();
|
|
236
|
+
// Clean up any existing tarballs first
|
|
237
|
+
const existingTarballs = fs_1.default.readdirSync(projectRoot).filter(f => f.startsWith('fraim-framework-') && f.endsWith('.tgz'));
|
|
238
|
+
for (const tarball of existingTarballs) {
|
|
239
|
+
try {
|
|
240
|
+
fs_1.default.unlinkSync(path_1.default.join(projectRoot, tarball));
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
// Ignore cleanup errors
|
|
244
|
+
}
|
|
245
|
+
}
|
|
210
246
|
const packResult = (0, node_child_process_1.execSync)('npm pack', {
|
|
211
247
|
cwd: projectRoot,
|
|
212
248
|
encoding: 'utf-8'
|
|
@@ -216,7 +252,7 @@ async function testFreshInstall() {
|
|
|
216
252
|
console.log(' ❌ Failed to create tarball');
|
|
217
253
|
return false;
|
|
218
254
|
}
|
|
219
|
-
|
|
255
|
+
tarballPath = path_1.default.join(projectRoot, tarballName);
|
|
220
256
|
console.log(` ✅ Created: ${tarballName}`);
|
|
221
257
|
// 2. Create empty project
|
|
222
258
|
console.log(' 📝 Creating empty project...');
|
|
@@ -277,24 +313,38 @@ try {
|
|
|
277
313
|
const cliLoaded = testResult.stdout.includes('✓ FRAIM CLI loaded successfully') || testResult.stderr.includes('Usage: fraim');
|
|
278
314
|
if (cliLoaded && !hasERR_REQUIRE_ESM) {
|
|
279
315
|
console.log(' ✅ SUCCESS: Fresh install works correctly');
|
|
280
|
-
fs_1.default.
|
|
316
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
317
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
318
|
+
}
|
|
281
319
|
return true;
|
|
282
320
|
}
|
|
283
321
|
else if (hasERR_REQUIRE_ESM) {
|
|
284
322
|
console.log(' ❌ REGRESSION: Got ERR_REQUIRE_ESM error!');
|
|
285
|
-
fs_1.default.
|
|
323
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
324
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
325
|
+
}
|
|
286
326
|
return false;
|
|
287
327
|
}
|
|
288
328
|
else {
|
|
289
329
|
console.log(' ❌ Got unexpected error (exit code: ' + testResult.code + ')');
|
|
290
330
|
console.log(' ❌ Check the error output above');
|
|
291
|
-
fs_1.default.
|
|
331
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
332
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
333
|
+
}
|
|
292
334
|
return false;
|
|
293
335
|
}
|
|
294
336
|
}
|
|
295
337
|
catch (error) {
|
|
296
338
|
console.error(' ❌ Fresh install test failed with exception:', error);
|
|
297
339
|
console.error(' ❌ This is likely a test infrastructure issue, not a chalk issue');
|
|
340
|
+
if (tarballPath && fs_1.default.existsSync(tarballPath)) {
|
|
341
|
+
try {
|
|
342
|
+
fs_1.default.unlinkSync(tarballPath);
|
|
343
|
+
}
|
|
344
|
+
catch (e) {
|
|
345
|
+
// Ignore cleanup errors
|
|
346
|
+
}
|
|
347
|
+
}
|
|
298
348
|
return false;
|
|
299
349
|
}
|
|
300
350
|
finally {
|
package/dist/tests/test-cli.js
CHANGED
|
@@ -42,6 +42,64 @@ const node_assert_1 = __importDefault(require("node:assert"));
|
|
|
42
42
|
const fs_1 = __importDefault(require("fs"));
|
|
43
43
|
const path_1 = __importDefault(require("path"));
|
|
44
44
|
const os_1 = __importDefault(require("os"));
|
|
45
|
+
async function testRepoNameRequirement() {
|
|
46
|
+
console.log(' 🚀 Testing Repo Name Requirement...');
|
|
47
|
+
// Create a temp directory for the test project
|
|
48
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'fraim-repo-test-'));
|
|
49
|
+
console.log(` 📂 Created temp dir: ${tempDir}`);
|
|
50
|
+
try {
|
|
51
|
+
const platform = process.platform;
|
|
52
|
+
const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
53
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
54
|
+
// Helper to run CLI commands
|
|
55
|
+
const runFraim = (args, cwdOverride) => {
|
|
56
|
+
return new Promise((resolve) => {
|
|
57
|
+
const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
|
|
58
|
+
cwd: cwdOverride || tempDir,
|
|
59
|
+
env: { ...process.env, TEST_MODE: 'true' },
|
|
60
|
+
shell: true
|
|
61
|
+
});
|
|
62
|
+
let stdout = '';
|
|
63
|
+
let stderr = '';
|
|
64
|
+
ps.stdout.on('data', d => stdout += d.toString());
|
|
65
|
+
ps.stderr.on('data', d => stderr += d.toString());
|
|
66
|
+
ps.on('close', (code) => {
|
|
67
|
+
resolve({ stdout, stderr, code });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
// Test 1: Init without git should fail
|
|
72
|
+
console.log(' Testing init without git remote (should fail)...');
|
|
73
|
+
const initWithoutGit = await runFraim(['init'], tempDir);
|
|
74
|
+
node_assert_1.default.strictEqual(initWithoutGit.code, 1, 'Init without git remote should fail');
|
|
75
|
+
node_assert_1.default.ok(initWithoutGit.stdout.includes('No git remote found') || initWithoutGit.stderr.includes('No git remote found'), 'Should show git remote error');
|
|
76
|
+
// Test 2: Init with git remote should succeed and use repo name
|
|
77
|
+
console.log(' Testing init with git remote (should succeed)...');
|
|
78
|
+
const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
|
|
79
|
+
execSync('git init', { cwd: tempDir });
|
|
80
|
+
execSync('git remote add origin https://github.com/test-owner/my-awesome-project.git', { cwd: tempDir });
|
|
81
|
+
const initWithGit = await runFraim(['init'], tempDir);
|
|
82
|
+
node_assert_1.default.strictEqual(initWithGit.code, 0, 'Init with git remote should succeed');
|
|
83
|
+
const configPath = path_1.default.join(tempDir, '.fraim', 'config.json');
|
|
84
|
+
node_assert_1.default.ok(fs_1.default.existsSync(configPath), 'config.json should exist');
|
|
85
|
+
const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
|
|
86
|
+
node_assert_1.default.strictEqual(config.project.name, 'my-awesome-project', 'project.name should use repo name');
|
|
87
|
+
node_assert_1.default.strictEqual(config.git.repoName, 'my-awesome-project', 'git.repoName should match');
|
|
88
|
+
node_assert_1.default.strictEqual(config.git.repoOwner, 'test-owner', 'git.repoOwner should be detected');
|
|
89
|
+
console.log(' ✅ Repo name requirement verified!');
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error(' ❌ Repo name test failed:', error);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
try {
|
|
98
|
+
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
99
|
+
}
|
|
100
|
+
catch (e) { }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
45
103
|
async function testCliLifecycle() {
|
|
46
104
|
console.log(' 🚀 Testing Fraim CLI Lifecycle...');
|
|
47
105
|
// Create a temp directory for the test project
|
|
@@ -50,11 +108,11 @@ async function testCliLifecycle() {
|
|
|
50
108
|
try {
|
|
51
109
|
const platform = process.platform;
|
|
52
110
|
const npx = platform === 'win32' ? 'npx.cmd' : 'npx';
|
|
53
|
-
const cliScript =
|
|
111
|
+
const cliScript = (0, test_utils_1.resolveProjectPath)('dist/src/cli/fraim.js');
|
|
54
112
|
// Helper to run CLI commands
|
|
55
113
|
const runFraim = (args, cwdOverride) => {
|
|
56
|
-
return new Promise((resolve
|
|
57
|
-
const ps = (0, node_child_process_1.spawn)(npx, ['
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
const ps = (0, node_child_process_1.spawn)(npx, ['node', `"${cliScript}"`, ...args], {
|
|
58
116
|
cwd: cwdOverride || tempDir,
|
|
59
117
|
env: { ...process.env, TEST_MODE: 'true' }, // Disable interactive first-run
|
|
60
118
|
shell: true
|
|
@@ -102,11 +160,12 @@ async function testCliLifecycle() {
|
|
|
102
160
|
if (hasGit) {
|
|
103
161
|
node_assert_1.default.strictEqual(config.git.repoOwner, 'test-owner', 'repoOwner should be detected from git');
|
|
104
162
|
node_assert_1.default.strictEqual(config.git.repoName, 'test-repo', 'repoName should be detected from git');
|
|
163
|
+
node_assert_1.default.strictEqual(config.project.name, 'test-repo', 'project.name should use repo name, not folder name');
|
|
105
164
|
}
|
|
106
165
|
else {
|
|
107
166
|
console.warn(' ⚠️ No .git found in tempDir, owner/repo detection skipped');
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
// Without git remote, init should fail with our new logic
|
|
168
|
+
console.warn(' ⚠️ Init should have failed without git remote in new logic');
|
|
110
169
|
}
|
|
111
170
|
node_assert_1.default.ok(config.customizations, 'config.customizations should exist');
|
|
112
171
|
node_assert_1.default.strictEqual(config.customizations.workflowsPath, '.fraim/workflows');
|
|
@@ -157,6 +216,12 @@ async function runCliTest(testCase) {
|
|
|
157
216
|
return await testCase.testFunction();
|
|
158
217
|
}
|
|
159
218
|
const testCases = [
|
|
219
|
+
{
|
|
220
|
+
name: 'Repo Name Requirement',
|
|
221
|
+
description: 'Tests that init requires git remote and uses repo name for project.name',
|
|
222
|
+
testFunction: testRepoNameRequirement,
|
|
223
|
+
tags: ['cli', 'fraim', 'git']
|
|
224
|
+
},
|
|
160
225
|
{
|
|
161
226
|
name: 'Fraim CLI Lifecycle',
|
|
162
227
|
description: 'Tests init, sync, list, and doctor commands',
|