@jseeio/jsee 0.3.7 → 0.3.9
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/.claude/settings.local.json +12 -0
- package/.eslintrc.js +38 -0
- package/AGENTS.md +38 -0
- package/CHANGELOG.md +86 -0
- package/CLAUDE.md +5 -0
- package/README.md +60 -42
- package/bin/jsee +1 -1
- package/dist/jsee.js +1 -1
- package/dist/jsee.runtime.js +1 -1
- package/jest-puppeteer.config.js +7 -5
- package/jest.unit.config.js +8 -0
- package/load/index.html +16 -4
- package/package.json +17 -13
- package/src/app.js +35 -11
- package/src/cli.js +591 -330
- package/src/constants.js +12 -0
- package/src/main.js +356 -183
- package/src/utils.js +748 -3
- package/src/worker.js +42 -18
- package/templates/bulma-app.vue +3 -2
- package/templates/bulma-input.vue +23 -18
- package/templates/bulma-output.vue +72 -7
- package/templates/common-inputs.js +2 -13
- package/templates/common-outputs.js +57 -2
- package/templates/file-picker-base.vue +169 -0
- package/templates/file-picker.vue +350 -0
- package/test/fixtures/lodash-like.js +15 -0
- package/test/fixtures/upload-sample.csv +3 -0
- package/test/test-basic.test.js +383 -17
- package/test/test-python.test.js +2 -5
- package/test/unit/cli-fetch.test.js +126 -0
- package/test/unit/utils.test.js +806 -0
- package/webpack.config.js +1 -0
package/src/cli.js
CHANGED
|
@@ -20,6 +20,8 @@ const converter = new showdown.Converter({
|
|
|
20
20
|
})
|
|
21
21
|
showdown.setFlavor('github')
|
|
22
22
|
|
|
23
|
+
const { getModelFuncJS, sanitizeName } = require('./utils.js')
|
|
24
|
+
|
|
23
25
|
// left padding of multiple lines
|
|
24
26
|
function pad (str, len, start=0) {
|
|
25
27
|
return str.split('\n').map((s, i) => i >= start ? ' '.repeat(len) + s : s).join('\n')
|
|
@@ -29,364 +31,146 @@ function depad (str, len) {
|
|
|
29
31
|
return str.split('\n').map(s => s.slice(len)).join('\n')
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// print all folders (file location, cwd, etc.)
|
|
37
|
-
console.log('Current working directory:', process.cwd())
|
|
38
|
-
console.log('Script location:', __dirname)
|
|
39
|
-
console.log('Script file:', __filename)
|
|
40
|
-
console.log('Require', require.main.filename)
|
|
34
|
+
function toArray (value) {
|
|
35
|
+
if (!value) return []
|
|
36
|
+
return Array.isArray(value) ? value : [value]
|
|
37
|
+
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
fetch: 'f',
|
|
50
|
-
execute: 'e'
|
|
51
|
-
}
|
|
52
|
-
const argvDefault = {
|
|
53
|
-
execute: false, // execute the model code on the server
|
|
54
|
-
fetch: false,
|
|
55
|
-
inputs: '',
|
|
56
|
-
port: 3000,
|
|
57
|
-
version: 'latest'
|
|
58
|
-
}
|
|
39
|
+
function collectFetchBundleBlocks (schema) {
|
|
40
|
+
return []
|
|
41
|
+
.concat(toArray(schema.model))
|
|
42
|
+
.concat(toArray(schema.view))
|
|
43
|
+
.concat(toArray(schema.render))
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
}
|
|
59
46
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
default: argvDefault,
|
|
64
|
-
})
|
|
47
|
+
function isHttpUrl (value) {
|
|
48
|
+
return /^https?:\/\//i.test(value)
|
|
49
|
+
}
|
|
65
50
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
51
|
+
function toRuntimeUrl (value) {
|
|
52
|
+
try {
|
|
53
|
+
return (new URL(value)).href
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return (new URL(value, 'https://cdn.jsdelivr.net/npm/')).href
|
|
69
56
|
}
|
|
57
|
+
}
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
let ga = argv.ga
|
|
79
|
-
let schema
|
|
80
|
-
let descriptionTxt = ''
|
|
81
|
-
let descriptionHtml = ''
|
|
82
|
-
let jsdocMarkdown = ''
|
|
83
|
-
let modelFuncs = {}
|
|
59
|
+
function isLocalJsImport (value) {
|
|
60
|
+
if (typeof value !== 'string') return false
|
|
61
|
+
const lower = value.toLowerCase()
|
|
62
|
+
if (!lower.endsWith('.js') && !lower.includes('.js?')) return false
|
|
63
|
+
if (isHttpUrl(value)) return false
|
|
64
|
+
return value.startsWith('./') || value.startsWith('../') || value.startsWith('/') || value.startsWith('file://')
|
|
65
|
+
}
|
|
84
66
|
|
|
85
|
-
|
|
86
|
-
if (typeof
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
67
|
+
function getImportUrlValue (importValue) {
|
|
68
|
+
if (typeof importValue === 'string') return importValue
|
|
69
|
+
if (importValue && typeof importValue === 'object' && typeof importValue.url === 'string') {
|
|
70
|
+
return importValue.url
|
|
90
71
|
}
|
|
72
|
+
return null
|
|
73
|
+
}
|
|
91
74
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
75
|
+
function resolveFetchImport (importValue, modelUrl, cwd) {
|
|
76
|
+
const importUrlValue = getImportUrlValue(importValue)
|
|
77
|
+
if (!importUrlValue) {
|
|
78
|
+
return null
|
|
95
79
|
}
|
|
80
|
+
const importIsObject = importValue && typeof importValue === 'object'
|
|
96
81
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
82
|
+
if (isLocalJsImport(importUrlValue)) {
|
|
83
|
+
const modelDir = modelUrl && !isHttpUrl(modelUrl) ? path.dirname(modelUrl) : '.'
|
|
84
|
+
const localSchemaPath = path.normalize(path.join(modelDir, importUrlValue))
|
|
85
|
+
const schemaImport = localSchemaPath.split(path.sep).join('/')
|
|
86
|
+
return {
|
|
87
|
+
schemaImport: schemaImport,
|
|
88
|
+
schemaEntry: importIsObject ? { ...importValue, url: schemaImport } : schemaImport,
|
|
89
|
+
importUrl: toRuntimeUrl(schemaImport),
|
|
90
|
+
localFilePath: path.resolve(cwd, localSchemaPath),
|
|
91
|
+
remoteUrl: null
|
|
92
|
+
}
|
|
101
93
|
}
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} else {
|
|
113
|
-
// Array of js files
|
|
114
|
-
// Generate schema
|
|
115
|
-
let jsdocData = jsdoc2md.getTemplateDataSync({ files: inputs.map(f => path.join(cwd, f)) })
|
|
116
|
-
schema = genSchema(jsdocData)
|
|
117
|
-
// jsdocMarkdown = jsdoc2md.renderSync({
|
|
118
|
-
// data: jsdocData,
|
|
119
|
-
// 'param-list-format': 'list',
|
|
120
|
-
// })
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Iterate over schema inputs and update aliases
|
|
124
|
-
if (schema.inputs) {
|
|
125
|
-
schema.inputs.forEach(inp => {
|
|
126
|
-
if (inp.name) {
|
|
127
|
-
if (inp.alias) {
|
|
128
|
-
argvAlias[inp.name] = inp.alias
|
|
129
|
-
}
|
|
130
|
-
if (inp.default) {
|
|
131
|
-
argvDefault[inp.name] = inp.default
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
})
|
|
95
|
+
const remoteUrl = isHttpUrl(importUrlValue)
|
|
96
|
+
? importUrlValue
|
|
97
|
+
: `https://cdn.jsdelivr.net/npm/${importUrlValue}`
|
|
98
|
+
return {
|
|
99
|
+
schemaImport: importUrlValue,
|
|
100
|
+
schemaEntry: importIsObject ? { ...importValue, url: importUrlValue } : importUrlValue,
|
|
101
|
+
importUrl: toRuntimeUrl(importUrlValue),
|
|
102
|
+
localFilePath: null,
|
|
103
|
+
remoteUrl: remoteUrl
|
|
135
104
|
}
|
|
105
|
+
}
|
|
136
106
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
console.log('Updated argv', argv)
|
|
144
|
-
|
|
145
|
-
// Initially in argv.fetch branch
|
|
146
|
-
// Check if schema has model, convert to array if needed
|
|
147
|
-
if (!schema.model) {
|
|
148
|
-
console.error('No model found in schema')
|
|
149
|
-
process.exit(1)
|
|
107
|
+
function resolveRuntimeMode (runtime, fetchEnabled, outputs) {
|
|
108
|
+
const requestedRuntime = runtime || 'auto'
|
|
109
|
+
const availableModes = ['auto', 'local', 'cdn', 'inline']
|
|
110
|
+
if (!availableModes.includes(requestedRuntime)) {
|
|
111
|
+
throw new Error(`Invalid runtime mode: ${requestedRuntime}. Use one of: ${availableModes.join(', ')}`)
|
|
150
112
|
}
|
|
151
|
-
if (
|
|
152
|
-
|
|
113
|
+
if (requestedRuntime !== 'auto') {
|
|
114
|
+
return requestedRuntime
|
|
153
115
|
}
|
|
154
|
-
if (
|
|
155
|
-
|
|
156
|
-
console.log('Executing model on the server:', m.name)
|
|
157
|
-
modelFuncs[m.name] = require(path.join(cwd, m.url))
|
|
158
|
-
m.type = 'post'
|
|
159
|
-
m.url = `/${m.name}`
|
|
160
|
-
m.worker = false
|
|
161
|
-
})
|
|
116
|
+
if (fetchEnabled) {
|
|
117
|
+
return 'inline'
|
|
162
118
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
// Generate description block
|
|
167
|
-
if (description) {
|
|
168
|
-
const descriptionMd = fs.readFileSync(path.join(cwd, description), 'utf8')
|
|
169
|
-
descriptionHtml = converter.makeHtml(descriptionMd)
|
|
119
|
+
return outputs ? 'cdn' : 'local'
|
|
120
|
+
}
|
|
170
121
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
.replace(/\n/g, ' ')
|
|
175
|
-
.replace(/\s+/g, ' ')
|
|
176
|
-
.replace(/#/g, '')
|
|
177
|
-
.replace(/\*/g, '')
|
|
178
|
-
.trim()
|
|
179
|
-
}
|
|
122
|
+
function resolveOutputPath (cwd, outputPath) {
|
|
123
|
+
if (path.isAbsolute(outputPath)) {
|
|
124
|
+
return outputPath
|
|
180
125
|
}
|
|
126
|
+
return path.join(cwd, outputPath)
|
|
127
|
+
}
|
|
181
128
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// Generate jsee code
|
|
187
|
-
let jseeHtml = ''
|
|
188
|
-
let hiddenElementHtml = ''
|
|
189
|
-
if (argv.fetch) {
|
|
190
|
-
// Fetch jsee code from the CDN or local server
|
|
191
|
-
let jseeCode
|
|
192
|
-
if (argv.version === 'dev') {
|
|
193
|
-
jseeCode = fs.readFileSync(path.join(__dirname, '..', 'dist', 'jsee.js'), 'utf8')
|
|
194
|
-
} else if (argv.version === 'latest') {
|
|
195
|
-
jseeCode = fs.readFileSync(path.join(__dirname, '..', 'dist', 'jsee.runtime.js'), 'utf8')
|
|
196
|
-
} else {
|
|
197
|
-
// Pre-fetch the jsee runtime from the CDN https://cdn.jsdelivr.net/npm/@jseeio/jsee@${argv.version}/dist/jsee.runtime.js
|
|
198
|
-
jseeCode = await fetch(`https://cdn.jsdelivr.net/npm/@jseeio/jsee@${argv.version}/dist/jsee.runtime.js`)
|
|
199
|
-
jseeCode = await jseeCode.text()
|
|
200
|
-
}
|
|
201
|
-
jseeHtml = `<script>${jseeCode}</script>`
|
|
202
|
-
// Fetch model files and store them in hidden elements
|
|
203
|
-
hiddenElementHtml += '<div id="hidden-storage" style="display: none;">'
|
|
204
|
-
|
|
205
|
-
for (let m of schema.model) {
|
|
206
|
-
if (m.type === 'get' || m.type === 'post') {
|
|
207
|
-
continue
|
|
208
|
-
}
|
|
209
|
-
if (m.url) {
|
|
210
|
-
const modelCode = fs.readFileSync(path.join(cwd, m.url), 'utf8')
|
|
211
|
-
hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${m.url}">${modelCode}</script>`
|
|
212
|
-
}
|
|
213
|
-
if (m.imports) {
|
|
214
|
-
for (let i of m.imports) {
|
|
215
|
-
const importUrl = i.includes('.js') ? i : `https://cdn.jsdelivr.net/npm/${i}`
|
|
216
|
-
|
|
217
|
-
// Create cache directory if it doesn't exist
|
|
218
|
-
const cacheDir = path.join(os.homedir(), '.cache', 'jsee')
|
|
219
|
-
fs.mkdirSync(cacheDir, { recursive: true })
|
|
220
|
-
|
|
221
|
-
// Create a hash of the importUrl
|
|
222
|
-
const hash = crypto.createHash('sha256').update(importUrl).digest('hex')
|
|
223
|
-
const cacheFilePath = path.join(cacheDir, `${hash}.js`)
|
|
224
|
-
|
|
225
|
-
let importCode
|
|
226
|
-
let useCache = false
|
|
227
|
-
|
|
228
|
-
// Check if cache file exists and is less than 1 day old
|
|
229
|
-
if (fs.existsSync(cacheFilePath)) {
|
|
230
|
-
const stats = fs.statSync(cacheFilePath)
|
|
231
|
-
const mtime = new Date(stats.mtime)
|
|
232
|
-
const now = new Date()
|
|
233
|
-
const ageInDays = (now - mtime) / (1000 * 60 * 60 * 24)
|
|
234
|
-
|
|
235
|
-
if (ageInDays < 1) {
|
|
236
|
-
console.log('Using cached import:', importUrl)
|
|
237
|
-
importCode = fs.readFileSync(cacheFilePath, 'utf8');
|
|
238
|
-
useCache = true;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!useCache) {
|
|
243
|
-
const response = await fetch(importUrl);
|
|
244
|
-
if (!response.ok) {
|
|
245
|
-
console.error(`Failed to fetch ${importUrl}: ${response.statusText}`);
|
|
246
|
-
process.exit(1);
|
|
247
|
-
}
|
|
248
|
-
importCode = await response.text()
|
|
249
|
-
fs.writeFileSync(cacheFilePath, importCode, 'utf8')
|
|
250
|
-
console.log('Fetched and stored to cache:', importUrl)
|
|
251
|
-
}
|
|
252
|
-
hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${importUrl}">${importCode}</script>`
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
hiddenElementHtml += '</div>'
|
|
257
|
-
} else {
|
|
258
|
-
jseeHtml = outputs
|
|
259
|
-
? `<script src="https://cdn.jsdelivr.net/npm/@jseeio/jsee@${argv.version}/dist/jsee.runtime.js"></script>`
|
|
260
|
-
: `<script src="http://localhost:${argv.port}/dist/jsee.runtime.js"></script>`
|
|
129
|
+
async function loadRuntimeCode (version) {
|
|
130
|
+
if (version === 'dev') {
|
|
131
|
+
return fs.readFileSync(path.join(__dirname, '..', 'dist', 'jsee.js'), 'utf8')
|
|
261
132
|
}
|
|
133
|
+
if (version === 'latest') {
|
|
134
|
+
return fs.readFileSync(path.join(__dirname, '..', 'dist', 'jsee.runtime.js'), 'utf8')
|
|
135
|
+
}
|
|
136
|
+
const response = await fetch(`https://cdn.jsdelivr.net/npm/@jseeio/jsee@${version}/dist/jsee.runtime.js`)
|
|
137
|
+
return response.text()
|
|
138
|
+
}
|
|
262
139
|
|
|
263
|
-
|
|
264
|
-
let
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Social media links
|
|
285
|
-
if (schema.page.social) {
|
|
286
|
-
// iterate over dict with k, v pairs
|
|
287
|
-
for (let [name, url] of Object.entries(schema.page.social)) {
|
|
288
|
-
switch (name) {
|
|
289
|
-
case 'twitter':
|
|
290
|
-
socialHtml += `<li><a rel="me" href="https://twitter.com/${url}">Twitter</a></li>`
|
|
291
|
-
break
|
|
292
|
-
case 'github':
|
|
293
|
-
socialHtml += `<li><a rel="me" href="https://github.com/${url}">GitHub</a></li>`
|
|
294
|
-
break
|
|
295
|
-
case 'facebook':
|
|
296
|
-
socialHtml += `<li><a rel="me" href="https://www.facebook.com/${url}">Facebook</a></li>`
|
|
297
|
-
break
|
|
298
|
-
case 'linkedin':
|
|
299
|
-
socialHtml += `<li><a rel="me" href="https://www.linkedin.com/company/${url}">LinkedIn</a></li>`
|
|
140
|
+
function getDataFromArgv (schema, argv, loadFiles=true) {
|
|
141
|
+
let data = {}
|
|
142
|
+
if (schema.inputs) {
|
|
143
|
+
schema.inputs.forEach(inp => {
|
|
144
|
+
const inputName = sanitizeName(inp.name)
|
|
145
|
+
console.log('Processing input:', inp.name, 'as', inputName)
|
|
146
|
+
if (inputName in argv) {
|
|
147
|
+
switch (inp.type) {
|
|
148
|
+
case 'file':
|
|
149
|
+
if (!loadFiles) {
|
|
150
|
+
// If we don't want to load files, just set the value to the file path
|
|
151
|
+
data[inp.name] = argv[inputName]
|
|
152
|
+
break
|
|
153
|
+
} else if (fs.existsSync(argv[inputName])) {
|
|
154
|
+
data[inp.name] = fs.readFileSync(argv[inputName], 'utf8')
|
|
155
|
+
} else {
|
|
156
|
+
console.error(`File not found: ${argv[inputName]}`)
|
|
157
|
+
process.exit(1)
|
|
158
|
+
}
|
|
300
159
|
break
|
|
301
|
-
case '
|
|
302
|
-
|
|
160
|
+
case 'int':
|
|
161
|
+
data[inp.name] = parseInt(argv[inputName], 10)
|
|
303
162
|
break
|
|
304
|
-
case '
|
|
305
|
-
|
|
163
|
+
case 'float':
|
|
164
|
+
data[inp.name] = parseFloat(argv[inputName])
|
|
306
165
|
break
|
|
166
|
+
case 'string':
|
|
307
167
|
default:
|
|
308
|
-
|
|
168
|
+
data[inp.name] = argv[inputName]
|
|
309
169
|
}
|
|
310
170
|
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (schema.page.org) {
|
|
314
|
-
orgHtml = `<div class="footer-org"><h4 class="footer-heading"><a href="${schema.page.org.url}">${schema.page.org.name}</a></h4>`
|
|
315
|
-
if (schema.page.org.description) {
|
|
316
|
-
orgHtml += `<p>${schema.page.org.description}</p>`
|
|
317
|
-
}
|
|
318
|
-
orgHtml += '</div>'
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const html = template(schema, {
|
|
324
|
-
descriptionHtml: pad(descriptionHtml, 8, 1),
|
|
325
|
-
descriptionTxt: descriptionTxt,
|
|
326
|
-
gaHtml: pad(gaHtml, 2, 1),
|
|
327
|
-
jseeHtml: jseeHtml,
|
|
328
|
-
hiddenElementHtml: hiddenElementHtml,
|
|
329
|
-
socialHtml: pad(socialHtml, 2, 1),
|
|
330
|
-
orgHtml: pad(orgHtml, 2, 1),
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
if (returnHtml) {
|
|
334
|
-
// Return the html as a string
|
|
335
|
-
return html
|
|
336
|
-
} else if (outputs) {
|
|
337
|
-
// Store the html in the output file
|
|
338
|
-
for (let o of outputs) {
|
|
339
|
-
if (o === 'stdout') {
|
|
340
|
-
console.log(html)
|
|
341
|
-
} else if (o.includes('.html')) {
|
|
342
|
-
fs.writeFileSync(path.join(cwd, o), html)
|
|
343
|
-
} else if (o.includes('.json')) {
|
|
344
|
-
fs.writeFileSync(path.join(cwd, o), JSON.stringify(schema, null, 2))
|
|
345
|
-
} else if (o.includes('.md')) {
|
|
346
|
-
fs.writeFileSync(path.join(cwd, o), genMarkdownFromSchema(schema))
|
|
347
|
-
} else {
|
|
348
|
-
console.error('Invalid output file:', o)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
fs.writeFileSync(path.join(cwd, outputs[0]), html)
|
|
352
|
-
} else {
|
|
353
|
-
// Serve the html
|
|
354
|
-
const express = require('express')
|
|
355
|
-
const app = express()
|
|
356
|
-
app.use(express.json())
|
|
357
|
-
if (argv.execute) {
|
|
358
|
-
// Create post endpoint for executing the model
|
|
359
|
-
schema.model.forEach(m => {
|
|
360
|
-
app.post(m.url, (req, res) => {
|
|
361
|
-
console.log(`Executing model: ${m.name}`)
|
|
362
|
-
if (m.name in modelFuncs) {
|
|
363
|
-
const modelFunc = modelFuncs[m.name]
|
|
364
|
-
try {
|
|
365
|
-
const result = modelFunc(req.body)
|
|
366
|
-
res.json(result)
|
|
367
|
-
} catch (error) {
|
|
368
|
-
console.error('Error executing model:', error)
|
|
369
|
-
res.status(500).json({ error: error.message })
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
})
|
|
373
|
-
console.log('Model execution endpoints created:', m.url)
|
|
374
|
-
})
|
|
375
|
-
}
|
|
376
|
-
app.get('/', async (req, res) => {
|
|
377
|
-
console.log('Serving index.html')
|
|
378
|
-
res.send(await gen(process.argv, true))
|
|
379
|
-
})
|
|
380
|
-
app.get('/dist/jsee.runtime.js', (req, res) => {
|
|
381
|
-
const pathToJSEE = path.join(__dirname, '..', 'dist', 'jsee.runtime.js')
|
|
382
|
-
console.log(`Serving jsee.runtime.js from ${pathToJSEE}`)
|
|
383
|
-
res.sendFile(pathToJSEE)
|
|
384
|
-
})
|
|
385
|
-
app.use(express.static(cwd))
|
|
386
|
-
app.listen(argv.port, () => {
|
|
387
|
-
console.log(`JSEE app is running: http://localhost:${argv.port}`)
|
|
388
171
|
})
|
|
389
172
|
}
|
|
173
|
+
return data
|
|
390
174
|
}
|
|
391
175
|
|
|
392
176
|
function genSchema (jsdocData) {
|
|
@@ -640,7 +424,7 @@ function template(schema, blocks) {
|
|
|
640
424
|
pre { padding: 8px 12px; overflow-x: auto; }
|
|
641
425
|
pre > code { border: 0; padding-right: 0; padding-left: 0; }
|
|
642
426
|
.wrapper { max-width: calc(800px - (30px)); margin-right: auto; margin-left: auto; padding-right: 15px; padding-left: 15px; }
|
|
643
|
-
@media screen and (min-width: 800px) { .wrapper { max-width: calc(
|
|
427
|
+
@media screen and (min-width: 800px) { .wrapper { max-width: calc(1024px - (30px * 2)); padding-right: 30px; padding-left: 30px; } }
|
|
644
428
|
.wrapper:after { content: ""; display: table; clear: both; }
|
|
645
429
|
.orange { color: #f66a0a; }
|
|
646
430
|
.grey { color: #828282; }
|
|
@@ -783,4 +567,481 @@ function template(schema, blocks) {
|
|
|
783
567
|
</html>`
|
|
784
568
|
}
|
|
785
569
|
|
|
786
|
-
|
|
570
|
+
async function gen (pargv, returnHtml=false) {
|
|
571
|
+
// Determine if JSEE CLI is imported or run directly
|
|
572
|
+
const imported = path.dirname(__dirname) !== path.dirname(require.main.path)
|
|
573
|
+
|
|
574
|
+
// First pass over CLI arguments
|
|
575
|
+
// JSEE-level args
|
|
576
|
+
const argvAlias = {
|
|
577
|
+
inputs: 'i',
|
|
578
|
+
outputs: 'o',
|
|
579
|
+
description: 'd',
|
|
580
|
+
port: 'p',
|
|
581
|
+
version: 'v',
|
|
582
|
+
fetch: 'f',
|
|
583
|
+
execute: 'e',
|
|
584
|
+
cdn: 'c',
|
|
585
|
+
runtime: 'r',
|
|
586
|
+
}
|
|
587
|
+
const argvDefault = {
|
|
588
|
+
execute: false, // execute the model code on the server
|
|
589
|
+
fetch: false, // fetch the JSEE runtime from the CDN or local server
|
|
590
|
+
inputs: 'schema.json', // default input is schema.json in the current working directory
|
|
591
|
+
port: 3000, // default port for the server
|
|
592
|
+
version: 'latest', // default version of JSEE runtime to use
|
|
593
|
+
verbose: false, // verbose mode
|
|
594
|
+
cdn: false,
|
|
595
|
+
runtime: 'auto'
|
|
596
|
+
}
|
|
597
|
+
let argv = minimist(pargv, {
|
|
598
|
+
alias: argvAlias,
|
|
599
|
+
default: argvDefault,
|
|
600
|
+
boolean: ['help', 'h'],
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
if (argv.help || argv.h) {
|
|
604
|
+
console.log(`
|
|
605
|
+
Usage: jsee [schema.json] [options]
|
|
606
|
+
|
|
607
|
+
Options:
|
|
608
|
+
-i, --inputs <file> Input schema file (default: schema.json)
|
|
609
|
+
-o, --outputs <file> Output HTML file path
|
|
610
|
+
-d, --description <file> Markdown description file to include
|
|
611
|
+
-p, --port <number> Dev server port (default: 3000)
|
|
612
|
+
-v, --version <version> JSEE runtime version (default: latest)
|
|
613
|
+
-f, --fetch Fetch and bundle runtime + dependencies into output
|
|
614
|
+
-e, --execute Execute model server-side
|
|
615
|
+
-c, --cdn <url|bool> Rewrite model URLs for CDN deployment
|
|
616
|
+
-r, --runtime <mode> Runtime mode: auto|local|cdn|inline (default: auto)
|
|
617
|
+
--verbose Enable verbose logging
|
|
618
|
+
|
|
619
|
+
Examples:
|
|
620
|
+
jsee schema.json Start dev server with schema
|
|
621
|
+
jsee schema.json -o app.html Generate static HTML file
|
|
622
|
+
jsee schema.json -o app.html -f Generate self-contained HTML with bundled runtime
|
|
623
|
+
jsee -p 8080 Start dev server on port 8080
|
|
624
|
+
|
|
625
|
+
Documentation: https://jsee.org
|
|
626
|
+
`.trim())
|
|
627
|
+
return
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Set argv.inputs to the first non-option argument if it exists
|
|
631
|
+
if (!imported && argv._.length > 0 && !argv.inputs) {
|
|
632
|
+
argv.inputs = argv._[0]
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function log (...args) {
|
|
636
|
+
if (argv.verbose) {
|
|
637
|
+
console.log('[JSEE CLI]', ...args)
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
log('Imported:', imported)
|
|
642
|
+
log('Current working directory:', process.cwd())
|
|
643
|
+
log('Script location:', __dirname)
|
|
644
|
+
log('Script file:', __filename)
|
|
645
|
+
log('Require location:', require.main.path)
|
|
646
|
+
log('Require file:', require.main.filename)
|
|
647
|
+
|
|
648
|
+
let cwd = process.cwd()
|
|
649
|
+
let inputs = argv.inputs
|
|
650
|
+
let outputs = argv.outputs
|
|
651
|
+
let description = argv.description
|
|
652
|
+
let schema
|
|
653
|
+
let schemaPath
|
|
654
|
+
let descriptionTxt = ''
|
|
655
|
+
let descriptionHtml = ''
|
|
656
|
+
let jsdocMarkdown = ''
|
|
657
|
+
let modelFuncs = {}
|
|
658
|
+
|
|
659
|
+
// Determine the inputs and outputs
|
|
660
|
+
// if inputs is a string with js file names, split it into an array
|
|
661
|
+
if (typeof inputs === 'string') {
|
|
662
|
+
if (inputs.includes('.js')) {
|
|
663
|
+
inputs = inputs.split(',')
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// if outputs is a string with js file names, split it into an array
|
|
668
|
+
if (typeof outputs === 'string') {
|
|
669
|
+
outputs = outputs.split(',')
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (inputs.length === 0) {
|
|
673
|
+
console.error('No inputs provided')
|
|
674
|
+
process.exit(1)
|
|
675
|
+
} else if ((inputs.length === 1) && (inputs[0].includes('.json'))) {
|
|
676
|
+
// Input is json schema
|
|
677
|
+
// Curren working directory if not provided
|
|
678
|
+
// schema = require(path.join(cwd, inputs[0]))
|
|
679
|
+
// switch to fs.readFileSync to reload the schema if it changes
|
|
680
|
+
schemaPath = inputs[0].startsWith('/') ? inputs[0] : path.join(cwd, inputs[0])
|
|
681
|
+
schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
|
|
682
|
+
} else {
|
|
683
|
+
// Array of js files
|
|
684
|
+
// Generate schema
|
|
685
|
+
let jsdocData = jsdoc2md.getTemplateDataSync({ files: inputs.map(f => path.join(cwd, f)) })
|
|
686
|
+
schema = genSchema(jsdocData)
|
|
687
|
+
// jsdocMarkdown = jsdoc2md.renderSync({
|
|
688
|
+
// data: jsdocData,
|
|
689
|
+
// 'param-list-format': 'list',
|
|
690
|
+
// })
|
|
691
|
+
}
|
|
692
|
+
log('Schema path:', schemaPath)
|
|
693
|
+
|
|
694
|
+
// Second pass over CLI arguments
|
|
695
|
+
// Iterate over schema inputs and update argv aliases
|
|
696
|
+
if (schema.inputs) {
|
|
697
|
+
schema.inputs.forEach((inp, inp_index) => {
|
|
698
|
+
if (inp.name) {
|
|
699
|
+
const inputName = sanitizeName(inp.name)
|
|
700
|
+
if (inp.alias) {
|
|
701
|
+
argvAlias[inputName] = inp.alias
|
|
702
|
+
}
|
|
703
|
+
// Use positional arguments as schema inputs defaults if JSEE CLI is imported
|
|
704
|
+
if (imported && argv._.length > inp_index) {
|
|
705
|
+
log('Using positional argument for input:', inputName, argv._[inp_index])
|
|
706
|
+
argvDefault[inputName] = argv._[inp_index]
|
|
707
|
+
}
|
|
708
|
+
// We don't need to duplicate defaults here, as we handle them on the frontend
|
|
709
|
+
// else if (inp.default) {
|
|
710
|
+
// argvDefault[inputName] = inp.default
|
|
711
|
+
// }
|
|
712
|
+
}
|
|
713
|
+
})
|
|
714
|
+
}
|
|
715
|
+
// Update argv with the new aliases and defaults
|
|
716
|
+
argv = minimist(pargv, {
|
|
717
|
+
alias: argvAlias,
|
|
718
|
+
default: argvDefault,
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
// Now deactivate the inputs present in argv
|
|
722
|
+
// If you set parameter on the command line, it should not be editable in the GUI
|
|
723
|
+
// E.g. file selected
|
|
724
|
+
const dataFromArgvWithoutFileLoading = getDataFromArgv(schema, argv, false)
|
|
725
|
+
log('Data from argv without file loading:', dataFromArgvWithoutFileLoading)
|
|
726
|
+
if (schema.inputs) {
|
|
727
|
+
schema.inputs.forEach(inp => {
|
|
728
|
+
// Here data contains unsanitized input names
|
|
729
|
+
if (inp.name in dataFromArgvWithoutFileLoading) {
|
|
730
|
+
inp.default = dataFromArgvWithoutFileLoading[inp.name]
|
|
731
|
+
inp.disabled = true // Deactivate the input if it's present in argv
|
|
732
|
+
}
|
|
733
|
+
})
|
|
734
|
+
}
|
|
735
|
+
log('Argv:', argv)
|
|
736
|
+
|
|
737
|
+
// Initially in argv.fetch branch
|
|
738
|
+
// Check if schema has model, convert to array if needed
|
|
739
|
+
if (!schema.model) {
|
|
740
|
+
// console.error('No model found in schema')
|
|
741
|
+
// process.exit(1)
|
|
742
|
+
// It's still valid schema, can be only render function or vis of inputs/outputs
|
|
743
|
+
schema.model = []
|
|
744
|
+
}
|
|
745
|
+
if (!Array.isArray(schema.model)) {
|
|
746
|
+
schema.model = [schema.model]
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Server-side execution
|
|
750
|
+
// If execute is true, we will prepare the model functions to run on the server side
|
|
751
|
+
// Schema model will be updated with the server url and POST method
|
|
752
|
+
if (argv.execute) {
|
|
753
|
+
await Promise.all(schema.model.map(async m => {
|
|
754
|
+
log('Preparing a model to run on the server side:', m.name, m.url)
|
|
755
|
+
const target = require(path.join(schemaPath ? path.dirname(schemaPath) : cwd, m.url))
|
|
756
|
+
modelFuncs[m.name] = await getModelFuncJS(m, target, {log})
|
|
757
|
+
m.type = 'post'
|
|
758
|
+
m.url = `/${m.name}`
|
|
759
|
+
m.worker = false
|
|
760
|
+
}))
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Switch to CDN for model files
|
|
764
|
+
if (argv.cdn) {
|
|
765
|
+
let cdn = ''
|
|
766
|
+
console.log(argv)
|
|
767
|
+
if (typeof argv.cdn === 'string') {
|
|
768
|
+
cdn = argv.cdn
|
|
769
|
+
} else if (typeof argv.cdn === 'boolean') {
|
|
770
|
+
// Check package.json in cwd
|
|
771
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
772
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
773
|
+
const target = require(packageJsonPath)
|
|
774
|
+
const packageName = target.name
|
|
775
|
+
cdn = `https://cdn.jsdelivr.net/npm/${packageName}@${target.version}/`
|
|
776
|
+
} else {
|
|
777
|
+
console.error(`No package.json found: ${packageJsonPath}`)
|
|
778
|
+
process.exit(1)
|
|
779
|
+
}
|
|
780
|
+
} else {
|
|
781
|
+
console.error('Invalid CDN argument. Use --cdn <url> or --cdn true to use package.json version.')
|
|
782
|
+
process.exit(1)
|
|
783
|
+
}
|
|
784
|
+
log('Using CDN for model files:', cdn)
|
|
785
|
+
schema.model.forEach(m => {
|
|
786
|
+
if (m.url) {
|
|
787
|
+
// If url is relative, make it absolute
|
|
788
|
+
if (!m.url.startsWith('http')) {
|
|
789
|
+
m.url = path.join(cdn, m.url)
|
|
790
|
+
log(`Updated ${m.name} model URL to: ${m.url}`)
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
})
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
log('Schema:', schema)
|
|
797
|
+
|
|
798
|
+
// Generate description block
|
|
799
|
+
if (description) {
|
|
800
|
+
const descriptionMd = fs.readFileSync(path.join(cwd, description), 'utf8')
|
|
801
|
+
descriptionHtml = converter.makeHtml(descriptionMd)
|
|
802
|
+
|
|
803
|
+
if (descriptionMd.includes('---')) {
|
|
804
|
+
descriptionTxt = descriptionMd
|
|
805
|
+
.split('---')[0]
|
|
806
|
+
.replace(/\n/g, ' ')
|
|
807
|
+
.replace(/\s+/g, ' ')
|
|
808
|
+
.replace(/#/g, '')
|
|
809
|
+
.replace(/\*/g, '')
|
|
810
|
+
.trim()
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
descriptionHtml += genHtmlFromSchema(schema)
|
|
814
|
+
|
|
815
|
+
// Generate jsee code
|
|
816
|
+
let jseeHtml = ''
|
|
817
|
+
let hiddenElementHtml = ''
|
|
818
|
+
const hasOutputs = Array.isArray(outputs) ? outputs.length > 0 : Boolean(outputs)
|
|
819
|
+
const runtimeMode = resolveRuntimeMode(argv.runtime, argv.fetch, hasOutputs)
|
|
820
|
+
if (argv.fetch) {
|
|
821
|
+
// Fetch jsee code from the CDN or local server
|
|
822
|
+
const jseeCode = await loadRuntimeCode(argv.version)
|
|
823
|
+
jseeHtml = `<script>${jseeCode}</script>`
|
|
824
|
+
// Fetch model files and store them in hidden elements
|
|
825
|
+
hiddenElementHtml += '<div id="hidden-storage" style="display: none;">'
|
|
826
|
+
|
|
827
|
+
const bundleBlocks = collectFetchBundleBlocks(schema)
|
|
828
|
+
for (let m of bundleBlocks) {
|
|
829
|
+
if (m.type === 'get' || m.type === 'post') {
|
|
830
|
+
continue
|
|
831
|
+
}
|
|
832
|
+
if (m.url) {
|
|
833
|
+
// Fetch model from the local file system (remote URLs not yet supported here)
|
|
834
|
+
const modelCode = fs.readFileSync(path.join(cwd, m.url), 'utf8')
|
|
835
|
+
hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${m.url}">${modelCode}</script>`
|
|
836
|
+
}
|
|
837
|
+
const imports = toArray(m.imports)
|
|
838
|
+
if (imports.length) {
|
|
839
|
+
m.imports = imports
|
|
840
|
+
for (let [index, i] of imports.entries()) {
|
|
841
|
+
if (typeof i !== 'string') {
|
|
842
|
+
continue
|
|
843
|
+
}
|
|
844
|
+
const importMeta = resolveFetchImport(i, m.url, cwd)
|
|
845
|
+
if (!importMeta) {
|
|
846
|
+
continue
|
|
847
|
+
}
|
|
848
|
+
m.imports[index] = importMeta.schemaEntry
|
|
849
|
+
|
|
850
|
+
let importCode
|
|
851
|
+
if (importMeta.localFilePath) {
|
|
852
|
+
importCode = fs.readFileSync(importMeta.localFilePath, 'utf8')
|
|
853
|
+
} else {
|
|
854
|
+
// Create cache directory if it doesn't exist
|
|
855
|
+
const cacheDir = path.join(os.homedir(), '.cache', 'jsee')
|
|
856
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
857
|
+
|
|
858
|
+
// Create a hash of the importUrl
|
|
859
|
+
const hash = crypto.createHash('sha256').update(importMeta.importUrl).digest('hex')
|
|
860
|
+
const cacheFilePath = path.join(cacheDir, `${hash}.js`)
|
|
861
|
+
|
|
862
|
+
let useCache = false
|
|
863
|
+
|
|
864
|
+
// Check if cache file exists and is less than 1 day old
|
|
865
|
+
if (fs.existsSync(cacheFilePath)) {
|
|
866
|
+
const stats = fs.statSync(cacheFilePath)
|
|
867
|
+
const mtime = new Date(stats.mtime)
|
|
868
|
+
const now = new Date()
|
|
869
|
+
const ageInDays = (now - mtime) / (1000 * 60 * 60 * 24)
|
|
870
|
+
|
|
871
|
+
if (ageInDays < 1) {
|
|
872
|
+
log('Using cached import:', importMeta.importUrl)
|
|
873
|
+
importCode = fs.readFileSync(cacheFilePath, 'utf8')
|
|
874
|
+
useCache = true
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
if (!useCache) {
|
|
879
|
+
const response = await fetch(importMeta.remoteUrl)
|
|
880
|
+
if (!response.ok) {
|
|
881
|
+
console.error(`Failed to fetch ${importMeta.remoteUrl}: ${response.statusText}`)
|
|
882
|
+
process.exit(1)
|
|
883
|
+
}
|
|
884
|
+
importCode = await response.text()
|
|
885
|
+
fs.writeFileSync(cacheFilePath, importCode, 'utf8')
|
|
886
|
+
log('Fetched and stored to cache:', importMeta.importUrl)
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${importMeta.importUrl}">${importCode}</script>`
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
hiddenElementHtml += '</div>'
|
|
894
|
+
} else {
|
|
895
|
+
if (runtimeMode === 'inline') {
|
|
896
|
+
const jseeCode = await loadRuntimeCode(argv.version)
|
|
897
|
+
jseeHtml = `<script>${jseeCode}</script>`
|
|
898
|
+
} else if (runtimeMode === 'cdn') {
|
|
899
|
+
jseeHtml = `<script src="https://cdn.jsdelivr.net/npm/@jseeio/jsee@${argv.version}/dist/jsee.runtime.js"></script>`
|
|
900
|
+
} else {
|
|
901
|
+
jseeHtml = argv.version === 'dev'
|
|
902
|
+
? `<script src="http://localhost:${argv.port}/dist/jsee.js"></script>`
|
|
903
|
+
: `<script src="http://localhost:${argv.port}/dist/jsee.runtime.js"></script>`
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
let socialHtml = ''
|
|
908
|
+
let gaHtml = ''
|
|
909
|
+
let orgHtml = ''
|
|
910
|
+
|
|
911
|
+
if (schema.page) {
|
|
912
|
+
if (schema.page.ga) {
|
|
913
|
+
gaHtml = `
|
|
914
|
+
<script id="ga-src" async src="https://www.googletagmanager.com/gtag/js?id=${schema.page.ga}"></script>
|
|
915
|
+
<script id="ga-body">
|
|
916
|
+
window['ga-disable-${schema.page.ga}'] = window.doNotTrack === "1" || navigator.doNotTrack === "1" || navigator.doNotTrack === "yes" || navigator.msDoNotTrack === "1";
|
|
917
|
+
window.dataLayer = window.dataLayer || [];
|
|
918
|
+
function gtag(){dataLayer.push(arguments);}
|
|
919
|
+
gtag('js', new Date());
|
|
920
|
+
gtag('config', '${schema.page.ga}');
|
|
921
|
+
</script>
|
|
922
|
+
`
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// Social media links
|
|
926
|
+
if (schema.page.social) {
|
|
927
|
+
// iterate over dict with k, v pairs
|
|
928
|
+
for (let [name, url] of Object.entries(schema.page.social)) {
|
|
929
|
+
switch (name) {
|
|
930
|
+
case 'twitter':
|
|
931
|
+
socialHtml += `<li><a rel="me" href="https://twitter.com/${url}">Twitter</a></li>`
|
|
932
|
+
break
|
|
933
|
+
case 'github':
|
|
934
|
+
socialHtml += `<li><a rel="me" href="https://github.com/${url}">GitHub</a></li>`
|
|
935
|
+
break
|
|
936
|
+
case 'facebook':
|
|
937
|
+
socialHtml += `<li><a rel="me" href="https://www.facebook.com/${url}">Facebook</a></li>`
|
|
938
|
+
break
|
|
939
|
+
case 'linkedin':
|
|
940
|
+
socialHtml += `<li><a rel="me" href="https://www.linkedin.com/company/${url}">LinkedIn</a></li>`
|
|
941
|
+
break
|
|
942
|
+
case 'instagram':
|
|
943
|
+
socialHtml += `<li><a rel="me" href="https://www.instagram.com/${url}">Instagram</a></li>`
|
|
944
|
+
break
|
|
945
|
+
case 'youtube':
|
|
946
|
+
socialHtml += `<li><a rel="me" href="https://www.youtube.com/${url}">YouTube</a></li>`
|
|
947
|
+
break
|
|
948
|
+
default:
|
|
949
|
+
socialHtml += `<li><a rel="me" href="${url}">${name}</a></li>`
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (schema.page.org) {
|
|
955
|
+
orgHtml = `<div class="footer-org"><h4 class="footer-heading"><a href="${schema.page.org.url}">${schema.page.org.name}</a></h4>`
|
|
956
|
+
if (schema.page.org.description) {
|
|
957
|
+
orgHtml += `<p>${schema.page.org.description}</p>`
|
|
958
|
+
}
|
|
959
|
+
orgHtml += '</div>'
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
const html = template(schema, {
|
|
965
|
+
descriptionHtml: pad(descriptionHtml, 8, 1),
|
|
966
|
+
descriptionTxt: descriptionTxt,
|
|
967
|
+
gaHtml: pad(gaHtml, 2, 1),
|
|
968
|
+
jseeHtml: jseeHtml,
|
|
969
|
+
hiddenElementHtml: hiddenElementHtml,
|
|
970
|
+
socialHtml: pad(socialHtml, 2, 1),
|
|
971
|
+
orgHtml: pad(orgHtml, 2, 1),
|
|
972
|
+
})
|
|
973
|
+
|
|
974
|
+
if (returnHtml) {
|
|
975
|
+
// Return the html as a string
|
|
976
|
+
return html
|
|
977
|
+
} else if (outputs) {
|
|
978
|
+
// Store the html in the output file
|
|
979
|
+
for (let o of outputs) {
|
|
980
|
+
if (o === 'stdout') {
|
|
981
|
+
log(html)
|
|
982
|
+
} else if (o.includes('.html')) {
|
|
983
|
+
fs.writeFileSync(resolveOutputPath(cwd, o), html)
|
|
984
|
+
} else if (o.includes('.json')) {
|
|
985
|
+
fs.writeFileSync(resolveOutputPath(cwd, o), JSON.stringify(schema, null, 2))
|
|
986
|
+
} else if (o.includes('.md')) {
|
|
987
|
+
fs.writeFileSync(resolveOutputPath(cwd, o), genMarkdownFromSchema(schema))
|
|
988
|
+
} else {
|
|
989
|
+
console.error('Invalid output file:', o)
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} else {
|
|
993
|
+
// Serve the html
|
|
994
|
+
const express = require('express')
|
|
995
|
+
const app = express()
|
|
996
|
+
app.use(express.json())
|
|
997
|
+
if (argv.execute) {
|
|
998
|
+
// Create post endpoint for executing the model
|
|
999
|
+
schema.model.forEach(m => {
|
|
1000
|
+
app.post(m.url, (req, res) => {
|
|
1001
|
+
log(`Executing model: ${m.name}`)
|
|
1002
|
+
if (m.name in modelFuncs) {
|
|
1003
|
+
const modelFunc = modelFuncs[m.name]
|
|
1004
|
+
try {
|
|
1005
|
+
const dataFromArgv = getDataFromArgv(schema, argv)
|
|
1006
|
+
const dataFromGUI = req.body
|
|
1007
|
+
const data = { ...dataFromGUI, ...dataFromArgv }
|
|
1008
|
+
log('Data for model execution:', data)
|
|
1009
|
+
const result = modelFunc(data)
|
|
1010
|
+
res.json(result)
|
|
1011
|
+
log(`Model ${m.name} executed successfully: `, result)
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
console.error('Error executing model:', error)
|
|
1014
|
+
res.status(500).json({ error: error.message })
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
})
|
|
1018
|
+
log('Model execution endpoints created:', m.url)
|
|
1019
|
+
})
|
|
1020
|
+
}
|
|
1021
|
+
app.get('/', async (req, res) => {
|
|
1022
|
+
log('Serving index.html')
|
|
1023
|
+
res.send(await gen(pargv, true))
|
|
1024
|
+
})
|
|
1025
|
+
// app.get('/dist/jsee.runtime.js', (req, res) => {
|
|
1026
|
+
// // __dirname points to this file location (it's jsee/src/cli.js, likely in node_modules)
|
|
1027
|
+
// // so we need to go up one level to get to the dist folder with jsee.runtime.js
|
|
1028
|
+
// const pathToJSEE = path.join(__dirname, '..', 'dist', 'jsee.runtime.js')
|
|
1029
|
+
// log(`Serving jsee.runtime.js from: ${pathToJSEE}`)
|
|
1030
|
+
// res.sendFile(pathToJSEE)
|
|
1031
|
+
// })
|
|
1032
|
+
app.use('/dist', express.static(path.join(__dirname, '..', 'dist'))) // Serve static files from the dist folder
|
|
1033
|
+
// app.use(express.static(cwd))
|
|
1034
|
+
// app.use(express.static(require.main.path)) // Serve static files from the main module path
|
|
1035
|
+
// app.use(express.static(path.join(require.main.path, '..'))) // Serve static files from the parent directory of the main module path
|
|
1036
|
+
app.use(express.static(schemaPath ? path.dirname(schemaPath) : cwd)) // Serve static files from the schema path or current working directory
|
|
1037
|
+
app.listen(argv.port, () => {
|
|
1038
|
+
console.log(`JSEE app is running: http://localhost:${argv.port}`)
|
|
1039
|
+
})
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
module.exports = gen
|
|
1044
|
+
module.exports.collectFetchBundleBlocks = collectFetchBundleBlocks
|
|
1045
|
+
module.exports.resolveFetchImport = resolveFetchImport
|
|
1046
|
+
module.exports.resolveRuntimeMode = resolveRuntimeMode
|
|
1047
|
+
module.exports.resolveOutputPath = resolveOutputPath
|