@c6fc/spellcraft 0.0.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/LICENSE +21 -0
- package/README.md +17 -0
- package/bin/spellcraft.js +146 -0
- package/bin/spellcraft.js.bak +47 -0
- package/jsdoc.json +33 -0
- package/lib/spellcraft +6 -0
- package/package.json +39 -0
- package/src/index.js +796 -0
- package/src/index.js.bak +403 -0
- package/src/test.js +16 -0
package/src/index.js.bak
ADDED
@@ -0,0 +1,403 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
const fs = require("fs");
|
4
|
+
const os = require("os");
|
5
|
+
const ini = require("ini");
|
6
|
+
const path = require("path");
|
7
|
+
const yaml = require('js-yaml');
|
8
|
+
const crypto = require('crypto');
|
9
|
+
const readline = require("readline");
|
10
|
+
const { spawnSync } = require('child_process');
|
11
|
+
const { Jsonnet } = require("@hanazuki/node-jsonnet");
|
12
|
+
|
13
|
+
const baseDir = process.cwd();
|
14
|
+
|
15
|
+
exports.SpellFrame = class {
|
16
|
+
renderPath; cliExtensions; cleanBeforeRender; fileTypeHandlers; initFn; jsonnet; lastRender; activePath; functionContext; useDefaultFileHandlers;
|
17
|
+
|
18
|
+
constructor(options) {
|
19
|
+
|
20
|
+
const defaults = {
|
21
|
+
renderPath: "./render",
|
22
|
+
cleanBeforeRender: true,
|
23
|
+
useDefaultFileHandlers: true
|
24
|
+
};
|
25
|
+
|
26
|
+
Object.keys(defaults).forEach(e => {
|
27
|
+
this[e] = options?.[e] ?? defaults[e];
|
28
|
+
});
|
29
|
+
|
30
|
+
// An array of functions aggregated from all plugins that must all succeed before files are processed
|
31
|
+
this.initFn = [];
|
32
|
+
|
33
|
+
// A cache of synchronous function execution results.
|
34
|
+
this.cache = {};
|
35
|
+
|
36
|
+
// An array of functions that extend the CLI 'yargs' argument.
|
37
|
+
this.cliExtensions = [];
|
38
|
+
|
39
|
+
this.fileTypeHandlers = (this.useDefaultFileHandlers) ? defaultFileTypeHandlers : {};
|
40
|
+
|
41
|
+
// An object to pass as `this` to all functions invoked via JSonnet.
|
42
|
+
this.functionContext = {};
|
43
|
+
|
44
|
+
this.jsonnet = new Jsonnet()
|
45
|
+
.addJpath(path.join(__dirname, '../lib'))
|
46
|
+
.addJpath(path.join(__dirname, '../modules'));
|
47
|
+
|
48
|
+
this.addFunction("envvar", (name) => {
|
49
|
+
return process.env?.[name] ?? false;
|
50
|
+
}, "name");
|
51
|
+
|
52
|
+
this.addFunction("path", () => {
|
53
|
+
return `${process.cwd()}`;
|
54
|
+
});
|
55
|
+
|
56
|
+
this.loadModulesFromPackageList();
|
57
|
+
|
58
|
+
return this;
|
59
|
+
}
|
60
|
+
|
61
|
+
_cacheKey(...args) {
|
62
|
+
return crypto.createHash('sha256').update(JSON.stringify(args)).digest('hex');
|
63
|
+
}
|
64
|
+
|
65
|
+
addFileTypeHander(pattern, handler) {
|
66
|
+
Object.defineProperty(this.fileTypeHandlers, pattern, {
|
67
|
+
value: handler,
|
68
|
+
writable: false
|
69
|
+
});
|
70
|
+
|
71
|
+
return this.fileTypeHandlers;
|
72
|
+
}
|
73
|
+
|
74
|
+
addFunction(name, fn, ...parameters) {
|
75
|
+
this.jsonnet.nativeCallback(name, (...args) => {
|
76
|
+
|
77
|
+
let key = this._cacheKey(name, args);
|
78
|
+
if (!!this.cache?.[key]) {
|
79
|
+
return this.cache[key];
|
80
|
+
}
|
81
|
+
|
82
|
+
this.cache[key] = fn.call(this.functionContext, ...args);
|
83
|
+
|
84
|
+
return this.cache[key];
|
85
|
+
}, ...parameters);
|
86
|
+
}
|
87
|
+
|
88
|
+
export(name, value) {
|
89
|
+
if (typeof value !== "string") {
|
90
|
+
value = JSON.stringify(value);
|
91
|
+
}
|
92
|
+
|
93
|
+
this.jsonnet = this.jsonnet.extCode(name, value);
|
94
|
+
return this;
|
95
|
+
}
|
96
|
+
|
97
|
+
extendWithModuleMetadata(metadata) {
|
98
|
+
|
99
|
+
}
|
100
|
+
|
101
|
+
import(path) {
|
102
|
+
this.jsonnet = this.jsonnet.addJpath(path);
|
103
|
+
|
104
|
+
return this;
|
105
|
+
}
|
106
|
+
|
107
|
+
async init() {
|
108
|
+
for (const step of this.initFn) {
|
109
|
+
await step();
|
110
|
+
}
|
111
|
+
}
|
112
|
+
|
113
|
+
async importSpellCraftModuleFromNpm(npmPackage, name = false) {
|
114
|
+
const npmPath = path.resolve(path.join(baseDir, 'node_modules', npmPackage));
|
115
|
+
if (!fs.existsSync(npmPath)) {
|
116
|
+
const install = spawnSync(`npm`, ['install', '-p', npmPackage], {
|
117
|
+
cwd: this.renderPath,
|
118
|
+
stdio: [process.stdin, process.stdout, process.stderr]
|
119
|
+
});
|
120
|
+
}
|
121
|
+
|
122
|
+
const configFile = path.join(npmPath, 'package.json');
|
123
|
+
|
124
|
+
if (!fs.existsSync(configFile)) {
|
125
|
+
console.log(`[!] Package ${npmPackage} is missing package.json`);
|
126
|
+
process.exit(1);
|
127
|
+
}
|
128
|
+
|
129
|
+
const { config } = JSON.parse(fs.readFileSync(path.join(configFile)));
|
130
|
+
|
131
|
+
if (!name && !config?.spellcraft_module_default_name) {
|
132
|
+
console.log(config);
|
133
|
+
console.log(`[!] No import name specified, and ${npmPackage} has no default import name`);
|
134
|
+
process.exit(1);
|
135
|
+
}
|
136
|
+
|
137
|
+
const packagesDirPath = path.join(baseDir, 'spellcraft_modules');
|
138
|
+
if (!fs.existsSync(packagesDirPath)) {
|
139
|
+
fs.mkdirSync(packagesDirPath);
|
140
|
+
}
|
141
|
+
|
142
|
+
let packages = {};
|
143
|
+
const packagesFilePath = path.join(baseDir, 'spellcraft_modules', 'packages.json')
|
144
|
+
if (fs.existsSync(packagesFilePath)) {
|
145
|
+
packages = JSON.parse(fs.readFileSync(packagesFilePath));
|
146
|
+
}
|
147
|
+
|
148
|
+
let npmPackageBaseName;
|
149
|
+
|
150
|
+
// If the package is namespaced,
|
151
|
+
if (npmPackage[0] == "@") {
|
152
|
+
[ , npmPackageBaseName ] = npmPackage.split('@');
|
153
|
+
npmPackageBaseName = `@${npmPackageBaseName}`;
|
154
|
+
} else {
|
155
|
+
[ npmPackageBaseName ] = npmPackage.split('@');
|
156
|
+
}
|
157
|
+
|
158
|
+
const packagesKey = name || config.spellcraft_module_default_name;
|
159
|
+
packages[packagesKey] = npmPackageBaseName;
|
160
|
+
|
161
|
+
fs.writeFileSync(packagesFilePath, JSON.stringify(packages, null, "\t"));
|
162
|
+
|
163
|
+
console.log(`[+] Linked ${npmPackage} as ${packagesKey}`);
|
164
|
+
|
165
|
+
}
|
166
|
+
|
167
|
+
async render(file) {
|
168
|
+
if (!fs.existsSync(file)) {
|
169
|
+
throw new Error(`Sonnetry Error: ${file} does not exist.`);
|
170
|
+
}
|
171
|
+
|
172
|
+
this.activePath = path.dirname(path.resolve(file));
|
173
|
+
|
174
|
+
const moduleFile = path.resolve(path.join(__dirname, '../modules/modules'));
|
175
|
+
|
176
|
+
if (fs.existsSync(moduleFile)) {
|
177
|
+
throw new Error(`[!] The module target file [${moduleFile}] already exists. Remove or rename it before continuing.`);
|
178
|
+
}
|
179
|
+
|
180
|
+
this.loadModulesFromModuleDirectory(moduleFile);
|
181
|
+
|
182
|
+
console.log(this.renderPath);
|
183
|
+
|
184
|
+
this.renderPath = (this.renderPath.split("").slice(-1)[0] == "/") ?
|
185
|
+
this.renderPath.split("").slice(0, -1).join("") :
|
186
|
+
this.renderPath;
|
187
|
+
|
188
|
+
try {
|
189
|
+
this.lastRender = JSON.parse(await this.jsonnet.evaluateFile(file));
|
190
|
+
} catch (e) {
|
191
|
+
throw new Error(`Error parsing Jsonnet file: ${e}`);
|
192
|
+
}
|
193
|
+
|
194
|
+
const modulePath = path.resolve(path.join(__dirname, '../modules/'));
|
195
|
+
|
196
|
+
fs.readdirSync(modulePath)
|
197
|
+
.map(e => path.join(modulePath, e))
|
198
|
+
.forEach(e => fs.unlinkSync(e));
|
199
|
+
|
200
|
+
return this.lastRender;
|
201
|
+
}
|
202
|
+
|
203
|
+
loadModulesFromModuleDirectory(moduleFile) {
|
204
|
+
|
205
|
+
const modulePath = path.join(baseDir, 'spellcraft_modules');
|
206
|
+
|
207
|
+
if (!fs.existsSync(modulePath)) {
|
208
|
+
return [];
|
209
|
+
}
|
210
|
+
|
211
|
+
const regex = /.*?\.js$/
|
212
|
+
const fileList = fs.readdirSync(modulePath)
|
213
|
+
.filter(f => regex.test(f))
|
214
|
+
.map(f => path.join(modulePath, f));
|
215
|
+
|
216
|
+
return this.loadModulesFromFileList(fileList, moduleFile);
|
217
|
+
}
|
218
|
+
|
219
|
+
loadModulesFromPackageList() {
|
220
|
+
const packagePath = path.join(baseDir, 'spellcraft_modules', 'packages.json');
|
221
|
+
|
222
|
+
if (!fs.existsSync(packagePath)) {
|
223
|
+
console.log('No spellcraft_modules/packages.json file found. Skip package-based module import');
|
224
|
+
return [];
|
225
|
+
}
|
226
|
+
|
227
|
+
const packages = JSON.parse(fs.readFileSync(packagePath));
|
228
|
+
|
229
|
+
return Object.keys(packages).map(k => {
|
230
|
+
const configFile = path.join(baseDir, 'node_modules', packages[k], 'package.json');
|
231
|
+
const config = JSON.parse(fs.readFileSync(configFile));
|
232
|
+
|
233
|
+
const jsMainFile = path.join(baseDir, 'node_modules', packages[k], config.main);
|
234
|
+
this.loadFunctionsFromFile(jsMainFile);
|
235
|
+
|
236
|
+
const moduleFile = path.resolve(path.join(__dirname, '..', 'modules', k));
|
237
|
+
const importFile = path.resolve(path.join(jsMainFile, '..', 'module.libsonnet'));
|
238
|
+
|
239
|
+
if (fs.existsSync(importFile)) {
|
240
|
+
fs.copyFileSync(importFile, moduleFile);
|
241
|
+
}
|
242
|
+
|
243
|
+
return k;
|
244
|
+
});
|
245
|
+
}
|
246
|
+
|
247
|
+
loadModulesFromFileList(fileList, moduleFile) {
|
248
|
+
|
249
|
+
let registeredFunctions = [];
|
250
|
+
|
251
|
+
if (fileList.length < 1) {
|
252
|
+
return [];
|
253
|
+
}
|
254
|
+
|
255
|
+
let magicContent = [];
|
256
|
+
|
257
|
+
fileList.map(file => {
|
258
|
+
const { functions, magic } = this.loadFunctionsFromFile(file);
|
259
|
+
registeredFunctions = registeredFunctions.concat(functions);
|
260
|
+
magicContent = magicContent.concat(magic);
|
261
|
+
});
|
262
|
+
|
263
|
+
fs.writeFileSync(moduleFile, `{\n${magicContent.join(",\n")}\n}`);
|
264
|
+
|
265
|
+
console.log(`[+] Registered ${fileList.length} module${(fileList.length > 1) ? 's' : ''} as '${path.basename(moduleFile)}' comprising ${registeredFunctions.length} function${(registeredFunctions.length > 1) ? 's' : ''}: [ ${registeredFunctions.sort().join(', ')} ]`)
|
266
|
+
|
267
|
+
return { registeredFunctions, magicContent };
|
268
|
+
}
|
269
|
+
|
270
|
+
loadFunctionsFromFile(file) {
|
271
|
+
const functions = require(file);
|
272
|
+
|
273
|
+
const magicContent = [];
|
274
|
+
if (functions._spellcraft_metadata) {
|
275
|
+
const metadata = functions._spellcraft_metadata;
|
276
|
+
['fileTypeHandlers', 'functionContext'].forEach(e => Object.assign(this[e], metadata[e] ?? {}));
|
277
|
+
|
278
|
+
['cliExtensions'].forEach(e => metadata[e] && this[e].push(metadata[e]));
|
279
|
+
|
280
|
+
metadata.initFn && this.init.push(metadata.initFn);
|
281
|
+
}
|
282
|
+
|
283
|
+
const registeredFunctions = Object.keys(functions).filter(e => e !== '_spellcraft_metadata').map(e => {
|
284
|
+
|
285
|
+
let fn, parameters;
|
286
|
+
|
287
|
+
if (typeof functions[e] == "object") {
|
288
|
+
[fn, ...parameters] = functions[e];
|
289
|
+
}
|
290
|
+
|
291
|
+
if (typeof functions[e] == "function") {
|
292
|
+
fn = functions[e];
|
293
|
+
parameters = getFunctionParameterList(fn);
|
294
|
+
}
|
295
|
+
|
296
|
+
magicContent.push(`\t${e}(${parameters.join(', ')}):: std.native('${e}')(${parameters.join(', ')})`);
|
297
|
+
|
298
|
+
this.addFunction(e, fn, ...parameters);
|
299
|
+
return e;
|
300
|
+
});
|
301
|
+
|
302
|
+
return { functions: registeredFunctions, magic: magicContent };
|
303
|
+
}
|
304
|
+
|
305
|
+
toString() {
|
306
|
+
if (this?.lastRender) {
|
307
|
+
return this.lastRender
|
308
|
+
}
|
309
|
+
|
310
|
+
return null;
|
311
|
+
}
|
312
|
+
|
313
|
+
write(files = this.lastRender) {
|
314
|
+
try {
|
315
|
+
if (!fs.existsSync(this.renderPath)) {
|
316
|
+
fs.mkdirSync(this.renderPath, { recursive: true });
|
317
|
+
}
|
318
|
+
} catch (e) {
|
319
|
+
throw new Error(`Spellcraft Error: renderPath could not be created. ${e}`);
|
320
|
+
}
|
321
|
+
|
322
|
+
if (this.cleanBeforeRender) {
|
323
|
+
try {
|
324
|
+
Object.keys(this.fileTypeHandlers).forEach(regex => {
|
325
|
+
// console.log(regex);
|
326
|
+
fs.readdirSync(this.renderPath)
|
327
|
+
.filter(f => new RegExp(regex, "i").test(f))
|
328
|
+
.map(f => fs.unlinkSync(`${this.renderPath}/${f}`));
|
329
|
+
});
|
330
|
+
} catch (e) {
|
331
|
+
throw new Error(`Failed to remove files from renderPath. ${e}`);
|
332
|
+
}
|
333
|
+
}
|
334
|
+
|
335
|
+
try {
|
336
|
+
for (const filename in files) {
|
337
|
+
const outputPath = `${this.renderPath}/${filename}`;
|
338
|
+
|
339
|
+
const [, handler] = Object.entries(this.fileTypeHandlers)
|
340
|
+
.find(([pattern, ]) => new RegExp(pattern).test(filename)) || [false, defaultFileHandler];
|
341
|
+
|
342
|
+
fs.writeFileSync(outputPath, handler(files[filename]));
|
343
|
+
console.log(' ' + path.basename(outputPath));
|
344
|
+
}
|
345
|
+
} catch (e) {
|
346
|
+
throw new Error(`Failed to write to renderPath. ${e}`);
|
347
|
+
}
|
348
|
+
|
349
|
+
return this;
|
350
|
+
}
|
351
|
+
};
|
352
|
+
|
353
|
+
const defaultFileTypeHandlers = {
|
354
|
+
// JSON files
|
355
|
+
'.*?\.json$': (e) => {
|
356
|
+
// console.log('Using JSON encoder');
|
357
|
+
return JSON.stringify(e, null, 4)
|
358
|
+
},
|
359
|
+
|
360
|
+
// YAML files
|
361
|
+
'.*?\.yaml$': (e) => {
|
362
|
+
// console.log('Using YAML encoder');
|
363
|
+
yaml.dump(e, {
|
364
|
+
indent: 4
|
365
|
+
})
|
366
|
+
},
|
367
|
+
|
368
|
+
'.*?\.yml$': (e) => {
|
369
|
+
// console.log('Using YAML encoder');
|
370
|
+
yaml.dump(e, {
|
371
|
+
indent: 4
|
372
|
+
})
|
373
|
+
},
|
374
|
+
};
|
375
|
+
|
376
|
+
const defaultFileHandler = (e) => JSON.stringify(e, null, 4);
|
377
|
+
|
378
|
+
function getFunctionParameterList(fn) {
|
379
|
+
|
380
|
+
let str = fn.toString();
|
381
|
+
|
382
|
+
str = str.replace(/\/\*[\s\S]*?\*\//g, '')
|
383
|
+
.replace(/\/\/(.)*/g, '')
|
384
|
+
.replace(/{[\s\S]*}/, '')
|
385
|
+
.replace(/=>/g, '')
|
386
|
+
.trim();
|
387
|
+
|
388
|
+
const start = str.indexOf("(") + 1;
|
389
|
+
const end = str.length - 1;
|
390
|
+
|
391
|
+
const result = str.substring(start, end).split(", ");
|
392
|
+
|
393
|
+
const params = [];
|
394
|
+
result.forEach(element => {
|
395
|
+
element = element.replace(/=[\s\S]*/g, '').trim();
|
396
|
+
|
397
|
+
if(element.length > 0) {
|
398
|
+
params.push(element);
|
399
|
+
}
|
400
|
+
});
|
401
|
+
|
402
|
+
return params;
|
403
|
+
}
|
package/src/test.js
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#! /usr/bin/env node
|
2
|
+
|
3
|
+
const fs = require('fs');
|
4
|
+
const { SpellFrame } = require('./index.js');
|
5
|
+
|
6
|
+
const sonnetry = new SpellFrame({
|
7
|
+
renderPath: './render',
|
8
|
+
cleanBeforeRender: true
|
9
|
+
});
|
10
|
+
|
11
|
+
(async () => {
|
12
|
+
|
13
|
+
testBootstrap = sonnetry.render(`local spellcraft = import 'spellcraft'; { test: spellcraft.path() }`);
|
14
|
+
console.log(testBootstrap);
|
15
|
+
|
16
|
+
})();
|