@c6fc/spellcraft 0.1.3 → 0.1.4
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/package.json +7 -10
- package/src/index.js +95 -2
package/package.json
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"@colors/colors": "^1.6.0",
|
|
4
4
|
"@hanazuki/node-jsonnet": "^0.4.2",
|
|
5
5
|
"js-yaml": "^4.1.0",
|
|
6
|
-
"yargs": "^
|
|
6
|
+
"yargs": "^18.0.0"
|
|
7
7
|
},
|
|
8
8
|
"name": "@c6fc/spellcraft",
|
|
9
9
|
"description": "Extensible JSonnet CLI platform",
|
|
10
|
-
"version": "0.1.
|
|
10
|
+
"version": "0.1.4",
|
|
11
11
|
"main": "src/index.js",
|
|
12
12
|
"directories": {
|
|
13
13
|
"lib": "lib"
|
|
@@ -16,23 +16,20 @@
|
|
|
16
16
|
"spellcraft": "bin/spellcraft.js"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
|
-
"test": "node src/test.js"
|
|
20
|
-
"doc": "node src/doc-generator.js"
|
|
19
|
+
"test": "node src/test.js"
|
|
21
20
|
},
|
|
22
21
|
"repository": {
|
|
23
22
|
"type": "git",
|
|
24
23
|
"url": "git+https://github.com/c6fc/spellcraft.git"
|
|
25
24
|
},
|
|
26
25
|
"keywords": [
|
|
27
|
-
"jsonnet"
|
|
26
|
+
"jsonnet",
|
|
27
|
+
"spellcraft"
|
|
28
28
|
],
|
|
29
29
|
"author": "Brad Woodward (brad@bradwoodward.io)",
|
|
30
30
|
"license": "MIT",
|
|
31
31
|
"bugs": {
|
|
32
32
|
"url": "https://github.com/c6fc/spellcraft/issues"
|
|
33
33
|
},
|
|
34
|
-
"homepage": "https://github.com/c6fc/spellcraft#readme"
|
|
35
|
-
|
|
36
|
-
"clean-jsdoc-theme": "^4.3.0"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
34
|
+
"homepage": "https://github.com/c6fc/spellcraft#readme"
|
|
35
|
+
}
|
package/src/index.js
CHANGED
|
@@ -60,6 +60,9 @@ exports.SpellFrame = class SpellFrame {
|
|
|
60
60
|
this.functionContext = {};
|
|
61
61
|
this.lastRender = null;
|
|
62
62
|
this.activePath = null;
|
|
63
|
+
this.visitedPlugins = new Set();
|
|
64
|
+
this.loadedPlugins = new Map();
|
|
65
|
+
this.isInitialized = false;
|
|
63
66
|
|
|
64
67
|
this.jsonnet = new Jsonnet()
|
|
65
68
|
.addJpath(path.join(__dirname, '../lib'))
|
|
@@ -73,6 +76,8 @@ exports.SpellFrame = class SpellFrame {
|
|
|
73
76
|
|
|
74
77
|
// REFACTOR: Automatically find and register plugins from package.json
|
|
75
78
|
this.loadPluginsFromDependencies();
|
|
79
|
+
this.loadPluginsRecursively(baseDir);
|
|
80
|
+
this.validatePluginRequirements();
|
|
76
81
|
|
|
77
82
|
// 2. Load Local Magic Modules (Rapid Prototyping Mode)
|
|
78
83
|
this.loadLocalMagicModules();
|
|
@@ -117,20 +122,27 @@ exports.SpellFrame = class SpellFrame {
|
|
|
117
122
|
this.addFileTypeHandler(pattern, handler);
|
|
118
123
|
});
|
|
119
124
|
}
|
|
125
|
+
|
|
120
126
|
if (metadata.cliExtensions) {
|
|
121
127
|
this.cliExtensions.push(...(Array.isArray(metadata.cliExtensions) ? metadata.cliExtensions : [metadata.cliExtensions]));
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
|
|
130
|
+
if (metadata.init) {
|
|
131
|
+
this.initFn.push(...(Array.isArray(metadata.init) ? metadata.init : [metadata.init]));
|
|
125
132
|
}
|
|
133
|
+
|
|
126
134
|
Object.assign(this.functionContext, metadata.functionContext || {});
|
|
127
135
|
return this;
|
|
128
136
|
}
|
|
129
137
|
|
|
130
138
|
async init() {
|
|
139
|
+
if (this.isInitialized) return;
|
|
140
|
+
|
|
131
141
|
for (const step of this.initFn) {
|
|
132
142
|
await step.call();
|
|
133
143
|
}
|
|
144
|
+
|
|
145
|
+
this.isInitialized = true;
|
|
134
146
|
}
|
|
135
147
|
|
|
136
148
|
loadPluginsFromDependencies() {
|
|
@@ -244,6 +256,10 @@ exports.SpellFrame = class SpellFrame {
|
|
|
244
256
|
loadPlugin(packageName, jsMainPath) {
|
|
245
257
|
if (!jsMainPath || !fs.existsSync(jsMainPath)) return;
|
|
246
258
|
|
|
259
|
+
if (this.loadedPlugins.has(packageName)) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
247
263
|
let moduleExports;
|
|
248
264
|
try {
|
|
249
265
|
moduleExports = require(jsMainPath);
|
|
@@ -254,6 +270,11 @@ exports.SpellFrame = class SpellFrame {
|
|
|
254
270
|
|
|
255
271
|
if (moduleExports._spellcraft_metadata) {
|
|
256
272
|
this.extendWithModuleMetadata(moduleExports._spellcraft_metadata);
|
|
273
|
+
|
|
274
|
+
this.loadedPlugins.set(packageName, {
|
|
275
|
+
name: packageName,
|
|
276
|
+
requires: moduleExports._spellcraft_metadata.requires || []
|
|
277
|
+
});
|
|
257
278
|
}
|
|
258
279
|
|
|
259
280
|
Object.keys(moduleExports).forEach(key => {
|
|
@@ -279,7 +300,63 @@ exports.SpellFrame = class SpellFrame {
|
|
|
279
300
|
});
|
|
280
301
|
}
|
|
281
302
|
|
|
303
|
+
loadPluginsRecursively(currentDir) {
|
|
304
|
+
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
305
|
+
|
|
306
|
+
// If we've already scanned this specific directory, stop (Circular Dep protection)
|
|
307
|
+
if (this.visitedPlugins.has(packageJsonPath)) return;
|
|
308
|
+
this.visitedPlugins.add(packageJsonPath);
|
|
309
|
+
|
|
310
|
+
if (!fs.existsSync(packageJsonPath)) return;
|
|
311
|
+
|
|
312
|
+
let pkg;
|
|
313
|
+
try {
|
|
314
|
+
pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
315
|
+
} catch (e) { return; }
|
|
316
|
+
|
|
317
|
+
// Combine dependencies (devDeps are usually only relevant at the root,
|
|
318
|
+
// but we scan both for completeness at the root level).
|
|
319
|
+
// For sub-dependencies, standard 'dependencies' is usually what matters.
|
|
320
|
+
const deps = { ...pkg.dependencies, ...(currentDir === baseDir ? pkg.devDependencies : {}) };
|
|
321
|
+
|
|
322
|
+
// Create a resolver anchored to the CURRENT directory.
|
|
323
|
+
// This is crucial: it tells Node "Find dependencies relative to THIS module",
|
|
324
|
+
// not relative to the root project.
|
|
325
|
+
const localResolver = require('module').createRequire(packageJsonPath);
|
|
326
|
+
|
|
327
|
+
Object.keys(deps).forEach(depName => {
|
|
328
|
+
try {
|
|
329
|
+
// 1. Resolve where this dependency actually lives on disk
|
|
330
|
+
const depManifestPath = localResolver.resolve(`${depName}/package.json`);
|
|
331
|
+
const depDir = path.dirname(depManifestPath);
|
|
332
|
+
|
|
333
|
+
// 2. Load its package.json
|
|
334
|
+
const depPkg = require(depManifestPath);
|
|
335
|
+
|
|
336
|
+
// 3. Check if it is a SpellCraft module
|
|
337
|
+
if (depPkg.spellcraft) {
|
|
338
|
+
|
|
339
|
+
// A. Load the Plugin Logic
|
|
340
|
+
const jsMainPath = path.join(depDir, depPkg.main || 'index.js');
|
|
341
|
+
this.loadPlugin(depPkg.name, jsMainPath);
|
|
342
|
+
|
|
343
|
+
// B. Recurse!
|
|
344
|
+
// Now scan *this* dependency's dependencies
|
|
345
|
+
this.loadPluginsRecursively(depDir);
|
|
346
|
+
}
|
|
347
|
+
} catch (e) {
|
|
348
|
+
// Dependency might be optional or failed to resolve; skip gracefully
|
|
349
|
+
// console.warn(`Debug: Skipped ${depName} from ${currentDir}: ${e.message}`);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
282
354
|
async render(file) {
|
|
355
|
+
|
|
356
|
+
if (!this.isInitialized) {
|
|
357
|
+
await this.init();
|
|
358
|
+
}
|
|
359
|
+
|
|
283
360
|
const absoluteFilePath = path.resolve(file);
|
|
284
361
|
if (!fs.existsSync(absoluteFilePath)) {
|
|
285
362
|
throw new Error(`SpellCraft Render Error: Input file ${absoluteFilePath} does not exist.`);
|
|
@@ -313,6 +390,22 @@ exports.SpellFrame = class SpellFrame {
|
|
|
313
390
|
|
|
314
391
|
return this.lastRender;
|
|
315
392
|
}
|
|
393
|
+
|
|
394
|
+
validatePluginRequirements() {
|
|
395
|
+
for (const [pluginName, data] of this.loadedPlugins.entries()) {
|
|
396
|
+
if (!data.requires || data.requires.length === 0) continue;
|
|
397
|
+
|
|
398
|
+
data.requires.forEach(req => {
|
|
399
|
+
if (!this.loadedPlugins.has(req)) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`[SpellCraft Dependency Error] The module '${pluginName}' requires '${req}', ` +
|
|
402
|
+
`but '${req}' was not found or failed to load. \n` +
|
|
403
|
+
` -> Try running: npm install --save ${req}`
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
}
|
|
316
409
|
|
|
317
410
|
write(filesToWrite = this.lastRender) {
|
|
318
411
|
if (!filesToWrite || typeof filesToWrite !== 'object') return this;
|