@c6fc/spellcraft 0.0.11 → 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 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.0.11",
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": "echo \"Error: no test specified\" && exit 1",
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
- exports.SpellFrame = class SpellFrame {
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 = {}; // Initialize 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.addJpath(path.join(__dirname, '../lib')) // For core SpellCraft libsonnet files
54
- .addJpath(path.join(this.modulePath)) // For dynamically generated module imports
55
- .addNativeFunction("envvar", (name) => process.env[name] || false, "name")
56
- .addNativeFunction("path", () => this.activePath || process.cwd()) // Use activePath if available
57
- .cleanModulePath();
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'));
58
68
 
59
- this.loadedModules = this.loadModulesFromPackageList();
60
- this.loadModulesFromModuleDirectory();
69
+ // Built-in native functions
70
+ this.addNativeFunction("envvar", (name) => process.env[name] || false, "name");
71
+ this.addNativeFunction("path", () => this.activePath || process.cwd());
61
72
 
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));
74
-
75
- } catch (e) {
76
- throw new Error(`[!] Could not create/clean up temporary module folder ${path.dirname(this.modulePath).green}: ${e.message.red}`);
77
- }
78
-
79
- return this;
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, // Or true if overrides should be easy
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,226 +129,79 @@ exports.SpellFrame = class SpellFrame {
139
129
  }
140
130
  }
141
131
 
142
- getCwdPackage() {
143
- return require(path.resolve(this.getCwdPackagePath(), 'package.json'));
144
- }
145
-
146
- getCwdPackagePath() {
147
- let depth = 0;
148
- const maxdepth = 3
149
- let checkPath = process.cwd();
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
- }
168
-
169
- return require(require.resolve(name, { paths: [this.currentPackagePath] }));
170
- }
171
-
172
- getModulePackagePath(name) {
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
- }
189
-
190
- const registeredFunctionNames = Object.keys(moduleExports)
191
- .filter(key => key !== '_spellcraft_metadata' && typeof moduleExports[key] !== 'undefined')
192
- .map(funcName => {
193
- let func, params;
194
-
195
- if (typeof moduleExports[funcName] === "object" && Array.isArray(moduleExports[funcName])) {
196
- // Expects [function, paramName1, paramName2, ...]
197
- [func, ...params] = moduleExports[funcName];
198
- }
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;
199
140
 
200
- if (typeof func !== 'function') {
201
- console.warn(`[!] Export '${funcName}' in module ${file} is not a valid function for native binding.`);
202
- return null;
203
- }
204
-
205
- // For `modules` to provide convenient wrappers:
206
- // e.g. myNativeFunc(a,b):: std.native('myNativeFunc')(a,b)
207
- const paramString = params.join(', ');
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;
218
- }
219
-
220
- loadModulesFromPackageList() {
221
- const packagesConfigPath = path.join(this.currentPackagePath, 'spellcraft_modules', 'packages.json');
222
-
223
- if (!fs.existsSync(packagesConfigPath)) {
224
- // console.log('[+] No spellcraft_modules/packages.json file found. Skipping package-based module import.');
225
- return [];
226
- }
227
-
228
- let packages;
141
+ let pkg;
229
142
  try {
230
- packages = JSON.parse(fs.readFileSync(packagesConfigPath, 'utf-8'));
231
- } catch (e) {
232
- console.error(`[!] Error parsing ${packagesConfigPath.green}: ${e.message.red}. Skipping package-based module import.`);
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
- }
143
+ pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
144
+ } catch (e) { return; }
245
145
 
246
- loadModuleByName(moduleKey, npmPackageName) {
247
- const importModuleConfig = this.getModulePackage(npmPackageName);
248
- const importModulePath = this.getModulePackagePath(npmPackageName);
146
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
249
147
 
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 = [];
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`);
269
153
 
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}`);
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);
157
+ }
158
+ } catch (e) {
159
+ // Dependency might not be installed or resolvable, skip quietly
160
+ }
273
161
  });
274
-
275
- return this;
276
162
  }
277
163
 
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: [] };
287
- }
288
-
289
- const jsModuleFiles = fs.readdirSync(spellcraftModulesPath)
290
- .filter(f => f.endsWith('.js')) // Simpler check for .js files
291
- .map(f => path.join(spellcraftModulesPath, f));
292
-
293
- return this.loadModulesFromFileList(jsModuleFiles, 'modules');
294
- }
295
-
296
- async importSpellCraftModuleFromNpm(npmPackage, name = false) {
297
-
298
- let packagePath;
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;
299
171
 
172
+ let moduleExports;
300
173
  try {
301
- packagePath = fs.existsSync(this.getModulePackagePath(npmPackage));
174
+ moduleExports = require(jsMainPath);
302
175
  } catch (e) {
303
- packagePath = false;
176
+ console.warn(`[!] Failed to load plugin ${packageName}: ${e.message}`);
177
+ return;
304
178
  }
305
179
 
306
- if (!packagePath) {
307
- console.log(`[*] Attempting to install ${npmPackage.blue}...`);
308
-
309
- const install = spawnSync(`npm`, ['install', '--save', npmPackage], {
310
- cwd: this.currentPackagePath,
311
- stdio: 'inherit'
312
- });
313
-
314
- if (install.error || install.status !== 0) {
315
- throw new Error(`Failed to install npm package ${npmPackage.blue}. Error: ${install.error.red || install.stderr.toString().red}`);
316
- }
317
-
318
- console.log(`[+] Successfully installed ${npmPackage.blue}.`);
319
- }
320
-
321
- const importModuleConfig = this.getModulePackage(`${npmPackage}/package.json`).config;
322
- const currentPackageConfig = this.currentPackage.config;
323
-
324
- if (!name && !!!importModuleConfig?.spellcraft_module_default_name) {
325
- 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);
326
182
  }
327
183
 
328
- // Only link if this package is not a module itself.
329
- if (!!!currentPackageConfig?.spellcraft_module_default_name) {
330
-
331
- const packagesDirPath = path.join(this.currentPackagePath, 'spellcraft_modules');
332
- if (!fs.existsSync(packagesDirPath)) {
333
- fs.mkdirSync(packagesDirPath, { recursive: true });
334
- }
335
-
336
- const packagesFilePath = path.join(packagesDirPath, 'packages.json');
337
- let packages = {};
338
- if (fs.existsSync(packagesFilePath)) {
339
- try {
340
- packages = JSON.parse(fs.readFileSync(packagesFilePath, 'utf-8'));
341
- } catch (e) {
342
- console.warn(`[!] Could not parse existing ${packagesFilePath}. Starting fresh. Error: ${e.message}`.red);
343
- packages = {};
344
- }
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;
345
195
  }
346
196
 
347
- // Derive the base name to store (e.g., "my-package" from "my-package@1.0.0")
348
- const npmPackageBaseName = npmPackage.startsWith("@") ?
349
- `@${npmPackage.split('/')[1].split('@')[0]}` : // Handles @scope/name and @scope/name@version
350
- npmPackage.split('@')[0]; // Handles name and name@version
351
-
352
- const packagesKey = name || importModuleConfig.spellcraft_module_default_name;
353
- packages[npmPackage] = packagesKey; // Store the clean package name
354
-
355
- fs.writeFileSync(packagesFilePath, JSON.stringify(packages, null, "\t"));
356
- 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);
357
201
 
358
- } else {
359
- console.log(`[*] Module installed, but not linked because the current project is also a module.`);
360
- console.log(`---> You can use the module's JS native functions, or import its JSonnet modules.`);
361
- }
202
+ // Optional: Log debug info
203
+ // console.log(`[+] Registered native function: ${uniqueId}`);
204
+ });
362
205
  }
363
206
 
364
207
  async render(file) {
@@ -367,105 +210,74 @@ exports.SpellFrame = class SpellFrame {
367
210
  throw new Error(`SpellCraft Render Error: Input file ${absoluteFilePath} does not exist.`);
368
211
  }
369
212
 
370
- this.activePath = path.dirname(absoluteFilePath); // Set active path for relative 'path()' calls
371
-
372
- this.magicContent.modules.push(this.loadedModules.flatMap(e => {
373
- return `\t${e}:: import '${e}.libsonnet'`;
374
- }));
213
+ this.activePath = path.dirname(absoluteFilePath);
375
214
 
376
- if (this.registeredFunctions.modules.length > 0) {
377
- fs.writeFileSync(path.join(this.modulePath, `modules`), `{\n${this.magicContent.modules.join(",\n")}\n}`, 'utf-8');
378
- 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);
379
217
  }
380
218
 
381
- delete this.magicContent.modules;
382
-
383
- Object.keys(this.magicContent).forEach(e => {
384
- fs.appendFileSync(path.join(this.modulePath, `${e}.libsonnet`), ` + {\n${this.magicContent[e].join(",\n")}\n}`, 'utf-8');
385
- console.log(`[+] Registered native functions [${this.registeredFunctions[e].join(', ').cyan}] to modules.${e.green} `);
386
- });
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
+ }
387
228
 
388
- console.log(`[+] Evaluating Jsonnet file ${path.basename(absoluteFilePath).green}`);
389
- this.lastRender = JSON.parse(await this.jsonnet.evaluateFile(absoluteFilePath));
229
+ async renderString(snippet) {
390
230
 
391
- if (this.cleanModulesAfterRender) {
392
- this.cleanModulePath();
231
+ this.activePath = process.cwd();
393
232
 
394
- fs.rmdirSync(this.modulePath);
395
- } else {
396
- console.log(`[*] Leaving ${this.spellcraftModuleRelativePath} in place.`.magenta);
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}`);
397
237
  }
398
238
 
399
239
  return this.lastRender;
400
240
  }
401
241
 
402
- toString() {
403
- return this.lastRender ?? null;
404
- }
242
+ // Removed: importSpellCraftModuleFromNpm
243
+ // Removed: loadModulesFromModuleDirectory
244
+ // Removed: loadModulesFromPackageList
245
+ // Removed: loadModuleByName (file copier)
405
246
 
406
247
  write(filesToWrite = this.lastRender) {
407
- if (!filesToWrite || typeof filesToWrite !== 'object' || Object.keys(filesToWrite).length === 0) {
408
- console.log("[+] No files to write from the last render or provided input.");
409
- return this;
410
- }
248
+ if (!filesToWrite || typeof filesToWrite !== 'object') return this;
411
249
 
412
- try {
413
- if (!fs.existsSync(this.renderPath)) {
414
- fs.mkdirSync(this.renderPath, { recursive: true });
415
- }
416
- } catch (e) {
417
- 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 });
418
252
  }
419
253
 
420
254
  if (this.cleanBeforeRender) {
421
- console.log(`[+] Cleaning render path: ${this.renderPath}`);
422
- try {
255
+ // ... (Cleaning logic remains the same)
256
+ try {
423
257
  Object.keys(this.fileTypeHandlers).forEach(regexPattern => {
424
- const regex = new RegExp(regexPattern, "i"); // Case-insensitive match
425
- fs.readdirSync(this.renderPath)
426
- .filter(f => regex.test(f))
427
- .forEach(f => {
428
- const filePathToClean = path.join(this.renderPath, f);
429
- try {
430
- fs.unlinkSync(filePathToClean);
431
- // console.log(` - Removed ${filePathToClean}`);
432
- } catch (cleanError) {
433
- console.warn(` [!] Failed to remove ${filePathToClean}: ${cleanError.message}`);
434
- }
435
- });
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
+ }
436
262
  });
437
- } catch (e) {
438
- // This error is for readdirSync itself, less likely but possible
439
- throw new Error(`SpellCraft Clean Error: Failed to read/clean files from renderPath '${this.renderPath}'. ${e.message}`);
440
- }
263
+ } catch (e) {}
441
264
  }
442
265
 
443
266
  console.log(`[+] Writing files to: ${this.renderPath}`);
444
- try {
445
- for (const filename in filesToWrite) {
446
- if (Object.prototype.hasOwnProperty.call(filesToWrite, filename)) {
447
- const outputFilePath = path.join(this.renderPath, filename);
448
- const fileContent = filesToWrite[filename];
449
-
450
- // Find the appropriate handler or use default
451
- const [, handlerFn] = Object.entries(this.fileTypeHandlers)
452
- .find(([pattern]) => new RegExp(pattern).test(filename)) || [null, defaultFileHandler];
453
-
454
- try {
455
- const processedContent = handlerFn(fileContent);
456
- fs.writeFileSync(outputFilePath, processedContent, 'utf-8');
457
- console.log(` -> ${path.basename(outputFilePath).green}`);
458
- } catch (handlerError) {
459
- console.error(` [!] Error processing or writing file ${filename}: ${handlerError.message}`);
460
- // Optionally re-throw or collect errors
461
- }
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}`);
462
278
  }
463
279
  }
464
- } catch (e) {
465
- // This would catch errors in the loop structure itself, less likely for file operations
466
- throw new Error(`SpellCraft Write Error: Failed during file writing loop. ${e.message}`);
467
280
  }
468
-
469
281
  return this;
470
282
  }
471
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 sonnetry = new SpellFrame({
6
+ const spell = new SpellFrame({
7
7
  renderPath: './render',
8
8
  cleanBeforeRender: true
9
9
  });
10
10
 
11
11
  (async () => {
12
12
 
13
- testBootstrap = sonnetry.render(`local spellcraft = import 'spellcraft'; { test: spellcraft.path() }`);
13
+ testBootstrap = await spell.renderString(`local spellcraft = import 'spellcraft'; { test: spellcraft.path() }`);
14
14
  console.log(testBootstrap);
15
15
 
16
16
  })();