@c6fc/spellcraft 0.0.10 → 0.1.1
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 -18
- package/package.json +2 -4
- package/src/index.js +146 -325
- package/src/test.js +2 -2
package/bin/spellcraft.js
CHANGED
|
@@ -100,24 +100,6 @@ const spellframe = new SpellFrame();
|
|
|
100
100
|
}*/
|
|
101
101
|
})
|
|
102
102
|
|
|
103
|
-
.command("importModule <npmPackage> [name]", "Configures the current project to use a SpellCraft plugin as an import", (yargsInstance) => {
|
|
104
|
-
return yargsInstance
|
|
105
|
-
.positional('npmPackage', {
|
|
106
|
-
describe: 'The NPM package name of a SpellCraft Plugin to import',
|
|
107
|
-
type: 'string',
|
|
108
|
-
demandOption: true,
|
|
109
|
-
})
|
|
110
|
-
.positional('name', {
|
|
111
|
-
describe: 'Optional alias name for the module in SpellCraft',
|
|
112
|
-
type: 'string',
|
|
113
|
-
default: undefined,
|
|
114
|
-
});
|
|
115
|
-
},
|
|
116
|
-
async (argv) => {
|
|
117
|
-
await sfInstance.importSpellCraftModuleFromNpm(argv.npmPackage, argv.name);
|
|
118
|
-
console.log(`[+] Module '${argv.npmPackage.green}' ${argv.name ? `(aliased as ${argv.name.green}) ` : ''}linked successfully.`);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
103
|
// No JSDoc for CLI extensions loop if considered internal detail
|
|
122
104
|
if (sfInstance.cliExtensions && sfInstance.cliExtensions.length > 0) {
|
|
123
105
|
sfInstance.cliExtensions.forEach((extensionFn) => {
|
package/package.json
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@colors/colors": "^1.6.0",
|
|
4
3
|
"@hanazuki/node-jsonnet": "^0.4.2",
|
|
5
|
-
"ini": "^5.0.0",
|
|
6
4
|
"js-yaml": "^4.1.0",
|
|
7
5
|
"yargs": "^17.2.1"
|
|
8
6
|
},
|
|
9
7
|
"name": "@c6fc/spellcraft",
|
|
10
8
|
"description": "Extensible JSonnet CLI platform",
|
|
11
|
-
"version": "0.
|
|
9
|
+
"version": "0.1.1",
|
|
12
10
|
"main": "src/index.js",
|
|
13
11
|
"directories": {
|
|
14
12
|
"lib": "lib"
|
|
@@ -17,7 +15,7 @@
|
|
|
17
15
|
"spellcraft": "bin/spellcraft.js"
|
|
18
16
|
},
|
|
19
17
|
"scripts": {
|
|
20
|
-
"test": "
|
|
18
|
+
"test": "node src/test.js",
|
|
21
19
|
"doc": "jsdoc -c jsdoc.json --verbose"
|
|
22
20
|
},
|
|
23
21
|
"repository": {
|
package/src/index.js
CHANGED
|
@@ -4,79 +4,74 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const yaml = require('js-yaml');
|
|
6
6
|
const crypto = require('crypto');
|
|
7
|
-
const colors = require('@colors/colors');
|
|
8
|
-
const { spawnSync } = require('child_process');
|
|
9
7
|
const { Jsonnet } = require("@hanazuki/node-jsonnet");
|
|
10
8
|
|
|
9
|
+
const baseDir = process.cwd();
|
|
10
|
+
|
|
11
11
|
const defaultFileTypeHandlers = {
|
|
12
|
-
// JSON files
|
|
13
12
|
'.*?\.json$': (content) => JSON.stringify(content, null, 4),
|
|
14
|
-
// YAML files
|
|
15
13
|
'.*?\.yaml$': (content) => yaml.dump(content, { indent: 4 }),
|
|
16
14
|
'.*?\.yml$': (content) => yaml.dump(content, { indent: 4 }),
|
|
17
15
|
};
|
|
18
16
|
|
|
19
17
|
const defaultFileHandler = (content) => JSON.stringify(content, null, 4);
|
|
20
18
|
|
|
21
|
-
|
|
19
|
+
function getFunctionParameterList(func) {
|
|
20
|
+
let funcStr = func.toString()
|
|
21
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
22
|
+
.replace(/\/\/(.)*/g, '')
|
|
23
|
+
.replace(/{[\s\S]*}/, '')
|
|
24
|
+
.replace(/=>/g, '')
|
|
25
|
+
.trim();
|
|
26
|
+
|
|
27
|
+
const paramStartIndex = funcStr.indexOf("(") + 1;
|
|
28
|
+
const paramEndIndex = funcStr.lastIndexOf(")");
|
|
29
|
+
|
|
30
|
+
if (paramStartIndex === 0 || paramEndIndex === -1 || paramStartIndex >= paramEndIndex) {
|
|
31
|
+
const potentialSingleArg = funcStr.split('=>')[0].trim();
|
|
32
|
+
if (potentialSingleArg && !potentialSingleArg.includes('(') && !potentialSingleArg.includes(')')) {
|
|
33
|
+
return [potentialSingleArg].filter(p => p.length > 0);
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const paramsString = funcStr.substring(paramStartIndex, paramEndIndex);
|
|
39
|
+
if (!paramsString.trim()) return [];
|
|
22
40
|
|
|
41
|
+
return paramsString.split(",")
|
|
42
|
+
.map(param => param.replace(/=[\s\S]*/g, '').trim())
|
|
43
|
+
.filter(param => param.length > 0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
exports.SpellFrame = class SpellFrame {
|
|
23
47
|
constructor(options = {}) {
|
|
24
48
|
const defaults = {
|
|
25
|
-
renderPath: "render",
|
|
26
|
-
spellcraftModuleRelativePath: ".spellcraft_linked_modules",
|
|
49
|
+
renderPath: "./render",
|
|
27
50
|
cleanBeforeRender: true,
|
|
28
|
-
cleanModulesAfterRender: true,
|
|
29
51
|
useDefaultFileHandlers: true
|
|
30
52
|
};
|
|
31
53
|
|
|
32
|
-
// Assign options, falling back to defaults
|
|
33
54
|
Object.assign(this, defaults, options);
|
|
34
55
|
|
|
35
56
|
this.initFn = [];
|
|
36
|
-
this._cache = {};
|
|
57
|
+
this._cache = {};
|
|
37
58
|
this.cliExtensions = [];
|
|
38
|
-
this.currentPackage = this.getCwdPackage();
|
|
39
|
-
this.currentPackagePath = this.getCwdPackagePath();
|
|
40
59
|
this.fileTypeHandlers = (this.useDefaultFileHandlers) ? { ...defaultFileTypeHandlers } : {};
|
|
41
60
|
this.functionContext = {};
|
|
42
61
|
this.lastRender = null;
|
|
43
62
|
this.activePath = null;
|
|
44
|
-
this.loadedModules = [];
|
|
45
|
-
this.magicContent = {}; // { modulefile: [...snippets] }
|
|
46
|
-
this.registeredFunctions = {}; // { modulefile: [...functionNames] }
|
|
47
|
-
|
|
48
|
-
this.renderPath = path.resolve(this.currentPackagePath, this.renderPath);
|
|
49
|
-
this.modulePath = path.resolve(this.currentPackagePath, this.spellcraftModuleRelativePath);
|
|
50
|
-
|
|
51
|
-
this.jsonnet = new Jsonnet();
|
|
52
63
|
|
|
53
|
-
this.
|
|
54
|
-
.addJpath(path.join(
|
|
55
|
-
|
|
56
|
-
.
|
|
57
|
-
.cleanModulePath();
|
|
58
|
-
|
|
59
|
-
this.loadedModules = this.loadModulesFromPackageList();
|
|
60
|
-
this.loadModulesFromModuleDirectory();
|
|
61
|
-
|
|
62
|
-
return this;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
cleanModulePath() {
|
|
66
|
-
if (!fs.existsSync(this.modulePath)) {
|
|
67
|
-
fs.mkdirSync(this.modulePath, { recursive: true });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
fs.readdirSync(this.modulePath)
|
|
72
|
-
.map(e => path.join(this.modulePath, e))
|
|
73
|
-
.forEach(e => fs.unlinkSync(e));
|
|
64
|
+
this.jsonnet = new Jsonnet()
|
|
65
|
+
.addJpath(path.join(__dirname, '../lib'))
|
|
66
|
+
// REFACTOR: Look in the local project's node_modules for explicit imports
|
|
67
|
+
.addJpath(path.join(baseDir, 'node_modules'));
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
// Built-in native functions
|
|
70
|
+
this.addNativeFunction("envvar", (name) => process.env[name] || false, "name");
|
|
71
|
+
this.addNativeFunction("path", () => this.activePath || process.cwd());
|
|
78
72
|
|
|
79
|
-
|
|
73
|
+
// REFACTOR: Automatically find and register plugins from package.json
|
|
74
|
+
this.loadPluginsFromDependencies();
|
|
80
75
|
}
|
|
81
76
|
|
|
82
77
|
_generateCacheKey(functionName, args) {
|
|
@@ -84,11 +79,9 @@ exports.SpellFrame = class SpellFrame {
|
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
addFileTypeHandler(pattern, handler) {
|
|
87
|
-
// Making it writable: false by default is a strong choice.
|
|
88
|
-
// If flexibility to override is needed later, this could be a simple assignment.
|
|
89
82
|
Object.defineProperty(this.fileTypeHandlers, pattern, {
|
|
90
83
|
value: handler,
|
|
91
|
-
writable: false,
|
|
84
|
+
writable: false,
|
|
92
85
|
enumerable: true,
|
|
93
86
|
configurable: true
|
|
94
87
|
});
|
|
@@ -101,8 +94,6 @@ exports.SpellFrame = class SpellFrame {
|
|
|
101
94
|
if (this._cache[key] !== undefined) {
|
|
102
95
|
return this._cache[key];
|
|
103
96
|
}
|
|
104
|
-
|
|
105
|
-
// Execute the function with `this.functionContext` as its `this` value.
|
|
106
97
|
const result = func.apply(this.functionContext, args);
|
|
107
98
|
this._cache[key] = result;
|
|
108
99
|
return result;
|
|
@@ -110,6 +101,12 @@ exports.SpellFrame = class SpellFrame {
|
|
|
110
101
|
return this;
|
|
111
102
|
}
|
|
112
103
|
|
|
104
|
+
addExternalCode(name, value) {
|
|
105
|
+
const finalValue = (typeof value === "string") ? value : JSON.stringify(value);
|
|
106
|
+
this.jsonnet = this.jsonnet.extCode(name, finalValue);
|
|
107
|
+
return this;
|
|
108
|
+
}
|
|
109
|
+
|
|
113
110
|
extendWithModuleMetadata(metadata) {
|
|
114
111
|
if (metadata.fileTypeHandlers) {
|
|
115
112
|
Object.entries(metadata.fileTypeHandlers).forEach(([pattern, handler]) => {
|
|
@@ -123,13 +120,6 @@ exports.SpellFrame = class SpellFrame {
|
|
|
123
120
|
this.initFn.push(...(Array.isArray(metadata.initFn) ? metadata.initFn : [metadata.initFn]));
|
|
124
121
|
}
|
|
125
122
|
Object.assign(this.functionContext, metadata.functionContext || {});
|
|
126
|
-
|
|
127
|
-
return this;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
addJpath(jpath) {
|
|
131
|
-
// console.log(`[*] Adding Jpath ${jpath}`);
|
|
132
|
-
this.jsonnet.addJpath(jpath);
|
|
133
123
|
return this;
|
|
134
124
|
}
|
|
135
125
|
|
|
@@ -139,217 +129,79 @@ exports.SpellFrame = class SpellFrame {
|
|
|
139
129
|
}
|
|
140
130
|
}
|
|
141
131
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
while (!fs.existsSync(path.join(checkPath, 'package.json')) && depth < maxdepth) {
|
|
152
|
-
path = path.join(checkPath, '..');
|
|
153
|
-
depth++;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (fs.existsSync(path.join(checkPath, 'package.json'))) {
|
|
157
|
-
return checkPath;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
getModulePackage(name) {
|
|
164
|
-
// For backwards compatability
|
|
165
|
-
if (name == '..') {
|
|
166
|
-
return this.currentPackage;
|
|
167
|
-
}
|
|
132
|
+
/**
|
|
133
|
+
* REFACTOR: Scans the project's package.json for dependencies.
|
|
134
|
+
* If a dependency has a 'spellcraft' key in its package.json,
|
|
135
|
+
* load its JS entrypoint and register native functions safely.
|
|
136
|
+
*/
|
|
137
|
+
loadPluginsFromDependencies() {
|
|
138
|
+
const packageJsonPath = path.join(baseDir, 'package.json');
|
|
139
|
+
if (!fs.existsSync(packageJsonPath)) return;
|
|
168
140
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
// For backwards compatability
|
|
174
|
-
if (name == '..') {
|
|
175
|
-
return this.currentPackagePath;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return path.dirname(require.resolve(name, { paths: [this.currentPackagePath] }));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
loadFunctionsFromFile(file, as) {
|
|
182
|
-
|
|
183
|
-
const moduleExports = require(file);
|
|
184
|
-
|
|
185
|
-
const magicContentSnippets = [];
|
|
186
|
-
if (moduleExports._spellcraft_metadata) {
|
|
187
|
-
this.extendWithModuleMetadata(moduleExports._spellcraft_metadata);
|
|
188
|
-
}
|
|
141
|
+
let pkg;
|
|
142
|
+
try {
|
|
143
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
144
|
+
} catch (e) { return; }
|
|
189
145
|
|
|
190
|
-
const
|
|
191
|
-
.filter(key => key !== '_spellcraft_metadata' && typeof moduleExports[key] !== 'undefined')
|
|
192
|
-
.map(funcName => {
|
|
193
|
-
let func, params;
|
|
146
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
194
147
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
148
|
+
Object.keys(deps).forEach(depName => {
|
|
149
|
+
try {
|
|
150
|
+
// Resolve the package.json of the dependency
|
|
151
|
+
const depPath = path.dirname(require.resolve(`${depName}/package.json`, { paths: [baseDir] }));
|
|
152
|
+
const depPkg = require(`${depName}/package.json`);
|
|
199
153
|
|
|
200
|
-
if
|
|
201
|
-
|
|
202
|
-
|
|
154
|
+
// Only load if it marks itself as a spellcraft module
|
|
155
|
+
if (depPkg.spellcraft || depPkg.keywords?.includes("spellcraft-module")) {
|
|
156
|
+
this.loadPlugin(depName, depPkg.main ? path.join(depPath, depPkg.main) : null);
|
|
203
157
|
}
|
|
204
|
-
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
magicContentSnippets.push(`\t${funcName}(${paramString}):: std.native('${funcName}')(${paramString})`);
|
|
209
|
-
|
|
210
|
-
this.addNativeFunction(funcName, func, ...params);
|
|
211
|
-
return funcName;
|
|
212
|
-
}).filter(Boolean); // Remove nulls from skipped items
|
|
213
|
-
|
|
214
|
-
this.registeredFunctions[as] = registeredFunctionNames;
|
|
215
|
-
this.magicContent[as] = magicContentSnippets;
|
|
216
|
-
|
|
217
|
-
return this;
|
|
158
|
+
} catch (e) {
|
|
159
|
+
// Dependency might not be installed or resolvable, skip quietly
|
|
160
|
+
}
|
|
161
|
+
});
|
|
218
162
|
}
|
|
219
163
|
|
|
220
|
-
|
|
221
|
-
|
|
164
|
+
/**
|
|
165
|
+
* REFACTOR: Loads a specific plugin JS file.
|
|
166
|
+
* Namespaces native functions using the package name to prevent collisions.
|
|
167
|
+
* e.g., @c6fc/spellcraft-aws-auth exports 'aws' -> registered as '@c6fc/spellcraft-aws-auth:aws'
|
|
168
|
+
*/
|
|
169
|
+
loadPlugin(packageName, jsMainPath) {
|
|
170
|
+
if (!jsMainPath || !fs.existsSync(jsMainPath)) return;
|
|
222
171
|
|
|
223
|
-
|
|
224
|
-
// console.log('[+] No spellcraft_modules/packages.json file found. Skipping package-based module import.');
|
|
225
|
-
return [];
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let packages;
|
|
172
|
+
let moduleExports;
|
|
229
173
|
try {
|
|
230
|
-
|
|
174
|
+
moduleExports = require(jsMainPath);
|
|
231
175
|
} catch (e) {
|
|
232
|
-
console.
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return Object.entries(packages).map(([npmPackageName, moduleKey]) => {
|
|
237
|
-
this.loadModuleByName(moduleKey, npmPackageName);
|
|
238
|
-
return moduleKey;
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
loadCurrentPackageAsModule(moduleKey) {
|
|
243
|
-
return this.loadModuleByName(moduleKey, '..');
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
loadModuleByName(moduleKey, npmPackageName) {
|
|
247
|
-
const importModuleConfig = this.getModulePackage(npmPackageName);
|
|
248
|
-
const importModulePath = this.getModulePackagePath(npmPackageName);
|
|
249
|
-
|
|
250
|
-
this.loadFunctionsFromFile(path.resolve(importModulePath, 'module.js'), moduleKey);
|
|
251
|
-
|
|
252
|
-
const sourceLibsonnetPath = path.resolve(importModulePath, 'module.libsonnet');
|
|
253
|
-
const targetLibsonnetPath = path.resolve(this.modulePath, `${moduleKey}.libsonnet`);
|
|
254
|
-
|
|
255
|
-
if (fs.existsSync(targetLibsonnetPath)) {
|
|
256
|
-
throw new Error(`[!] Module library ${path.basename(targetLibsonnetPath)} already exists. This means there is a conflict with package link names.`);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
fs.copyFileSync(sourceLibsonnetPath, targetLibsonnetPath);
|
|
260
|
-
|
|
261
|
-
console.log(`[+] Linked ${(npmPackageName == '..') ? 'this package'.green : npmPackageName.green} as ${path.basename(targetLibsonnetPath).green}`);
|
|
262
|
-
|
|
263
|
-
return this;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
loadModulesFromFileList(jsModuleFiles, as) {
|
|
267
|
-
let allRegisteredFunctions = [];
|
|
268
|
-
let allMagicContent = [];
|
|
269
|
-
|
|
270
|
-
jsModuleFiles.forEach(file => {
|
|
271
|
-
this.loadFunctionsFromFile(file, as);
|
|
272
|
-
console.log(`[+] Loaded [${this.registeredFunctions[as].join(', ').cyan}] from ${path.basename(file).green} into modules.${as.green}`);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
return this;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
loadModulesFromModuleDirectory() {
|
|
279
|
-
const spellcraftModulesPath = path.join(this.currentPackagePath, 'spellcraft_modules');
|
|
280
|
-
if (!fs.existsSync(spellcraftModulesPath)) {
|
|
281
|
-
return this;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (!!this.currentPackage?.config?.spellcraft_module_default_name) {
|
|
285
|
-
console.log("[-] This package is a SpellCraft module. Skipping directory-based module import.");
|
|
286
|
-
return { registeredFunctions: [], magicContent: [] };
|
|
176
|
+
console.warn(`[!] Failed to load plugin ${packageName}: ${e.message}`);
|
|
177
|
+
return;
|
|
287
178
|
}
|
|
288
179
|
|
|
289
|
-
|
|
290
|
-
.
|
|
291
|
-
.map(f => path.join(spellcraftModulesPath, f));
|
|
292
|
-
|
|
293
|
-
return this.loadModulesFromFileList(jsModuleFiles, 'modules');
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
async importSpellCraftModuleFromNpm(npmPackage, name = false) {
|
|
297
|
-
if (!fs.existsSync(this.getModulePackagePath(npmPackage))) {
|
|
298
|
-
console.log(`[*] Attempting to install ${npmPackage.blue}...`);
|
|
299
|
-
|
|
300
|
-
const install = spawnSync(`npm`, ['install', '--save', npmPackage], {
|
|
301
|
-
cwd: baseDir,
|
|
302
|
-
stdio: 'inherit'
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
if (install.error || install.status !== 0) {
|
|
306
|
-
throw new Error(`Failed to install npm package ${npmPackage.blue}. Error: ${install.error.red || install.stderr.toString().red}`);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
console.log(`[+] Successfully installed ${npmPackage.blue}.`);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const importModuleConfig = this.getModulePackage(`${npmPackage}/package.json`).config;
|
|
313
|
-
const currentPackageConfig = this.currentPackage.config;
|
|
314
|
-
|
|
315
|
-
if (!name && !!!importModuleConfig?.spellcraft_module_default_name) {
|
|
316
|
-
throw new Error(`[!] No import name specified for ${npmPackage.blue}, and it has no 'spellcraft_module_default_name' in its package.json config.`.red);
|
|
180
|
+
if (moduleExports._spellcraft_metadata) {
|
|
181
|
+
this.extendWithModuleMetadata(moduleExports._spellcraft_metadata);
|
|
317
182
|
}
|
|
318
183
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
packages = JSON.parse(fs.readFileSync(packagesFilePath, 'utf-8'));
|
|
332
|
-
} catch (e) {
|
|
333
|
-
console.warn(`[!] Could not parse existing ${packagesFilePath}. Starting fresh. Error: ${e.message}`.red);
|
|
334
|
-
packages = {};
|
|
335
|
-
}
|
|
184
|
+
Object.keys(moduleExports).forEach(key => {
|
|
185
|
+
if (key === '_spellcraft_metadata') return;
|
|
186
|
+
|
|
187
|
+
let func, params;
|
|
188
|
+
if (Array.isArray(moduleExports[key])) {
|
|
189
|
+
[func, ...params] = moduleExports[key];
|
|
190
|
+
} else if (typeof moduleExports[key] === "function") {
|
|
191
|
+
func = moduleExports[key];
|
|
192
|
+
params = getFunctionParameterList(func);
|
|
193
|
+
} else {
|
|
194
|
+
return;
|
|
336
195
|
}
|
|
337
196
|
|
|
338
|
-
//
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const packagesKey = name || importModuleConfig.spellcraft_module_default_name;
|
|
344
|
-
packages[npmPackage] = packagesKey; // Store the clean package name
|
|
345
|
-
|
|
346
|
-
fs.writeFileSync(packagesFilePath, JSON.stringify(packages, null, "\t"));
|
|
347
|
-
console.log(`[+] Linked ${npmPackage} as SpellCraft module '${packagesKey}'`);
|
|
197
|
+
// REGISTER WITH NAMESPACE
|
|
198
|
+
// This is the key fix. We prefix the function name with the package name.
|
|
199
|
+
const uniqueId = `${packageName}:${key}`;
|
|
200
|
+
this.addNativeFunction(uniqueId, func, ...params);
|
|
348
201
|
|
|
349
|
-
|
|
350
|
-
console.log(`[
|
|
351
|
-
|
|
352
|
-
}
|
|
202
|
+
// Optional: Log debug info
|
|
203
|
+
// console.log(`[+] Registered native function: ${uniqueId}`);
|
|
204
|
+
});
|
|
353
205
|
}
|
|
354
206
|
|
|
355
207
|
async render(file) {
|
|
@@ -358,105 +210,74 @@ exports.SpellFrame = class SpellFrame {
|
|
|
358
210
|
throw new Error(`SpellCraft Render Error: Input file ${absoluteFilePath} does not exist.`);
|
|
359
211
|
}
|
|
360
212
|
|
|
361
|
-
this.activePath = path.dirname(absoluteFilePath);
|
|
213
|
+
this.activePath = path.dirname(absoluteFilePath);
|
|
362
214
|
|
|
363
|
-
this.
|
|
364
|
-
|
|
365
|
-
}));
|
|
366
|
-
|
|
367
|
-
if (this.registeredFunctions.modules.length > 0) {
|
|
368
|
-
fs.writeFileSync(path.join(this.modulePath, `modules`), `{\n${this.magicContent.modules.join(",\n")}\n}`, 'utf-8');
|
|
369
|
-
console.log(`[+] Registered native functions [${this.registeredFunctions.modules.join(', ').cyan}] to modules.${'modules'.green}`);
|
|
215
|
+
if (this.renderPath.endsWith(path.sep)) {
|
|
216
|
+
this.renderPath = this.renderPath.slice(0, -1);
|
|
370
217
|
}
|
|
371
218
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
219
|
+
try {
|
|
220
|
+
console.log(`[+] Evaluating Jsonnet file: ${absoluteFilePath}`);
|
|
221
|
+
this.lastRender = JSON.parse(await this.jsonnet.evaluateFile(absoluteFilePath));
|
|
222
|
+
} catch (e) {
|
|
223
|
+
throw new Error(`Jsonnet Evaluation Error: ${e.message || e}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return this.lastRender;
|
|
227
|
+
}
|
|
378
228
|
|
|
379
|
-
|
|
380
|
-
this.lastRender = JSON.parse(await this.jsonnet.evaluateFile(absoluteFilePath));
|
|
229
|
+
async renderString(snippet) {
|
|
381
230
|
|
|
382
|
-
|
|
383
|
-
this.cleanModulePath();
|
|
231
|
+
this.activePath = process.cwd();
|
|
384
232
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
233
|
+
try {
|
|
234
|
+
this.lastRender = JSON.parse(await this.jsonnet.evaluateSnippet(snippet));
|
|
235
|
+
} catch (e) {
|
|
236
|
+
throw new Error(`Jsonnet Evaluation Error: ${e.message || e}`);
|
|
388
237
|
}
|
|
389
238
|
|
|
390
239
|
return this.lastRender;
|
|
391
240
|
}
|
|
392
241
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
242
|
+
// Removed: importSpellCraftModuleFromNpm
|
|
243
|
+
// Removed: loadModulesFromModuleDirectory
|
|
244
|
+
// Removed: loadModulesFromPackageList
|
|
245
|
+
// Removed: loadModuleByName (file copier)
|
|
396
246
|
|
|
397
247
|
write(filesToWrite = this.lastRender) {
|
|
398
|
-
if (!filesToWrite || typeof filesToWrite !== 'object'
|
|
399
|
-
console.log("[+] No files to write from the last render or provided input.");
|
|
400
|
-
return this;
|
|
401
|
-
}
|
|
248
|
+
if (!filesToWrite || typeof filesToWrite !== 'object') return this;
|
|
402
249
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
fs.mkdirSync(this.renderPath, { recursive: true });
|
|
406
|
-
}
|
|
407
|
-
} catch (e) {
|
|
408
|
-
throw new Error(`SpellCraft Write Error: renderPath '${this.renderPath}' could not be created. ${e.message}`);
|
|
250
|
+
if (!fs.existsSync(this.renderPath)) {
|
|
251
|
+
fs.mkdirSync(this.renderPath, { recursive: true });
|
|
409
252
|
}
|
|
410
253
|
|
|
411
254
|
if (this.cleanBeforeRender) {
|
|
412
|
-
|
|
413
|
-
|
|
255
|
+
// ... (Cleaning logic remains the same)
|
|
256
|
+
try {
|
|
414
257
|
Object.keys(this.fileTypeHandlers).forEach(regexPattern => {
|
|
415
|
-
const regex = new RegExp(regexPattern, "i");
|
|
416
|
-
fs.
|
|
417
|
-
.filter(f => regex.test(f))
|
|
418
|
-
|
|
419
|
-
const filePathToClean = path.join(this.renderPath, f);
|
|
420
|
-
try {
|
|
421
|
-
fs.unlinkSync(filePathToClean);
|
|
422
|
-
// console.log(` - Removed ${filePathToClean}`);
|
|
423
|
-
} catch (cleanError) {
|
|
424
|
-
console.warn(` [!] Failed to remove ${filePathToClean}: ${cleanError.message}`);
|
|
425
|
-
}
|
|
426
|
-
});
|
|
258
|
+
const regex = new RegExp(regexPattern, "i");
|
|
259
|
+
if(fs.existsSync(this.renderPath)) {
|
|
260
|
+
fs.readdirSync(this.renderPath).filter(f => regex.test(f)).forEach(f => fs.unlinkSync(path.join(this.renderPath, f)));
|
|
261
|
+
}
|
|
427
262
|
});
|
|
428
|
-
} catch (e) {
|
|
429
|
-
// This error is for readdirSync itself, less likely but possible
|
|
430
|
-
throw new Error(`SpellCraft Clean Error: Failed to read/clean files from renderPath '${this.renderPath}'. ${e.message}`);
|
|
431
|
-
}
|
|
263
|
+
} catch (e) {}
|
|
432
264
|
}
|
|
433
265
|
|
|
434
266
|
console.log(`[+] Writing files to: ${this.renderPath}`);
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const processedContent = handlerFn(fileContent);
|
|
447
|
-
fs.writeFileSync(outputFilePath, processedContent, 'utf-8');
|
|
448
|
-
console.log(` -> ${path.basename(outputFilePath).green}`);
|
|
449
|
-
} catch (handlerError) {
|
|
450
|
-
console.error(` [!] Error processing or writing file ${filename}: ${handlerError.message}`);
|
|
451
|
-
// Optionally re-throw or collect errors
|
|
452
|
-
}
|
|
267
|
+
for (const filename in filesToWrite) {
|
|
268
|
+
if (Object.prototype.hasOwnProperty.call(filesToWrite, filename)) {
|
|
269
|
+
const outputFilePath = path.join(this.renderPath, filename);
|
|
270
|
+
const [, handlerFn] = Object.entries(this.fileTypeHandlers)
|
|
271
|
+
.find(([pattern]) => new RegExp(pattern).test(filename)) || [null, defaultFileHandler];
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
fs.writeFileSync(outputFilePath, handlerFn(filesToWrite[filename]), 'utf-8');
|
|
275
|
+
console.log(' -> ' + path.basename(outputFilePath));
|
|
276
|
+
} catch (e) {
|
|
277
|
+
console.error(` [!] Error writing ${filename}: ${e.message}`);
|
|
453
278
|
}
|
|
454
279
|
}
|
|
455
|
-
} catch (e) {
|
|
456
|
-
// This would catch errors in the loop structure itself, less likely for file operations
|
|
457
|
-
throw new Error(`SpellCraft Write Error: Failed during file writing loop. ${e.message}`);
|
|
458
280
|
}
|
|
459
|
-
|
|
460
281
|
return this;
|
|
461
282
|
}
|
|
462
283
|
};
|
package/src/test.js
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const { SpellFrame } = require('./index.js');
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const spell = new SpellFrame({
|
|
7
7
|
renderPath: './render',
|
|
8
8
|
cleanBeforeRender: true
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
(async () => {
|
|
12
12
|
|
|
13
|
-
testBootstrap =
|
|
13
|
+
testBootstrap = await spell.renderString(`local spellcraft = import 'spellcraft'; { test: spellcraft.path() }`);
|
|
14
14
|
console.log(testBootstrap);
|
|
15
15
|
|
|
16
16
|
})();
|