@c6fc/spellcraft 0.1.1 → 0.1.2
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/spellcraft.js +0 -21
- package/jsdoc.json +1 -2
- package/package.json +2 -1
- package/src/index.js +97 -7
package/bin/spellcraft.js
CHANGED
|
@@ -37,27 +37,6 @@ const spellframe = new SpellFrame();
|
|
|
37
37
|
* spellcraft generate ./myconfig.jsonnet
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
-
/**
|
|
41
|
-
* Links an npm package as a SpellCraft module for the current project.
|
|
42
|
-
* This command installs the specified npm package (if not already present) and
|
|
43
|
-
* registers it within the project's SpellCraft module configuration, making its
|
|
44
|
-
* functionalities available during the rendering process.
|
|
45
|
-
*
|
|
46
|
-
* **Usage:** `spellcraft importModule <npmPackage> [name]`
|
|
47
|
-
*
|
|
48
|
-
* @function importModule
|
|
49
|
-
* @name module:spellcraft-cli.importModule
|
|
50
|
-
* @param {object} argv - The arguments object provided by yargs.
|
|
51
|
-
* @param {string} argv.npmPackage The NPM package name of the SpellCraft Plugin to import. (Required)
|
|
52
|
-
* @param {string} [argv.name] An optional alias name to use for this module within SpellCraft.
|
|
53
|
-
* If not provided, a default name from the package may be used.
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* spellcraft importModule my-spellcraft-enhancer
|
|
57
|
-
* @example
|
|
58
|
-
* spellcraft importModule @my-scope/spellcraft-utils customUtils
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
40
|
// --- End of JSDoc Blocks for CLI Commands ---
|
|
62
41
|
|
|
63
42
|
(async () => {
|
package/jsdoc.json
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
+
"@colors/colors": "^1.6.0",
|
|
3
4
|
"@hanazuki/node-jsonnet": "^0.4.2",
|
|
4
5
|
"js-yaml": "^4.1.0",
|
|
5
6
|
"yargs": "^17.2.1"
|
|
6
7
|
},
|
|
7
8
|
"name": "@c6fc/spellcraft",
|
|
8
9
|
"description": "Extensible JSonnet CLI platform",
|
|
9
|
-
"version": "0.1.
|
|
10
|
+
"version": "0.1.2",
|
|
10
11
|
"main": "src/index.js",
|
|
11
12
|
"directories": {
|
|
12
13
|
"lib": "lib"
|
package/src/index.js
CHANGED
|
@@ -64,7 +64,8 @@ exports.SpellFrame = class SpellFrame {
|
|
|
64
64
|
this.jsonnet = new Jsonnet()
|
|
65
65
|
.addJpath(path.join(__dirname, '../lib'))
|
|
66
66
|
// REFACTOR: Look in the local project's node_modules for explicit imports
|
|
67
|
-
.addJpath(path.join(baseDir, 'node_modules'))
|
|
67
|
+
.addJpath(path.join(baseDir, 'node_modules'))
|
|
68
|
+
.addJpath(path.join(baseDir, '.spellcraft'));
|
|
68
69
|
|
|
69
70
|
// Built-in native functions
|
|
70
71
|
this.addNativeFunction("envvar", (name) => process.env[name] || false, "name");
|
|
@@ -72,6 +73,9 @@ exports.SpellFrame = class SpellFrame {
|
|
|
72
73
|
|
|
73
74
|
// REFACTOR: Automatically find and register plugins from package.json
|
|
74
75
|
this.loadPluginsFromDependencies();
|
|
76
|
+
|
|
77
|
+
// 2. Load Local Magic Modules (Rapid Prototyping Mode)
|
|
78
|
+
this.loadLocalMagicModules();
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
_generateCacheKey(functionName, args) {
|
|
@@ -144,23 +148,109 @@ exports.SpellFrame = class SpellFrame {
|
|
|
144
148
|
} catch (e) { return; }
|
|
145
149
|
|
|
146
150
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
151
|
+
|
|
152
|
+
// Create a require function that operates as if it's inside the user's project
|
|
153
|
+
const userProjectRequire = require('module').createRequire(packageJsonPath);
|
|
147
154
|
|
|
148
155
|
Object.keys(deps).forEach(depName => {
|
|
149
156
|
try {
|
|
150
|
-
//
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
157
|
+
// 1. Find the path to the dependency's package.json using the USER'S context
|
|
158
|
+
const depPackageJsonPath = userProjectRequire.resolve(`${depName}/package.json`);
|
|
159
|
+
|
|
160
|
+
// 2. Load that package.json using the absolute path
|
|
161
|
+
const depPkg = require(depPackageJsonPath);
|
|
162
|
+
const depDir = path.dirname(depPackageJsonPath);
|
|
163
|
+
|
|
164
|
+
// 3. Check for SpellCraft metadata
|
|
155
165
|
if (depPkg.spellcraft || depPkg.keywords?.includes("spellcraft-module")) {
|
|
156
|
-
|
|
166
|
+
const jsMainPath = path.join(depDir, depPkg.main || 'index.js');
|
|
167
|
+
|
|
168
|
+
// 4. Load the plugin using the calculated absolute path
|
|
169
|
+
this.loadPlugin(depName, jsMainPath);
|
|
157
170
|
}
|
|
158
171
|
} catch (e) {
|
|
159
172
|
// Dependency might not be installed or resolvable, skip quietly
|
|
173
|
+
console.warn(`Debug: Could not load potential plugin ${depName}: ${e.message}`);
|
|
160
174
|
}
|
|
161
175
|
});
|
|
162
176
|
}
|
|
163
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Scans the local 'spellcraft_modules' directory.
|
|
180
|
+
* 1. Registers JS exports as native functions (prefixed with 'local_<filename>_').
|
|
181
|
+
* 2. Generates a .spellcraft/modules.libsonnet file to allow `import 'modules'`.
|
|
182
|
+
*/
|
|
183
|
+
loadLocalMagicModules() {
|
|
184
|
+
const localModulesDir = path.join(baseDir, 'spellcraft_modules');
|
|
185
|
+
const generatedDir = path.join(baseDir, '.spellcraft');
|
|
186
|
+
const aggregateFile = path.join(generatedDir, 'modules');
|
|
187
|
+
|
|
188
|
+
if (!fs.existsSync(localModulesDir)) {
|
|
189
|
+
// Clean up if it exists so imports fail gracefully if folder is deleted
|
|
190
|
+
if(fs.existsSync(aggregateFile)) fs.unlinkSync(aggregateFile);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Ensure hidden directory exists
|
|
195
|
+
if (!fs.existsSync(generatedDir)) fs.mkdirSync(generatedDir, { recursive: true });
|
|
196
|
+
|
|
197
|
+
const jsFiles = fs.readdirSync(localModulesDir).filter(f => f.endsWith('.js'));
|
|
198
|
+
|
|
199
|
+
let jsonnetContentParts = [];
|
|
200
|
+
|
|
201
|
+
jsFiles.forEach(file => {
|
|
202
|
+
const moduleName = path.basename(file, '.js');
|
|
203
|
+
const fullPath = path.join(localModulesDir, file);
|
|
204
|
+
|
|
205
|
+
let moduleExports;
|
|
206
|
+
try {
|
|
207
|
+
// Cache busting for dev speed
|
|
208
|
+
delete require.cache[require.resolve(fullPath)];
|
|
209
|
+
moduleExports = require(fullPath);
|
|
210
|
+
} catch (e) {
|
|
211
|
+
console.warn(`[!] Error loading local module ${file}: ${e.message}`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let fileMethods = [];
|
|
216
|
+
|
|
217
|
+
Object.keys(moduleExports).forEach(funcName => {
|
|
218
|
+
if (funcName === '_spellcraft_metadata') return; // Skip metadata
|
|
219
|
+
|
|
220
|
+
let func, params;
|
|
221
|
+
// Handle [func, "arg1", "arg2"] syntax or plain function
|
|
222
|
+
if (Array.isArray(moduleExports[funcName])) {
|
|
223
|
+
[func, ...params] = moduleExports[funcName];
|
|
224
|
+
} else if (typeof moduleExports[funcName] === 'function') {
|
|
225
|
+
func = moduleExports[funcName];
|
|
226
|
+
// You'll need the getFunctionParameterList helper from before
|
|
227
|
+
params = getFunctionParameterList(func);
|
|
228
|
+
} else {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Register with a unique local prefix
|
|
233
|
+
const uniqueId = `local_${moduleName}_${funcName}`;
|
|
234
|
+
this.addNativeFunction(uniqueId, func, ...params);
|
|
235
|
+
|
|
236
|
+
// Create the Jsonnet wrapper string
|
|
237
|
+
// e.g. myFunc(a, b):: std.native("local_utils_myFunc")(a, b)
|
|
238
|
+
const paramStr = params.join(", ");
|
|
239
|
+
fileMethods.push(` ${funcName}(${paramStr}):: std.native("${uniqueId}")(${paramStr})`);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log(`[+] Loaded [${Object.keys(moduleExports).join(", ")}] from [${file}].`);
|
|
243
|
+
|
|
244
|
+
if (fileMethods.length > 0) {
|
|
245
|
+
jsonnetContentParts.push(` ${moduleName}: {\n${fileMethods.join(",\n")}\n }`);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Generate the file
|
|
250
|
+
const finalContent = "{\n" + jsonnetContentParts.join(",\n") + "\n}";
|
|
251
|
+
fs.writeFileSync(aggregateFile, finalContent, 'utf-8');
|
|
252
|
+
}
|
|
253
|
+
|
|
164
254
|
/**
|
|
165
255
|
* REFACTOR: Loads a specific plugin JS file.
|
|
166
256
|
* Namespaces native functions using the package name to prevent collisions.
|