@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 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.10",
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();
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
- } catch (e) {
76
- throw new Error(`[!] Could not create/clean up temporary module folder ${path.dirname(this.modulePath).green}: ${e.message.red}`);
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
- 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,217 +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
- }
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
- 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
- }
141
+ let pkg;
142
+ try {
143
+ pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
144
+ } catch (e) { return; }
189
145
 
190
- const registeredFunctionNames = Object.keys(moduleExports)
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
- if (typeof moduleExports[funcName] === "object" && Array.isArray(moduleExports[funcName])) {
196
- // Expects [function, paramName1, paramName2, ...]
197
- [func, ...params] = moduleExports[funcName];
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 (typeof func !== 'function') {
201
- console.warn(`[!] Export '${funcName}' in module ${file} is not a valid function for native binding.`);
202
- return null;
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
- // 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;
158
+ } catch (e) {
159
+ // Dependency might not be installed or resolvable, skip quietly
160
+ }
161
+ });
218
162
  }
219
163
 
220
- loadModulesFromPackageList() {
221
- const packagesConfigPath = path.join(this.currentPackagePath, 'spellcraft_modules', 'packages.json');
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
- 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;
172
+ let moduleExports;
229
173
  try {
230
- packages = JSON.parse(fs.readFileSync(packagesConfigPath, 'utf-8'));
174
+ moduleExports = require(jsMainPath);
231
175
  } 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
- }
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
- 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
- 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
- // Only link if this package is not a module itself.
320
- if (!!!currentPackageConfig?.spellcraft_module_default_name) {
321
-
322
- const packagesDirPath = path.join(this.currentPackagePath, 'spellcraft_modules');
323
- if (!fs.existsSync(packagesDirPath)) {
324
- fs.mkdirSync(packagesDirPath, { recursive: true });
325
- }
326
-
327
- const packagesFilePath = path.join(packagesDirPath, 'packages.json');
328
- let packages = {};
329
- if (fs.existsSync(packagesFilePath)) {
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
- // Derive the base name to store (e.g., "my-package" from "my-package@1.0.0")
339
- const npmPackageBaseName = npmPackage.startsWith("@") ?
340
- `@${npmPackage.split('/')[1].split('@')[0]}` : // Handles @scope/name and @scope/name@version
341
- npmPackage.split('@')[0]; // Handles name and name@version
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
- } else {
350
- console.log(`[*] Module installed, but not linked because the current project is also a module.`);
351
- console.log(`---> You can use the module's JS native functions, or import its JSonnet modules.`);
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); // Set active path for relative 'path()' calls
213
+ this.activePath = path.dirname(absoluteFilePath);
362
214
 
363
- this.magicContent.modules.push(this.loadedModules.flatMap(e => {
364
- return `\t${e}:: import '${e}.libsonnet'`;
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
- delete this.magicContent.modules;
373
-
374
- Object.keys(this.magicContent).forEach(e => {
375
- fs.appendFileSync(path.join(this.modulePath, `${e}.libsonnet`), ` + {\n${this.magicContent[e].join(",\n")}\n}`, 'utf-8');
376
- console.log(`[+] Registered native functions [${this.registeredFunctions[e].join(', ').cyan}] to modules.${e.green} `);
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
- console.log(`[+] Evaluating Jsonnet file ${path.basename(absoluteFilePath).green}`);
380
- this.lastRender = JSON.parse(await this.jsonnet.evaluateFile(absoluteFilePath));
229
+ async renderString(snippet) {
381
230
 
382
- if (this.cleanModulesAfterRender) {
383
- this.cleanModulePath();
231
+ this.activePath = process.cwd();
384
232
 
385
- fs.rmdirSync(this.modulePath);
386
- } else {
387
- 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}`);
388
237
  }
389
238
 
390
239
  return this.lastRender;
391
240
  }
392
241
 
393
- toString() {
394
- return this.lastRender ?? null;
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' || Object.keys(filesToWrite).length === 0) {
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
- try {
404
- if (!fs.existsSync(this.renderPath)) {
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
- console.log(`[+] Cleaning render path: ${this.renderPath}`);
413
- try {
255
+ // ... (Cleaning logic remains the same)
256
+ try {
414
257
  Object.keys(this.fileTypeHandlers).forEach(regexPattern => {
415
- const regex = new RegExp(regexPattern, "i"); // Case-insensitive match
416
- fs.readdirSync(this.renderPath)
417
- .filter(f => regex.test(f))
418
- .forEach(f => {
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
- try {
436
- for (const filename in filesToWrite) {
437
- if (Object.prototype.hasOwnProperty.call(filesToWrite, filename)) {
438
- const outputFilePath = path.join(this.renderPath, filename);
439
- const fileContent = filesToWrite[filename];
440
-
441
- // Find the appropriate handler or use default
442
- const [, handlerFn] = Object.entries(this.fileTypeHandlers)
443
- .find(([pattern]) => new RegExp(pattern).test(filename)) || [null, defaultFileHandler];
444
-
445
- try {
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 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
  })();