fraim-framework 2.0.33 → 2.0.35
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 +8 -0
- package/dist/src/cli/commands/sync.js +73 -78
- package/dist/src/utils/script-sync-utils.js +79 -81
- package/dist/tests/test-end-to-end-hybrid-validation.js +15 -36
- package/dist/tests/test-hybrid-script-execution.js +32 -61
- package/dist/tests/test-markdown-to-pdf.js +454 -0
- package/dist/tests/test-prep-issue.js +34 -1
- package/package.json +4 -1
- package/registry/scripts/markdown-to-pdf.js +391 -0
- package/registry/scripts/prep-issue.sh +61 -30
- package/registry/templates/marketing/STORYTELLING-TEMPLATE.md +130 -0
- package/registry/workflows/convert-to-pdf.md +235 -0
- package/registry/workflows/marketing/storytelling.md +65 -0
package/bin/fraim.js
ADDED
|
@@ -60,97 +60,92 @@ const runSync = async (options) => {
|
|
|
60
60
|
const existingDigest = fs_1.default.existsSync(digestPath) ? fs_1.default.readFileSync(digestPath, 'utf8') : '';
|
|
61
61
|
if (currentDigest === existingDigest && !options.force) {
|
|
62
62
|
console.log(chalk_1.default.green('✅ Workflows are already in sync.'));
|
|
63
|
-
return;
|
|
64
63
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
// Get all workflows from registry (recursive)
|
|
71
|
-
const getFiles = (dir) => {
|
|
72
|
-
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
73
|
-
const files = entries
|
|
74
|
-
.filter(e => !e.isDirectory() && e.name.endsWith('.md'))
|
|
75
|
-
.map(e => path_1.default.join(dir, e.name));
|
|
76
|
-
const folders = entries.filter(e => e.isDirectory());
|
|
77
|
-
for (const folder of folders) {
|
|
78
|
-
files.push(...getFiles(path_1.default.join(dir, folder.name)));
|
|
79
|
-
}
|
|
80
|
-
return files;
|
|
81
|
-
};
|
|
82
|
-
const registryFiles = getFiles(registryWorkflowsPath);
|
|
83
|
-
const generatedStubs = [];
|
|
84
|
-
for (const file of registryFiles) {
|
|
85
|
-
const content = fs_1.default.readFileSync(file, 'utf8');
|
|
86
|
-
const { intent, principles } = (0, stub_generator_1.parseRegistryWorkflow)(content);
|
|
87
|
-
const fileName = path_1.default.basename(file);
|
|
88
|
-
const workflowName = fileName.replace('.md', '');
|
|
89
|
-
// Calculate relative path from registry/workflows to preserve structure
|
|
90
|
-
const relativePath = path_1.default.relative(registryWorkflowsPath, file);
|
|
91
|
-
const relativeDir = path_1.default.dirname(relativePath);
|
|
92
|
-
// Ensure target directory exists
|
|
93
|
-
const targetDir = path_1.default.join(workflowsDir, relativeDir);
|
|
94
|
-
if (!fs_1.default.existsSync(targetDir)) {
|
|
95
|
-
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
96
|
-
}
|
|
97
|
-
const stubContent = (0, stub_generator_1.generateWorkflowStub)(workflowName, file, intent, principles);
|
|
98
|
-
const stubPath = path_1.default.join(targetDir, fileName);
|
|
99
|
-
fs_1.default.writeFileSync(stubPath, stubContent);
|
|
100
|
-
generatedStubs.push(relativePath); // Store relative path for cleanup tracking
|
|
101
|
-
console.log(chalk_1.default.gray(` + ${workflowName} (${relativeDir === '.' ? 'root' : relativeDir})`));
|
|
102
|
-
}
|
|
103
|
-
// Cleanup stubs that no longer exist in registry
|
|
104
|
-
// Cleanup stubs that no longer exist in registry
|
|
105
|
-
// Helper to get all local stubs recursively
|
|
106
|
-
const getLocalStubs = (dir) => {
|
|
107
|
-
if (!fs_1.default.existsSync(dir))
|
|
108
|
-
return [];
|
|
109
|
-
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
110
|
-
const files = entries
|
|
111
|
-
.filter(e => !e.isDirectory() && e.name.endsWith('.md'))
|
|
112
|
-
.map(e => path_1.default.relative(workflowsDir, path_1.default.join(dir, e.name)));
|
|
113
|
-
const folders = entries.filter(e => e.isDirectory());
|
|
114
|
-
for (const folder of folders) {
|
|
115
|
-
files.push(...getLocalStubs(path_1.default.join(dir, folder.name)));
|
|
64
|
+
else {
|
|
65
|
+
const registryWorkflowsPath = path_1.default.join(registryPath, 'workflows');
|
|
66
|
+
if (!fs_1.default.existsSync(registryWorkflowsPath)) {
|
|
67
|
+
console.log(chalk_1.default.yellow('⚠️ No workflows found in registry.'));
|
|
116
68
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
69
|
+
else {
|
|
70
|
+
// Get all workflows from registry (recursive)
|
|
71
|
+
const getFiles = (dir) => {
|
|
72
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
73
|
+
const files = entries
|
|
74
|
+
.filter(e => !e.isDirectory() && e.name.endsWith('.md'))
|
|
75
|
+
.map(e => path_1.default.join(dir, e.name));
|
|
76
|
+
const folders = entries.filter(e => e.isDirectory());
|
|
77
|
+
for (const folder of folders) {
|
|
78
|
+
files.push(...getFiles(path_1.default.join(dir, folder.name)));
|
|
79
|
+
}
|
|
80
|
+
return files;
|
|
81
|
+
};
|
|
82
|
+
const registryFiles = getFiles(registryWorkflowsPath);
|
|
83
|
+
const generatedStubs = [];
|
|
84
|
+
for (const file of registryFiles) {
|
|
85
|
+
const content = fs_1.default.readFileSync(file, 'utf8');
|
|
86
|
+
const { intent, principles } = (0, stub_generator_1.parseRegistryWorkflow)(content);
|
|
87
|
+
const fileName = path_1.default.basename(file);
|
|
88
|
+
const workflowName = fileName.replace('.md', '');
|
|
89
|
+
// Calculate relative path from registry/workflows to preserve structure
|
|
90
|
+
const relativePath = path_1.default.relative(registryWorkflowsPath, file);
|
|
91
|
+
const relativeDir = path_1.default.dirname(relativePath);
|
|
92
|
+
// Ensure target directory exists
|
|
93
|
+
const targetDir = path_1.default.join(workflowsDir, relativeDir);
|
|
94
|
+
if (!fs_1.default.existsSync(targetDir)) {
|
|
95
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
132
96
|
}
|
|
97
|
+
const stubContent = (0, stub_generator_1.generateWorkflowStub)(workflowName, file, intent, principles);
|
|
98
|
+
const stubPath = path_1.default.join(targetDir, fileName);
|
|
99
|
+
fs_1.default.writeFileSync(stubPath, stubContent);
|
|
100
|
+
generatedStubs.push(relativePath); // Store relative path for cleanup tracking
|
|
101
|
+
console.log(chalk_1.default.gray(` + ${workflowName} (${relativeDir === '.' ? 'root' : relativeDir})`));
|
|
133
102
|
}
|
|
134
|
-
|
|
103
|
+
// Cleanup stubs that no longer exist in registry
|
|
104
|
+
// Helper to get all local stubs recursively
|
|
105
|
+
const getLocalStubs = (dir) => {
|
|
106
|
+
if (!fs_1.default.existsSync(dir))
|
|
107
|
+
return [];
|
|
108
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
109
|
+
const files = entries
|
|
110
|
+
.filter(e => !e.isDirectory() && e.name.endsWith('.md'))
|
|
111
|
+
.map(e => path_1.default.relative(workflowsDir, path_1.default.join(dir, e.name)));
|
|
112
|
+
const folders = entries.filter(e => e.isDirectory());
|
|
113
|
+
for (const folder of folders) {
|
|
114
|
+
files.push(...getLocalStubs(path_1.default.join(dir, folder.name)));
|
|
115
|
+
}
|
|
116
|
+
return files;
|
|
117
|
+
};
|
|
118
|
+
const localStubs = getLocalStubs(workflowsDir);
|
|
119
|
+
for (const stub of localStubs) {
|
|
120
|
+
// standardise path separators for comparison
|
|
121
|
+
const normalizedStub = stub.replace(/\\/g, '/');
|
|
122
|
+
const normalizedGenerated = generatedStubs.map(s => s.replace(/\\/g, '/'));
|
|
123
|
+
if (!normalizedGenerated.includes(normalizedStub)) {
|
|
124
|
+
fs_1.default.unlinkSync(path_1.default.join(workflowsDir, stub));
|
|
125
|
+
console.log(chalk_1.default.yellow(` - ${stub} (removed from registry)`));
|
|
126
|
+
// Cleanup empty directories
|
|
127
|
+
try {
|
|
128
|
+
const dir = path_1.default.dirname(path_1.default.join(workflowsDir, stub));
|
|
129
|
+
if (fs_1.default.readdirSync(dir).length === 0) {
|
|
130
|
+
fs_1.default.rmdirSync(dir);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (e) { }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
fs_1.default.writeFileSync(digestPath, currentDigest);
|
|
137
|
+
console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Generated ${generatedStubs.length} stubs.`));
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
|
-
|
|
138
|
-
console.log(chalk_1.default.green(`\n✅ Workflow sync complete. Generated ${generatedStubs.length} stubs.`));
|
|
139
|
-
// Sync scripts to user directory
|
|
140
|
+
// Always sync scripts, regardless of workflow sync status
|
|
140
141
|
console.log(chalk_1.default.blue('\n🔄 Syncing FRAIM scripts to user directory...'));
|
|
141
142
|
const syncResult = (0, script_sync_utils_1.syncScriptsToUserDirectory)(registryPath);
|
|
142
143
|
const cleanedScriptCount = (0, script_sync_utils_1.cleanupObsoleteUserScripts)(registryPath);
|
|
143
144
|
if (syncResult.synced > 0 || cleanedScriptCount > 0) {
|
|
144
|
-
console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced}
|
|
145
|
-
if (syncResult.ephemeral > 0) {
|
|
146
|
-
console.log(chalk_1.default.gray(` ${syncResult.ephemeral} dependent scripts will use ephemeral execution.`));
|
|
147
|
-
}
|
|
145
|
+
console.log(chalk_1.default.green(`✅ Script sync complete. Updated ${syncResult.synced} scripts, removed ${cleanedScriptCount} obsolete scripts.`));
|
|
148
146
|
}
|
|
149
147
|
else {
|
|
150
148
|
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
149
|
}
|
|
155
150
|
};
|
|
156
151
|
exports.runSync = runSync;
|
|
@@ -6,14 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getUserFraimDir = getUserFraimDir;
|
|
7
7
|
exports.getUserScriptsDir = getUserScriptsDir;
|
|
8
8
|
exports.ensureUserFraimDirectories = ensureUserFraimDirectories;
|
|
9
|
-
exports.isScriptSelfContained = isScriptSelfContained;
|
|
10
9
|
exports.getRegistryScripts = getRegistryScripts;
|
|
11
10
|
exports.copyScriptToUserDirectory = copyScriptToUserDirectory;
|
|
12
11
|
exports.syncScriptsToUserDirectory = syncScriptsToUserDirectory;
|
|
13
12
|
exports.cleanupObsoleteUserScripts = cleanupObsoleteUserScripts;
|
|
14
13
|
exports.getUserScriptPath = getUserScriptPath;
|
|
15
14
|
exports.userScriptExists = userScriptExists;
|
|
16
|
-
exports.getDependentRegistryScripts = getDependentRegistryScripts;
|
|
17
15
|
exports.validateUserScripts = validateUserScripts;
|
|
18
16
|
const fs_1 = __importDefault(require("fs"));
|
|
19
17
|
const path_1 = __importDefault(require("path"));
|
|
@@ -46,60 +44,54 @@ function ensureUserFraimDirectories() {
|
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
46
|
/**
|
|
49
|
-
*
|
|
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
|
|
47
|
+
* Get all script files from the registry (recursively)
|
|
75
48
|
*/
|
|
76
49
|
function getRegistryScripts(registryPath) {
|
|
77
50
|
const scriptsPath = path_1.default.join(registryPath, 'scripts');
|
|
78
51
|
if (!fs_1.default.existsSync(scriptsPath)) {
|
|
79
52
|
return [];
|
|
80
53
|
}
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
54
|
+
const getAllFiles = (dir, baseDir = dir) => {
|
|
55
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
56
|
+
let files = [];
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
// Recursively get files from subdirectories
|
|
61
|
+
files = files.concat(getAllFiles(fullPath, baseDir));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Include all files (not just specific extensions)
|
|
65
|
+
files.push(fullPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return files;
|
|
69
|
+
};
|
|
70
|
+
return getAllFiles(scriptsPath);
|
|
87
71
|
}
|
|
88
72
|
/**
|
|
89
73
|
* Copy a script file to the user scripts directory with proper permissions
|
|
74
|
+
* Preserves directory structure from registry/scripts/
|
|
90
75
|
*/
|
|
91
|
-
function copyScriptToUserDirectory(sourcePath,
|
|
76
|
+
function copyScriptToUserDirectory(sourcePath, registryScriptsPath) {
|
|
92
77
|
const userScriptsDir = getUserScriptsDir();
|
|
93
|
-
|
|
78
|
+
// Calculate relative path from registry/scripts to preserve structure
|
|
79
|
+
const relativePath = path_1.default.relative(registryScriptsPath, sourcePath);
|
|
80
|
+
const targetPath = path_1.default.join(userScriptsDir, relativePath);
|
|
81
|
+
// Ensure target directory exists
|
|
82
|
+
const targetDir = path_1.default.dirname(targetPath);
|
|
83
|
+
if (!fs_1.default.existsSync(targetDir)) {
|
|
84
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
85
|
+
}
|
|
94
86
|
// Copy the file
|
|
95
87
|
fs_1.default.copyFileSync(sourcePath, targetPath);
|
|
96
|
-
// Set executable permissions on Unix systems
|
|
97
|
-
if (process.platform !== 'win32' &&
|
|
88
|
+
// Set executable permissions on Unix systems for shell scripts
|
|
89
|
+
if (process.platform !== 'win32' && sourcePath.endsWith('.sh')) {
|
|
98
90
|
try {
|
|
99
91
|
fs_1.default.chmodSync(targetPath, 0o755);
|
|
100
92
|
}
|
|
101
93
|
catch (error) {
|
|
102
|
-
console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${
|
|
94
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not set executable permissions for ${relativePath}`));
|
|
103
95
|
}
|
|
104
96
|
}
|
|
105
97
|
}
|
|
@@ -108,13 +100,13 @@ function copyScriptToUserDirectory(sourcePath, scriptName) {
|
|
|
108
100
|
*/
|
|
109
101
|
function syncScriptsToUserDirectory(registryPath) {
|
|
110
102
|
ensureUserFraimDirectories();
|
|
111
|
-
const
|
|
112
|
-
const
|
|
103
|
+
const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
|
|
104
|
+
const allScripts = getRegistryScripts(registryPath);
|
|
113
105
|
let syncedCount = 0;
|
|
114
|
-
console.log(chalk_1.default.gray(`
|
|
115
|
-
for (const scriptPath of
|
|
116
|
-
const
|
|
117
|
-
const userScriptPath = path_1.default.join(getUserScriptsDir(),
|
|
106
|
+
console.log(chalk_1.default.gray(` Syncing all scripts to ~/.fraim/scripts/:`));
|
|
107
|
+
for (const scriptPath of allScripts) {
|
|
108
|
+
const relativePath = path_1.default.relative(registryScriptsPath, scriptPath);
|
|
109
|
+
const userScriptPath = path_1.default.join(getUserScriptsDir(), relativePath);
|
|
118
110
|
try {
|
|
119
111
|
// Check if script needs updating
|
|
120
112
|
let needsUpdate = true;
|
|
@@ -124,26 +116,19 @@ function syncScriptsToUserDirectory(registryPath) {
|
|
|
124
116
|
needsUpdate = registryContent !== userContent;
|
|
125
117
|
}
|
|
126
118
|
if (needsUpdate) {
|
|
127
|
-
copyScriptToUserDirectory(scriptPath,
|
|
119
|
+
copyScriptToUserDirectory(scriptPath, registryScriptsPath);
|
|
128
120
|
syncedCount++;
|
|
129
|
-
console.log(chalk_1.default.gray(` + ${
|
|
121
|
+
console.log(chalk_1.default.gray(` + ${relativePath}`));
|
|
130
122
|
}
|
|
131
123
|
else {
|
|
132
|
-
console.log(chalk_1.default.gray(` = ${
|
|
124
|
+
console.log(chalk_1.default.gray(` = ${relativePath} (up to date)`));
|
|
133
125
|
}
|
|
134
126
|
}
|
|
135
127
|
catch (error) {
|
|
136
|
-
console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${
|
|
128
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not sync script ${relativePath}: ${error}`));
|
|
137
129
|
}
|
|
138
130
|
}
|
|
139
|
-
|
|
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 };
|
|
131
|
+
return { synced: syncedCount, ephemeral: 0 };
|
|
147
132
|
}
|
|
148
133
|
/**
|
|
149
134
|
* Clean up scripts in user directory that no longer exist in registry
|
|
@@ -153,21 +138,49 @@ function cleanupObsoleteUserScripts(registryPath) {
|
|
|
153
138
|
if (!fs_1.default.existsSync(userScriptsDir)) {
|
|
154
139
|
return 0;
|
|
155
140
|
}
|
|
141
|
+
const registryScriptsPath = path_1.default.join(registryPath, 'scripts');
|
|
156
142
|
const registryScripts = getRegistryScripts(registryPath);
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
143
|
+
const registryRelativePaths = registryScripts.map(scriptPath => path_1.default.relative(registryScriptsPath, scriptPath));
|
|
144
|
+
// Get all files in user scripts directory recursively
|
|
145
|
+
const getAllUserFiles = (dir, baseDir = dir) => {
|
|
146
|
+
if (!fs_1.default.existsSync(dir))
|
|
147
|
+
return [];
|
|
148
|
+
const entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
|
|
149
|
+
let files = [];
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
const fullPath = path_1.default.join(dir, entry.name);
|
|
152
|
+
if (entry.isDirectory()) {
|
|
153
|
+
files = files.concat(getAllUserFiles(fullPath, baseDir));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
files.push(path_1.default.relative(baseDir, fullPath));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return files;
|
|
160
|
+
};
|
|
161
|
+
const userFiles = getAllUserFiles(userScriptsDir);
|
|
160
162
|
let cleanedCount = 0;
|
|
161
|
-
for (const
|
|
162
|
-
|
|
163
|
+
for (const userFile of userFiles) {
|
|
164
|
+
// Normalize path separators for comparison
|
|
165
|
+
const normalizedUserFile = userFile.replace(/\\/g, '/');
|
|
166
|
+
const normalizedRegistry = registryRelativePaths.map(p => p.replace(/\\/g, '/'));
|
|
167
|
+
if (!normalizedRegistry.includes(normalizedUserFile)) {
|
|
163
168
|
try {
|
|
164
|
-
const
|
|
165
|
-
fs_1.default.unlinkSync(
|
|
169
|
+
const userFilePath = path_1.default.join(userScriptsDir, userFile);
|
|
170
|
+
fs_1.default.unlinkSync(userFilePath);
|
|
166
171
|
cleanedCount++;
|
|
167
|
-
console.log(chalk_1.default.yellow(` - ${
|
|
172
|
+
console.log(chalk_1.default.yellow(` - ${userFile} (removed from registry)`));
|
|
173
|
+
// Cleanup empty directories
|
|
174
|
+
try {
|
|
175
|
+
const dir = path_1.default.dirname(userFilePath);
|
|
176
|
+
if (fs_1.default.existsSync(dir) && fs_1.default.readdirSync(dir).length === 0) {
|
|
177
|
+
fs_1.default.rmdirSync(dir);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
catch (e) { }
|
|
168
181
|
}
|
|
169
182
|
catch (error) {
|
|
170
|
-
console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${
|
|
183
|
+
console.warn(chalk_1.default.yellow(`⚠️ Could not remove obsolete script ${userFile}: ${error}`));
|
|
171
184
|
}
|
|
172
185
|
}
|
|
173
186
|
}
|
|
@@ -185,21 +198,6 @@ function getUserScriptPath(scriptName) {
|
|
|
185
198
|
function userScriptExists(scriptName) {
|
|
186
199
|
return fs_1.default.existsSync(getUserScriptPath(scriptName));
|
|
187
200
|
}
|
|
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
201
|
/**
|
|
204
202
|
* Validate that all expected scripts are present in user directory
|
|
205
203
|
*/
|
|
@@ -187,52 +187,31 @@ async function testScriptCategorizationAccuracy() {
|
|
|
187
187
|
try {
|
|
188
188
|
const registryPath = path_1.default.resolve(__dirname, '../registry');
|
|
189
189
|
if (!fs_1.default.existsSync(registryPath)) {
|
|
190
|
-
console.log(' ⚠️ Registry not found, skipping
|
|
190
|
+
console.log(' ⚠️ Registry not found, skipping script sync test');
|
|
191
191
|
return true;
|
|
192
192
|
}
|
|
193
|
-
const { getRegistryScripts
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
console.log(`
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
// Verify specific known categorizations
|
|
200
|
-
const knownSelfContained = [
|
|
193
|
+
const { getRegistryScripts } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
194
|
+
const allScripts = getRegistryScripts(registryPath);
|
|
195
|
+
console.log(` 📊 Script sync results:`);
|
|
196
|
+
console.log(` Total scripts: ${allScripts.length} scripts`);
|
|
197
|
+
// Verify some known scripts exist
|
|
198
|
+
const knownScripts = [
|
|
201
199
|
'prep-issue.sh',
|
|
202
200
|
'code-quality-check.sh',
|
|
203
201
|
'exec-with-timeout.ts',
|
|
204
|
-
'evaluate-code-quality.ts'
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
'
|
|
208
|
-
'build-scripts-generator.ts'
|
|
202
|
+
'evaluate-code-quality.ts',
|
|
203
|
+
'comprehensive-explorer.py',
|
|
204
|
+
'interactive-explorer.py',
|
|
205
|
+
'scrape-site.py'
|
|
209
206
|
];
|
|
210
|
-
const
|
|
211
|
-
const
|
|
212
|
-
for (const scriptName of knownSelfContained) {
|
|
213
|
-
const scriptPath = path_1.default.join(registryPath, 'scripts', scriptName);
|
|
214
|
-
if (fs_1.default.existsSync(scriptPath)) {
|
|
215
|
-
node_assert_1.default.ok(selfContainedNames.includes(scriptName), `${scriptName} should be categorized as self-contained`);
|
|
216
|
-
node_assert_1.default.ok(isScriptSelfContained(scriptPath), `${scriptName} should be detected as self-contained`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
for (const scriptName of knownDependent) {
|
|
207
|
+
const scriptNames = allScripts.map(p => path_1.default.basename(p));
|
|
208
|
+
for (const scriptName of knownScripts) {
|
|
220
209
|
const scriptPath = path_1.default.join(registryPath, 'scripts', scriptName);
|
|
221
210
|
if (fs_1.default.existsSync(scriptPath)) {
|
|
222
|
-
node_assert_1.default.ok(
|
|
223
|
-
node_assert_1.default.ok(!isScriptSelfContained(scriptPath), `${scriptName} should be detected as dependent`);
|
|
211
|
+
node_assert_1.default.ok(scriptNames.includes(scriptName), `${scriptName} should be included in sync`);
|
|
224
212
|
}
|
|
225
213
|
}
|
|
226
|
-
|
|
227
|
-
const intersection = selfContainedNames.filter(name => dependentNames.includes(name));
|
|
228
|
-
node_assert_1.default.strictEqual(intersection.length, 0, 'No script should be in both categories');
|
|
229
|
-
// Verify all scripts are categorized
|
|
230
|
-
const allScripts = fs_1.default.readdirSync(path_1.default.join(registryPath, 'scripts'))
|
|
231
|
-
.filter(file => file.endsWith('.sh') || file.endsWith('.ts') || file.endsWith('.js'));
|
|
232
|
-
const categorizedScripts = [...selfContainedNames, ...dependentNames];
|
|
233
|
-
const uncategorized = allScripts.filter(script => !categorizedScripts.includes(script));
|
|
234
|
-
node_assert_1.default.strictEqual(uncategorized.length, 0, `All scripts should be categorized. Uncategorized: ${uncategorized.join(', ')}`);
|
|
235
|
-
console.log(' ✅ Script categorization accuracy verified!');
|
|
214
|
+
console.log(' ✅ All known scripts are included');
|
|
236
215
|
return true;
|
|
237
216
|
}
|
|
238
217
|
catch (error) {
|
|
@@ -77,24 +77,15 @@ const helper = require('../../src/utils/helper');
|
|
|
77
77
|
console.log('This script also depends on FRAIM source code');
|
|
78
78
|
`;
|
|
79
79
|
fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'dependent.js'), dependentJS);
|
|
80
|
-
// Test the
|
|
81
|
-
const {
|
|
82
|
-
// Test
|
|
83
|
-
|
|
84
|
-
node_assert_1.default.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const dependentScripts = getDependentRegistryScripts(tempDir);
|
|
90
|
-
node_assert_1.default.strictEqual(selfContainedScripts.length, 2, 'Should find 2 self-contained scripts');
|
|
91
|
-
node_assert_1.default.strictEqual(dependentScripts.length, 2, 'Should find 2 dependent scripts');
|
|
92
|
-
// Verify correct categorization
|
|
93
|
-
const selfContainedNames = selfContainedScripts.map(p => path_1.default.basename(p)).sort();
|
|
94
|
-
const dependentNames = dependentScripts.map(p => path_1.default.basename(p)).sort();
|
|
95
|
-
node_assert_1.default.deepStrictEqual(selfContainedNames, ['self-contained.sh', 'self-contained.ts'], 'Self-contained scripts correctly identified');
|
|
96
|
-
node_assert_1.default.deepStrictEqual(dependentNames, ['dependent.js', 'dependent.ts'], 'Dependent scripts correctly identified');
|
|
97
|
-
console.log(' ✅ Self-contained script detection verified!');
|
|
80
|
+
// Test the sync logic - all scripts should be synced now
|
|
81
|
+
const { getRegistryScripts } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
82
|
+
// Test batch detection - should get all scripts
|
|
83
|
+
const allScripts = getRegistryScripts(tempDir);
|
|
84
|
+
node_assert_1.default.strictEqual(allScripts.length, 4, 'Should find all 4 scripts');
|
|
85
|
+
// Verify all scripts are included
|
|
86
|
+
const scriptNames = allScripts.map(p => path_1.default.basename(p)).sort();
|
|
87
|
+
node_assert_1.default.deepStrictEqual(scriptNames, ['dependent.js', 'dependent.ts', 'self-contained.sh', 'self-contained.ts'], 'All scripts should be included');
|
|
88
|
+
console.log(' ✅ Script sync detection verified!');
|
|
98
89
|
return true;
|
|
99
90
|
}
|
|
100
91
|
catch (error) {
|
|
@@ -142,13 +133,13 @@ console.log('Dependent TypeScript script');
|
|
|
142
133
|
// Override user directory for testing
|
|
143
134
|
process.env.FRAIM_USER_DIR = userFraimDir;
|
|
144
135
|
const syncResult = syncScriptsToUserDirectory(registryDir);
|
|
145
|
-
// Verify sync results
|
|
146
|
-
node_assert_1.default.strictEqual(syncResult.synced,
|
|
147
|
-
node_assert_1.default.strictEqual(syncResult.ephemeral,
|
|
148
|
-
// Verify
|
|
136
|
+
// Verify sync results - now all scripts are synced
|
|
137
|
+
node_assert_1.default.strictEqual(syncResult.synced, 3, 'Should sync all 3 scripts');
|
|
138
|
+
node_assert_1.default.strictEqual(syncResult.ephemeral, 0, 'No ephemeral scripts (all are synced)');
|
|
139
|
+
// Verify all scripts were copied
|
|
149
140
|
node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'shell-script.sh')), 'Shell script should be synced');
|
|
150
141
|
node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'self-contained.ts')), 'Self-contained TS should be synced');
|
|
151
|
-
node_assert_1.default.ok(
|
|
142
|
+
node_assert_1.default.ok(fs_1.default.existsSync(path_1.default.join(userScriptsDir, 'dependent.ts')), 'Dependent TS should also be synced');
|
|
152
143
|
// Verify executable permissions on Unix
|
|
153
144
|
if (process.platform !== 'win32') {
|
|
154
145
|
const shellScriptPath = path_1.default.join(userScriptsDir, 'shell-script.sh');
|
|
@@ -181,52 +172,32 @@ async function testActualRegistryScriptCategorization() {
|
|
|
181
172
|
console.log(' ⚠️ Registry not found, skipping actual script categorization test');
|
|
182
173
|
return true;
|
|
183
174
|
}
|
|
184
|
-
const { getRegistryScripts
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
for (const script of selfContainedScripts) {
|
|
175
|
+
const { getRegistryScripts } = await Promise.resolve().then(() => __importStar(require('../src/utils/script-sync-utils')));
|
|
176
|
+
const allScripts = getRegistryScripts(registryPath);
|
|
177
|
+
console.log(` 📊 Found ${allScripts.length} scripts to sync:`);
|
|
178
|
+
for (const script of allScripts) {
|
|
189
179
|
console.log(` ✅ ${path_1.default.basename(script)}`);
|
|
190
180
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
//
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// Scripts with FRAIM imports should be dependent
|
|
201
|
-
const expectedDependentScripts = [
|
|
202
|
-
'fraim-config.ts',
|
|
203
|
-
'build-scripts-generator.ts'
|
|
204
|
-
];
|
|
205
|
-
for (const expectedDependent of expectedDependentScripts) {
|
|
206
|
-
if (fs_1.default.existsSync(path_1.default.join(registryPath, 'scripts', expectedDependent))) {
|
|
207
|
-
node_assert_1.default.ok(dependentNames.includes(expectedDependent), `${expectedDependent} should be dependent`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
// Scripts that should be self-contained
|
|
211
|
-
const expectedSelfContainedScripts = [
|
|
212
|
-
'prep-issue.sh',
|
|
213
|
-
'evaluate-code-quality.ts', // This only uses __dirname and standard modules
|
|
214
|
-
'code-quality-check.sh'
|
|
181
|
+
// Verify expected scripts are included
|
|
182
|
+
const scriptNames = allScripts.map(p => path_1.default.basename(p));
|
|
183
|
+
// prep-issue.sh should be included
|
|
184
|
+
node_assert_1.default.ok(scriptNames.includes('prep-issue.sh'), 'prep-issue.sh should be included');
|
|
185
|
+
// Python scripts should be included
|
|
186
|
+
const expectedPythonScripts = [
|
|
187
|
+
'comprehensive-explorer.py',
|
|
188
|
+
'interactive-explorer.py',
|
|
189
|
+
'scrape-site.py'
|
|
215
190
|
];
|
|
216
|
-
for (const
|
|
217
|
-
if (fs_1.default.existsSync(path_1.default.join(registryPath, 'scripts',
|
|
218
|
-
node_assert_1.default.ok(
|
|
191
|
+
for (const expectedScript of expectedPythonScripts) {
|
|
192
|
+
if (fs_1.default.existsSync(path_1.default.join(registryPath, 'scripts', expectedScript))) {
|
|
193
|
+
node_assert_1.default.ok(scriptNames.includes(expectedScript), `${expectedScript} should be included`);
|
|
219
194
|
}
|
|
220
195
|
}
|
|
221
|
-
|
|
222
|
-
const totalScripts = selfContainedScripts.length + dependentScripts.length;
|
|
223
|
-
node_assert_1.default.ok(totalScripts > 0, 'Should find some scripts in registry');
|
|
224
|
-
node_assert_1.default.ok(selfContainedScripts.length > 0, 'Should have some self-contained scripts');
|
|
225
|
-
console.log(' ✅ Actual registry script categorization verified!');
|
|
196
|
+
console.log(' ✅ Actual registry script sync verified!');
|
|
226
197
|
return true;
|
|
227
198
|
}
|
|
228
199
|
catch (error) {
|
|
229
|
-
console.error(' ❌ Registry script
|
|
200
|
+
console.error(' ❌ Registry script sync test failed:', error);
|
|
230
201
|
return false;
|
|
231
202
|
}
|
|
232
203
|
}
|