@jseeio/jsee 0.8.1 → 0.8.7

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/src/cli.js CHANGED
@@ -2,10 +2,11 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const os = require('os')
4
4
  const crypto = require('crypto')
5
+ const Module = require('module')
5
6
 
6
7
  const minimist = require('minimist')
7
8
 
8
- const { getModelFuncJS, sanitizeName, generateOpenAPISpec, serializeResult, parseMultipart, fileExtToOutputType } = require('./utils.js')
9
+ const { getModelFuncJS, sanitizeName, isRecordObject, normalizeFileOutputValue, generateOpenAPISpec, serializeResult, parseMultipart, fileExtToOutputType } = require('./utils.js')
9
10
 
10
11
  let jsdoc2md
11
12
  function getJsdocToMarkdown () {
@@ -69,6 +70,137 @@ function readDirListing (resolved, relative) {
69
70
  }))
70
71
  }
71
72
 
73
+ function isPackageSpecifier (value) {
74
+ if (typeof value !== 'string') return false
75
+ if (!value || value.startsWith('.') || value.startsWith('/') || value.startsWith('file:')) return false
76
+ if (isHttpUrl(value)) return false
77
+ if (value.includes('\\')) return false
78
+ if (value.startsWith('@')) {
79
+ return /^@[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/.test(value)
80
+ }
81
+ return /^[A-Za-z0-9._-]+$/.test(value)
82
+ }
83
+
84
+ function looksLikeMissingPackageInput (value, cwd) {
85
+ if (!isPackageSpecifier(value)) return false
86
+ if (value.includes('.json') || value.includes('.js')) return false
87
+ return !fs.existsSync(path.resolve(cwd, value))
88
+ }
89
+
90
+ function getPackageInputInstallHint (specifier) {
91
+ return `npx -p @jseeio/jsee -p ${specifier} jsee ${specifier} --serve`
92
+ }
93
+
94
+ function resolvePackageJsonPath (specifier, cwd) {
95
+ const resolver = Module.createRequire(path.join(cwd, '__jsee_resolve__.js'))
96
+ const searchPaths = resolver.resolve.paths(specifier) || []
97
+
98
+ for (const base of searchPaths) {
99
+ const candidate = path.join(base, specifier, 'package.json')
100
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate
101
+ }
102
+
103
+ try {
104
+ return require.resolve(`${specifier}/package.json`, { paths: [cwd] })
105
+ } catch (error) {
106
+ return null
107
+ }
108
+ }
109
+
110
+ function readJseePackageInput (specifier, packageJsonPath) {
111
+ const packageRoot = path.dirname(packageJsonPath)
112
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
113
+ const appConfig = packageJson.jsee
114
+ let schemaRel
115
+ let descriptionRel
116
+
117
+ if (typeof appConfig === 'string') {
118
+ schemaRel = appConfig
119
+ } else if (appConfig && typeof appConfig === 'object') {
120
+ schemaRel = appConfig.schema || appConfig.input || appConfig.inputs
121
+ descriptionRel = appConfig.description
122
+ }
123
+
124
+ if (!schemaRel) {
125
+ throw new Error(`${specifier} does not declare a JSEE app. Add "jsee": "schema.json" to its package.json.`)
126
+ }
127
+
128
+ const schemaPath = path.resolve(packageRoot, schemaRel)
129
+ if (!fs.existsSync(schemaPath) || !fs.statSync(schemaPath).isFile()) {
130
+ throw new Error(`${specifier} declares JSEE schema ${schemaRel}, but the file was not found.`)
131
+ }
132
+
133
+ let descriptionPath = null
134
+ if (descriptionRel) {
135
+ descriptionPath = path.resolve(packageRoot, descriptionRel)
136
+ } else {
137
+ const readmePath = path.join(packageRoot, 'README.md')
138
+ if (fs.existsSync(readmePath) && fs.statSync(readmePath).isFile()) descriptionPath = readmePath
139
+ }
140
+
141
+ return {
142
+ packageName: packageJson.name || specifier,
143
+ packageRoot,
144
+ schemaPath,
145
+ descriptionPath
146
+ }
147
+ }
148
+
149
+ function resolveJseePackageInput (specifier, cwd) {
150
+ if (!isPackageSpecifier(specifier)) return null
151
+
152
+ const packageJsonPath = resolvePackageJsonPath(specifier, cwd)
153
+ if (!packageJsonPath) return null
154
+
155
+ return readJseePackageInput(specifier, packageJsonPath)
156
+ }
157
+
158
+ function findPackageRoot (startDir) {
159
+ let dir = path.resolve(startDir)
160
+ if (fs.existsSync(dir) && fs.statSync(dir).isFile()) dir = path.dirname(dir)
161
+
162
+ while (true) {
163
+ const packageJsonPath = path.join(dir, 'package.json')
164
+ if (fs.existsSync(packageJsonPath) && fs.statSync(packageJsonPath).isFile()) return dir
165
+
166
+ const parent = path.dirname(dir)
167
+ if (parent === dir) throw new Error(`No package.json found from ${startDir}`)
168
+ dir = parent
169
+ }
170
+ }
171
+
172
+ function hasArgOption (args, names) {
173
+ return args.some(arg => names.some(name => arg === name || arg.startsWith(name + '=')))
174
+ }
175
+
176
+ async function runPackage (dirname, args=[]) {
177
+ if (!Array.isArray(args)) throw new Error('runPackage args must be an array')
178
+
179
+ const packageRoot = findPackageRoot(dirname)
180
+ const packageInput = readJseePackageInput(packageRoot, path.join(packageRoot, 'package.json'))
181
+ const pargv = []
182
+
183
+ if (!hasArgOption(args, ['--inputs', '-i'])) {
184
+ pargv.push('--inputs', packageInput.schemaPath)
185
+ }
186
+ if (!hasArgOption(args, ['--description', '-d']) && packageInput.descriptionPath) {
187
+ pargv.push('--description', packageInput.descriptionPath)
188
+ }
189
+ pargv.push(...args)
190
+
191
+ if (hasArgOption(args, ['--run'])) {
192
+ return await gen(pargv)
193
+ }
194
+
195
+ const previousCwd = process.cwd()
196
+ process.chdir(packageInput.packageRoot)
197
+ try {
198
+ return await gen(pargv)
199
+ } finally {
200
+ process.chdir(previousCwd)
201
+ }
202
+ }
203
+
72
204
  function generateIdentitySchema (target, cwd) {
73
205
  const resolved = path.isAbsolute(target) ? target : path.join(cwd, target)
74
206
  const stat = fs.existsSync(resolved) ? fs.statSync(resolved) : null
@@ -259,6 +391,510 @@ function resolveOutputPath (cwd, outputPath) {
259
391
  return path.join(cwd, outputPath)
260
392
  }
261
393
 
394
+ function writeOutputFile (cwd, outputPath, content) {
395
+ const resolved = resolveOutputPath(cwd, outputPath)
396
+ fs.mkdirSync(path.dirname(resolved), { recursive: true })
397
+ fs.writeFileSync(resolved, content)
398
+ }
399
+
400
+ // Run mode helpers
401
+ function getInputDefaultValue (input) {
402
+ if (!input || typeof input !== 'object') return undefined
403
+ if (input.type === 'group' && Array.isArray(input.elements)) {
404
+ const value = {}
405
+ input.elements.forEach(element => {
406
+ const elementValue = getInputDefaultValue(element)
407
+ if (typeof elementValue !== 'undefined' && element.name) value[element.name] = elementValue
408
+ })
409
+ return value
410
+ }
411
+ if (Object.prototype.hasOwnProperty.call(input, 'default')) return input.default
412
+
413
+ switch (input.type) {
414
+ case 'int':
415
+ case 'float':
416
+ case 'number':
417
+ return 0
418
+ case 'string':
419
+ case 'text':
420
+ return ''
421
+ case 'color':
422
+ return '#000000'
423
+ case 'categorical':
424
+ case 'select':
425
+ case 'radio':
426
+ return input.options && input.options.length ? input.options[0] : ''
427
+ case 'bool':
428
+ case 'checkbox':
429
+ case 'toggle':
430
+ return false
431
+ case 'multi-select':
432
+ return []
433
+ case 'range':
434
+ return [input.min || 0, input.max || 100]
435
+ case 'slider':
436
+ return input.min || 0
437
+ case 'folder':
438
+ return input.default || []
439
+ case 'file':
440
+ return ''
441
+ default:
442
+ return undefined
443
+ }
444
+ }
445
+
446
+ function getSchemaInputDefaults (schema) {
447
+ const data = {}
448
+ if (!schema.inputs) return data
449
+ schema.inputs.forEach(input => {
450
+ if (!input.name) return
451
+ const value = getInputDefaultValue(input)
452
+ if (typeof value !== 'undefined') data[input.name] = value
453
+ })
454
+ return data
455
+ }
456
+
457
+ function loadBrowserStyleModelFunction (src, model, modelPath) {
458
+ const modelName = getModelExportName(model)
459
+ const localRequire = modelPath
460
+ ? Module.createRequire(modelPath)
461
+ : require
462
+ const moduleShim = { exports: {} }
463
+ const dirname = modelPath ? path.dirname(modelPath) : process.cwd()
464
+ const filename = modelPath || path.join(dirname, '__jsee_model__.js')
465
+
466
+ const fn = new Function(
467
+ 'require',
468
+ 'module',
469
+ 'exports',
470
+ '__filename',
471
+ '__dirname',
472
+ `${src}
473
+ return typeof ${toJsIdentifier(modelName)} === 'function' ? ${toJsIdentifier(modelName)} : (module.exports && (module.exports.default || module.exports[${JSON.stringify(modelName)}] || module.exports))`
474
+ )
475
+ const target = fn(localRequire, moduleShim, moduleShim.exports, filename, dirname)
476
+ if (typeof target === 'function') return target
477
+ if (target && typeof target === 'object') {
478
+ if (typeof target[modelName] === 'function') return target[modelName]
479
+ if (typeof target.default === 'function') return target.default
480
+ }
481
+ return target
482
+ }
483
+
484
+ function resolveExportedTarget (target, model) {
485
+ const modelName = getModelExportName(model)
486
+ if (typeof target === 'function') return target
487
+ if (target && typeof target === 'object') {
488
+ if (typeof target[modelName] === 'function') return target[modelName]
489
+ if (typeof target.default === 'function') return target.default
490
+ }
491
+ return target
492
+ }
493
+
494
+ async function loadCliModelFunction (model, schemaPath, cwd, log) {
495
+ if (model.type === 'get' || model.type === 'post') {
496
+ throw new Error(`--run cannot execute remote ${model.type.toUpperCase()} model ${model.name || model.url}. Use a local JavaScript model.`)
497
+ }
498
+ if (model.type === 'py') {
499
+ throw new Error('--run currently supports local JavaScript models. Python model execution is available through the dev server/API path.')
500
+ }
501
+
502
+ let target
503
+ let modelPath = null
504
+
505
+ if (typeof model.code === 'function') {
506
+ target = model.code
507
+ } else if (typeof model.code === 'string' && model.code.trim()) {
508
+ try {
509
+ target = loadBrowserStyleModelFunction(model.code, model, null)
510
+ } catch (error) {
511
+ target = undefined
512
+ }
513
+ if (typeof target !== 'function') {
514
+ try {
515
+ target = new Function(`return (${model.code})`)()
516
+ } catch (error) {
517
+ // Keep the clearer error below.
518
+ }
519
+ }
520
+ } else if (model.url && !isHttpUrl(model.url)) {
521
+ modelPath = path.resolve(schemaPath ? path.dirname(schemaPath) : cwd, model.url)
522
+ target = resolveExportedTarget(require(modelPath), model)
523
+ if (typeof target !== 'function' && (!target || Object.keys(target).length === 0)) {
524
+ const src = fs.readFileSync(modelPath, 'utf-8')
525
+ target = loadBrowserStyleModelFunction(src, model, modelPath)
526
+ }
527
+ } else {
528
+ throw new Error(`--run requires local model code or a local model url for ${model.name || 'model'}`)
529
+ }
530
+
531
+ target = resolveExportedTarget(target, model)
532
+ if (typeof target !== 'function') {
533
+ throw new Error(`Could not load a callable function for model ${model.name || model.url || 'model'}`)
534
+ }
535
+
536
+ return getModelFuncJS(model, target, {
537
+ log,
538
+ progress: () => {},
539
+ isCancelled: () => false
540
+ })
541
+ }
542
+
543
+ async function runSchemaOnce (schema, data, schemaPath, cwd, log) {
544
+ let result = data
545
+ for (const model of schema.model) {
546
+ const modelFunc = await loadCliModelFunction(model, schemaPath, cwd, log)
547
+ const next = await modelFunc(result)
548
+ if (isRecordObject(result) && isRecordObject(next)) {
549
+ result = Object.assign({}, result, next)
550
+ } else if (typeof next !== 'undefined') {
551
+ result = next
552
+ }
553
+ if (isRecordObject(result) && result.stop) break
554
+ }
555
+ return result
556
+ }
557
+
558
+ function flattenOutputs (outputs, flat=[]) {
559
+ if (!Array.isArray(outputs)) return flat
560
+ outputs.forEach(output => {
561
+ if (output && output.type === 'group' && Array.isArray(output.elements)) {
562
+ flattenOutputs(output.elements, flat)
563
+ } else if (output) {
564
+ flat.push(output)
565
+ }
566
+ })
567
+ return flat
568
+ }
569
+
570
+ function cleanRunResultObject (result) {
571
+ if (!isRecordObject(result)) return result
572
+ const clean = Object.assign({}, result)
573
+ delete clean.caller
574
+ delete clean.stop
575
+ delete clean._status
576
+ delete clean._log
577
+ delete clean._progress
578
+ return clean
579
+ }
580
+
581
+ function getRunOutputValue (result, output) {
582
+ if (!isRecordObject(result)) return result
583
+ const names = [output.name, sanitizeName(output.name)]
584
+ if (output.alias) names.push(output.alias)
585
+ for (const name of names) {
586
+ if (name && Object.prototype.hasOwnProperty.call(result, name)) return result[name]
587
+ }
588
+ if (Object.prototype.hasOwnProperty.call(output, 'value')) return output.value
589
+ return undefined
590
+ }
591
+
592
+ function getRunOutputEntries (schema, result) {
593
+ const cleanResult = cleanRunResultObject(result)
594
+ const outputs = flattenOutputs(schema.outputs || [])
595
+ if (outputs.length) {
596
+ return outputs.map((output, index) => {
597
+ let value = getRunOutputValue(cleanResult, output)
598
+ if (typeof value === 'undefined' && outputs.length === 1 && !isRecordObject(cleanResult)) value = cleanResult
599
+ return {
600
+ output,
601
+ name: output.name || `output_${index + 1}`,
602
+ value
603
+ }
604
+ }).filter(entry => typeof entry.value !== 'undefined')
605
+ }
606
+
607
+ if (isRecordObject(cleanResult)) {
608
+ return Object.keys(cleanResult).map(key => ({
609
+ output: { name: key, type: typeof cleanResult[key] },
610
+ name: key,
611
+ value: cleanResult[key]
612
+ }))
613
+ }
614
+
615
+ return [{
616
+ output: { name: 'result', type: typeof cleanResult },
617
+ name: 'result',
618
+ value: cleanResult
619
+ }]
620
+ }
621
+
622
+ function getRunFileDescriptor (output, value) {
623
+ if (output.type !== 'file') {
624
+ return { value, filename: output.filename || output.name, mime: output.mime || output.contentType }
625
+ }
626
+ return normalizeFileOutputValue(output, value)
627
+ }
628
+
629
+ function extensionFromMime (mime) {
630
+ if (!mime || typeof mime !== 'string') return null
631
+ const clean = mime.split(';')[0].trim().toLowerCase()
632
+ const map = {
633
+ 'text/csv': '.csv',
634
+ 'text/plain': '.txt',
635
+ 'text/markdown': '.md',
636
+ 'text/html': '.html',
637
+ 'application/json': '.json',
638
+ 'application/pdf': '.pdf',
639
+ 'image/png': '.png',
640
+ 'image/jpeg': '.jpg',
641
+ 'image/svg+xml': '.svg'
642
+ }
643
+ return map[clean] || null
644
+ }
645
+
646
+ function extensionForRunOutput (output, value, descriptor) {
647
+ if (descriptor && descriptor.filename && path.extname(descriptor.filename)) return path.extname(descriptor.filename)
648
+ if (descriptor && descriptor.mime) {
649
+ const mimeExt = extensionFromMime(descriptor.mime)
650
+ if (mimeExt) return mimeExt
651
+ }
652
+
653
+ switch ((output.type || '').toLowerCase()) {
654
+ case 'file':
655
+ return '.txt'
656
+ case 'markdown':
657
+ return '.md'
658
+ case 'html':
659
+ return '.html'
660
+ case 'code':
661
+ case 'text':
662
+ case 'string':
663
+ case 'chat':
664
+ return '.txt'
665
+ case 'image':
666
+ return '.png'
667
+ case 'pdf':
668
+ return '.pdf'
669
+ default:
670
+ return '.json'
671
+ }
672
+ }
673
+
674
+ function getRunOutputFilename (entry) {
675
+ const descriptor = getRunFileDescriptor(entry.output, entry.value)
676
+ const base = descriptor.filename
677
+ ? path.basename(String(descriptor.filename))
678
+ : sanitizeName(entry.name || 'output')
679
+ if (path.extname(base)) return base
680
+ return base + extensionForRunOutput(entry.output, descriptor.value, descriptor)
681
+ }
682
+
683
+ function formatRunOutputContent (entry, forStdout=false) {
684
+ const descriptor = getRunFileDescriptor(entry.output, entry.value)
685
+ const value = descriptor.value
686
+ const type = (entry.output.type || '').toLowerCase()
687
+
688
+ if (Buffer.isBuffer(value)) return { content: value, addNewline: false }
689
+ if (value instanceof Uint8Array) return { content: Buffer.from(value), addNewline: false }
690
+
691
+ if (type === 'file') {
692
+ if (isRecordObject(value) || Array.isArray(value)) {
693
+ return { content: JSON.stringify(value, null, 2), addNewline: true }
694
+ }
695
+ return { content: typeof value === 'undefined' || value === null ? '' : String(value), addNewline: false }
696
+ }
697
+
698
+ if (['text', 'string', 'code', 'markdown', 'html', 'chat'].includes(type)) {
699
+ if (isRecordObject(value) || Array.isArray(value)) {
700
+ return { content: JSON.stringify(value, null, 2), addNewline: true }
701
+ }
702
+ return { content: typeof value === 'undefined' || value === null ? '' : String(value), addNewline: true }
703
+ }
704
+
705
+ if (typeof value === 'string' && forStdout && !['table', 'object', 'array', 'json'].includes(type)) {
706
+ return { content: value, addNewline: true }
707
+ }
708
+
709
+ return { content: JSON.stringify(value, null, 2), addNewline: true }
710
+ }
711
+
712
+ function writeRunContent (target, formatted) {
713
+ let content = formatted.content
714
+ if (typeof content === 'string' && formatted.addNewline && !content.endsWith('\n')) content += '\n'
715
+ fs.mkdirSync(path.dirname(target), { recursive: true })
716
+ fs.writeFileSync(target, content)
717
+ }
718
+
719
+ function writeRunStdout (formatted) {
720
+ let content = formatted.content
721
+ if (typeof content === 'string' && formatted.addNewline && !content.endsWith('\n')) content += '\n'
722
+ process.stdout.write(content)
723
+ }
724
+
725
+ function getRunOutputsDirectory (outputs, cwd) {
726
+ if (!outputs) return null
727
+ const value = Array.isArray(outputs) ? outputs[0] : outputs
728
+ if (value === true || typeof value !== 'string') {
729
+ throw new Error('--outputs in --run mode expects a directory path')
730
+ }
731
+ return resolveOutputPath(cwd, value)
732
+ }
733
+
734
+ function getRunOutputTarget (entry, argv, inputNames, cwd) {
735
+ const outputName = sanitizeName(entry.name)
736
+ const explicitKey = `output-${outputName}`
737
+ let value
738
+ if (Object.prototype.hasOwnProperty.call(argv, explicitKey)) {
739
+ value = argv[explicitKey]
740
+ } else if (!inputNames.has(outputName) && Object.prototype.hasOwnProperty.call(argv, outputName)) {
741
+ value = argv[outputName]
742
+ }
743
+ if (typeof value === 'undefined') return null
744
+ if (value === true || typeof value !== 'string') {
745
+ throw new Error(`Output target --${outputName} requires a file path`)
746
+ }
747
+ return resolveOutputPath(cwd, value)
748
+ }
749
+
750
+ function emitRunOutputs (schema, result, argv, outputs, cwd) {
751
+ const entries = getRunOutputEntries(schema, result)
752
+ const inputNames = new Set((schema.inputs || []).map(input => sanitizeName(input.name)))
753
+ const outputDir = getRunOutputsDirectory(outputs, cwd)
754
+ const targets = new Map()
755
+
756
+ entries.forEach(entry => {
757
+ const target = getRunOutputTarget(entry, argv, inputNames, cwd)
758
+ if (target) targets.set(entry.name, target)
759
+ })
760
+
761
+ if (outputDir) {
762
+ fs.mkdirSync(outputDir, { recursive: true })
763
+ entries.forEach(entry => {
764
+ const target = targets.get(entry.name) || path.join(outputDir, getRunOutputFilename(entry))
765
+ writeRunContent(target, formatRunOutputContent(entry))
766
+ })
767
+ return
768
+ }
769
+
770
+ if (targets.size) {
771
+ entries.forEach(entry => {
772
+ const target = targets.get(entry.name)
773
+ if (target) writeRunContent(target, formatRunOutputContent(entry))
774
+ })
775
+ return
776
+ }
777
+
778
+ if (entries.length === 1) {
779
+ writeRunStdout(formatRunOutputContent(entries[0], true))
780
+ return
781
+ }
782
+
783
+ const resultObject = {}
784
+ entries.forEach(entry => {
785
+ resultObject[entry.name] = entry.value
786
+ })
787
+ writeRunStdout({ content: JSON.stringify(resultObject, null, 2), addNewline: true })
788
+ }
789
+
790
+ let optionalEsbuild
791
+ function getOptionalEsbuild () {
792
+ if (optionalEsbuild) return optionalEsbuild
793
+ try {
794
+ optionalEsbuild = require('esbuild')
795
+ return optionalEsbuild
796
+ } catch (error) {
797
+ throw new Error('Bundling model dependencies requires optional dependency esbuild. Run `npm install esbuild` or use schema imports/prebundled browser code.')
798
+ }
799
+ }
800
+
801
+ function isJsIdentifier (value) {
802
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(value)
803
+ }
804
+
805
+ function toJsIdentifier (value, fallback='model') {
806
+ const raw = value ? String(value) : fallback
807
+ const clean = raw.replace(/[^A-Za-z0-9_$]/g, '_')
808
+ if (!clean) return fallback
809
+ return /^[A-Za-z_$]/.test(clean) ? clean : '_' + clean
810
+ }
811
+
812
+ function getModelExportName (model) {
813
+ if (model.name) return model.name
814
+ if (model.url) return path.basename(model.url).replace(/\.[^.]+$/, '')
815
+ return 'model'
816
+ }
817
+
818
+ function hasCommonJsExport (code) {
819
+ return /\bmodule\s*\.\s*exports\b|\bexports\s*\.\s*[A-Za-z_$]/.test(code)
820
+ }
821
+
822
+ function hasEsModuleExport (code) {
823
+ return /^\s*export\s+(?:default|function|class|const|let|var|\{)/m.test(code)
824
+ }
825
+
826
+ function hasStaticImport (code) {
827
+ return /^\s*import\s+(?!\()/m.test(code)
828
+ }
829
+
830
+ function hasRequireCall (code) {
831
+ return /\brequire\s*\(/.test(code)
832
+ }
833
+
834
+ function shouldBundleModelCode (code) {
835
+ return hasRequireCall(code) || hasStaticImport(code) || hasCommonJsExport(code) || hasEsModuleExport(code)
836
+ }
837
+
838
+ function buildBundledModelExposeCode (bundleGlobalName, modelName) {
839
+ return `
840
+ ;(function () {
841
+ var mod = ${bundleGlobalName}
842
+ var target = mod
843
+ if (mod && (typeof mod === 'object')) {
844
+ if (typeof mod[${JSON.stringify(modelName)}] !== 'undefined') {
845
+ target = mod[${JSON.stringify(modelName)}]
846
+ } else if (typeof mod.default !== 'undefined') {
847
+ target = mod.default
848
+ }
849
+ }
850
+ globalThis[${JSON.stringify(modelName)}] = target
851
+ })()
852
+ `
853
+ }
854
+
855
+ async function bundleModelCode (model, modelPath, code) {
856
+ if (!shouldBundleModelCode(code)) return code
857
+
858
+ const esbuild = getOptionalEsbuild()
859
+ const modelName = getModelExportName(model)
860
+ const bundleGlobalName = `__jsee_bundle_${toJsIdentifier(modelName)}`
861
+ const needsExportShim = modelName &&
862
+ !hasCommonJsExport(code) &&
863
+ !hasEsModuleExport(code) &&
864
+ (hasRequireCall(code) || hasStaticImport(code))
865
+
866
+ if (needsExportShim && !isJsIdentifier(modelName)) {
867
+ throw new Error(`Bundling ${model.url || modelPath} requires a valid JavaScript model name or an explicit module export.`)
868
+ }
869
+
870
+ const options = {
871
+ bundle: true,
872
+ write: false,
873
+ platform: 'browser',
874
+ format: 'iife',
875
+ globalName: bundleGlobalName,
876
+ logLevel: 'silent'
877
+ }
878
+
879
+ if (needsExportShim) {
880
+ options.stdin = {
881
+ contents: `${code}\nexport default ${modelName}\n`,
882
+ resolveDir: path.dirname(modelPath),
883
+ sourcefile: path.basename(modelPath),
884
+ loader: 'js'
885
+ }
886
+ } else {
887
+ options.entryPoints = [modelPath]
888
+ }
889
+
890
+ try {
891
+ const result = await esbuild.build(options)
892
+ return result.outputFiles[0].text + buildBundledModelExposeCode(bundleGlobalName, modelName)
893
+ } catch (error) {
894
+ throw new Error(`Failed to bundle ${model.url || modelPath}: ${error.message}`)
895
+ }
896
+ }
897
+
262
898
  const FULL_BUNDLE_TYPES = ['chart', '3d', 'map']
263
899
 
264
900
  function needsFullBundle (schema) {
@@ -596,10 +1232,18 @@ function template(schema, blocks) {
596
1232
  table th { background-color: #f0f0f0; border: 1px solid #e0e0e0; }
597
1233
  table td { border: 1px solid #e8e8e8; }
598
1234
  @media screen and (max-width: 800px) { table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; -ms-overflow-style: -ms-autohiding-scrollbar; } }
599
- .site-header { border-bottom: 1px solid #e8e8e8; min-height: 55.95px; line-height: 54px; position: relative; }
600
- .site-title { font-size: 1.625rem; font-weight: 800; letter-spacing: -1px; margin-bottom: 0; float: left; }
601
- @media screen and (max-width: 600px) { .site-title { padding-right: 45px; } }
1235
+ .site-header { border-bottom: 1px solid #e8e8e8; min-height: 55.95px; position: relative; }
1236
+ .site-header .wrapper { min-height: 54px; display: flex; align-items: center; justify-content: space-between; gap: 16px; }
1237
+ .site-header .wrapper:after { content: none; }
1238
+ .site-title { font-size: 1.625rem; font-weight: 800; letter-spacing: -1px; line-height: 1.2; margin-bottom: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
602
1239
  .site-title, .site-title:visited { color: #424242; }
1240
+ .jsee-header-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex: 0 0 auto; font-size: 12px; line-height: 1; }
1241
+ .jsee-header-badge, .jsee-header-button { border: 1px solid #e0e0e0; border-radius: 4px; background: #f8f8f8; color: #555; padding: 6px 10px; font: inherit; line-height: 1; white-space: nowrap; }
1242
+ .jsee-header-badge { font-family: Menlo, Inconsolata, Consolas, Roboto Mono, Ubuntu Mono, Liberation Mono, Courier New, monospace; }
1243
+ .jsee-header-button { cursor: pointer; }
1244
+ .jsee-header-button:hover { background: #f0f0f0; color: #111; }
1245
+ .jsee-header-toggle { display: flex; align-items: center; gap: 4px; color: #666; cursor: pointer; white-space: nowrap; }
1246
+ @media screen and (max-width: 600px) { .site-header .wrapper { flex-wrap: wrap; gap: 8px; padding-top: 10px; padding-bottom: 10px; } .site-title { flex: 1 1 100%; white-space: normal; } .jsee-header-actions { width: 100%; justify-content: flex-start; } }
603
1247
  .site-nav { position: absolute; top: 9px; right: 15px; background-color: #fdfdfd; border: 1px solid #e8e8e8; border-radius: 5px; text-align: right; }
604
1248
  .site-nav .nav-trigger { display: none; }
605
1249
  .site-nav .menu-icon { float: right; width: 36px; height: 26px; line-height: 0; padding-top: 10px; text-align: center; }
@@ -659,7 +1303,6 @@ function template(schema, blocks) {
659
1303
  @media screen and (min-width: 800px) { .one-half { width: calc(50% - (30px / 2)); } }
660
1304
  /** Jsee elements */
661
1305
  .app-container { background-color: #F0F1F4; border-bottom: 1px solid #e8e8e8; padding-bottom: 55px }
662
- #save-html-btn:hover { background-color: #f0f0f0 !important; }
663
1306
  .schema-description { background-color: #f8f8fa; padding: 20px; margin-top: 20px; border-radius: 10px; border: 1px solid #e8e8e8; }
664
1307
  .schema-description h2, .schema-description h3, .schema-description h4 { margin-top: 10px; }
665
1308
  /** Logos */
@@ -674,10 +1317,10 @@ function template(schema, blocks) {
674
1317
  </head>
675
1318
  <body>
676
1319
  ${blocks.hiddenElementHtml}
677
- ${blocks.serveBarHtml}
678
1320
  <header class="site-header">
679
1321
  <div class="wrapper">
680
1322
  <span class="site-title">${title}</span>
1323
+ ${blocks.headerRightHtml}
681
1324
  </div>
682
1325
  </header>
683
1326
  <div class="page-content app-container">
@@ -728,9 +1371,39 @@ function template(schema, blocks) {
728
1371
  env = new JSEE({ container: container, schema: currentSchema })
729
1372
  })
730
1373
  }
731
- var saveBtn = document.getElementById('save-html-btn')
732
- if (saveBtn) {
733
- saveBtn.addEventListener('click', function () { env.download("${title}") })
1374
+ var downloadBtn = document.getElementById('download-html-btn')
1375
+ if (downloadBtn) {
1376
+ var downloadTitle = ${JSON.stringify(title)}
1377
+ function formatHtmlSize(bytes) {
1378
+ if (bytes >= 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
1379
+ if (bytes >= 1024) return Math.round(bytes / 1024) + ' KB'
1380
+ return bytes + ' B'
1381
+ }
1382
+ function getDownloadHtml() {
1383
+ return '<!DOCTYPE html>\\n' + document.documentElement.outerHTML
1384
+ }
1385
+ function getDownloadFilename() {
1386
+ var base = (downloadTitle || 'jsee').toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '')
1387
+ return (base || 'jsee') + '.html'
1388
+ }
1389
+ function updateDownloadLabel() {
1390
+ var html = getDownloadHtml()
1391
+ var size = new Blob([html], { type: 'text/html' }).size
1392
+ downloadBtn.textContent = 'Download HTML - ' + formatHtmlSize(size)
1393
+ }
1394
+ downloadBtn.addEventListener('click', function () {
1395
+ var html = getDownloadHtml()
1396
+ var blob = new Blob([html], { type: 'text/html' })
1397
+ var url = URL.createObjectURL(blob)
1398
+ var a = document.createElement('a')
1399
+ a.href = url
1400
+ a.download = getDownloadFilename()
1401
+ document.body.appendChild(a)
1402
+ a.click()
1403
+ a.remove()
1404
+ setTimeout(function () { URL.revokeObjectURL(url) }, 0)
1405
+ })
1406
+ setTimeout(updateDownloadLabel, 0)
734
1407
  }
735
1408
  </script>
736
1409
  </body>
@@ -751,25 +1424,29 @@ async function gen (pargv, returnHtml=false) {
751
1424
  description: 'd',
752
1425
  port: 'p',
753
1426
  version: 'v',
754
- fetch: 'f',
1427
+ fetch: ['f', 'bundle', 'b'],
755
1428
  execute: 'e',
756
1429
  cdn: 'c',
757
1430
  runtime: 'r',
1431
+ serve: 's'
758
1432
  }
759
1433
  const argvDefault = {
760
1434
  execute: 'auto', // execute the model code on the server (auto = server-side when serving local .js models)
761
- fetch: false, // fetch the JSEE runtime from the CDN or local server
1435
+ fetch: false, // bundle runtime, local code and imports into generated output
762
1436
  inputs: 'schema.json', // default input is schema.json in the current working directory
763
1437
  port: 3000, // default port for the server
764
1438
  version: 'latest', // default version of JSEE runtime to use
765
1439
  verbose: false, // verbose mode
766
1440
  cdn: false,
767
- runtime: 'auto'
1441
+ runtime: 'auto',
1442
+ serve: false,
1443
+ run: false
768
1444
  }
1445
+ const booleanArgs = ['help', 'h', 'html', 'client', 'serve', 'run']
769
1446
  let argv = minimist(pargv, {
770
1447
  alias: argvAlias,
771
1448
  default: argvDefault,
772
- boolean: ['help', 'h', 'html', 'client'],
1449
+ boolean: booleanArgs,
773
1450
  })
774
1451
 
775
1452
  // ── jsee init <template> ──────────────────────────────────────────
@@ -907,7 +1584,7 @@ npx @jseeio/jsee schema.json
907
1584
 
908
1585
  if (argv.help || argv.h) {
909
1586
  console.log(`
910
- Usage: jsee [schema.json] [data...] [options]
1587
+ Usage: jsee [schema.json|package] [data...] [options]
911
1588
  jsee init [template] [--html]
912
1589
 
913
1590
  Commands:
@@ -920,7 +1597,10 @@ Options:
920
1597
  -d, --description <file> Markdown description file to include
921
1598
  -p, --port <number> Dev server port (default: 3000)
922
1599
  -v, --version <version> JSEE runtime version (default: latest)
923
- -f, --fetch Fetch and bundle runtime + dependencies into output
1600
+ -b, --bundle Bundle runtime + dependencies into output
1601
+ -f, --fetch Alias for --bundle
1602
+ -s, --serve Serve explicitly (default when no output is provided)
1603
+ --run Execute the model once and write pipeable outputs
924
1604
  -e, --execute Execute model server-side (auto-enabled when serving local .js models)
925
1605
  --client Force client-side execution (disable auto server-side)
926
1606
  -c, --cdn <url|bool> Rewrite model URLs for CDN deployment
@@ -938,11 +1618,14 @@ Examples:
938
1618
  jsee init chat Scaffold chat project
939
1619
  jsee init --html Generate single index.html
940
1620
  jsee schema.json Start dev server with schema
1621
+ jsee @scope/app --serve Start dev server from a JSEE app package
941
1622
  jsee schema.json 42 hello Pass positional data to first two inputs
942
1623
  jsee schema.json --a=100 --b=200 Pass named data inputs
943
1624
  jsee schema.json data.csv Pass a file path as input
944
1625
  jsee schema.json -o app.html Generate static HTML file
945
- jsee schema.json -o app.html -f Generate self-contained HTML with bundled runtime
1626
+ jsee schema.json -o app.html --bundle Generate self-contained HTML with bundled runtime
1627
+ jsee @statsim/gen --run --dataset Moons --format CSV --nSamples 500
1628
+ jsee @statsim/gen --run --dataset Moons --file moons.csv
946
1629
  jsee -p 8080 Start dev server on port 8080
947
1630
  jsee report.pdf Serve a PDF file (auto-detected viewer)
948
1631
  jsee data/ Serve a folder (file browser with preview)
@@ -952,14 +1635,18 @@ Documentation: https://jsee.org
952
1635
  return
953
1636
  }
954
1637
 
955
- // Check if first positional arg is a file or directory for identity serving
1638
+ // Check if first positional arg is a file/directory or JSEE app package.
956
1639
  let identityResult = null
1640
+ let packageInput = null
957
1641
  if (!imported && argv._.length > 0) {
958
1642
  identityResult = generateIdentitySchema(argv._[0], process.cwd())
1643
+ if (!identityResult) {
1644
+ packageInput = resolveJseePackageInput(argv._[0], process.cwd())
1645
+ }
959
1646
  }
960
1647
 
961
- // Set argv.inputs to the first positional argument if it looks like a schema/script
962
- if (!identityResult && !imported && argv._.length > 0 && argv.inputs === argvDefault.inputs) {
1648
+ // Set argv.inputs to the first positional argument if it looks like a schema/script/package.
1649
+ if (!identityResult && !packageInput && !imported && argv._.length > 0 && argv.inputs === argvDefault.inputs) {
963
1650
  argv.inputs = argv._[0]
964
1651
  }
965
1652
 
@@ -978,8 +1665,22 @@ Documentation: https://jsee.org
978
1665
 
979
1666
  let cwd = process.cwd()
980
1667
  let inputs = argv.inputs
981
- let outputs = argv.outputs
982
- let description = argv.description
1668
+ let outputs = argv.serve ? false : argv.outputs
1669
+ let description = argv.description || ''
1670
+
1671
+ if (packageInput || (!identityResult && typeof inputs === 'string')) {
1672
+ const resolvedPackageInput = packageInput || resolveJseePackageInput(inputs, cwd)
1673
+ if (resolvedPackageInput) {
1674
+ packageInput = resolvedPackageInput
1675
+ cwd = packageInput.packageRoot
1676
+ inputs = [packageInput.schemaPath]
1677
+ if (!description && packageInput.descriptionPath) {
1678
+ description = path.relative(cwd, packageInput.descriptionPath)
1679
+ }
1680
+ } else if (looksLikeMissingPackageInput(inputs, cwd)) {
1681
+ throw new Error(`Cannot resolve JSEE app package ${inputs}. Install it in this project or let npm provide both packages:\n ${getPackageInputInstallHint(inputs)}`)
1682
+ }
1683
+ }
983
1684
  let schema
984
1685
  let schemaPath
985
1686
  let descriptionTxt = ''
@@ -990,13 +1691,13 @@ Documentation: https://jsee.org
990
1691
  // Determine the inputs and outputs
991
1692
  // if inputs is a string with js file names, split it into an array
992
1693
  if (typeof inputs === 'string') {
993
- if (inputs.includes('.js')) {
1694
+ if (inputs.includes('.js') || inputs.includes('.json') || inputs.includes(',')) {
994
1695
  inputs = inputs.split(',')
995
1696
  }
996
1697
  }
997
1698
 
998
1699
  // if outputs is a string with js file names, split it into an array
999
- if (typeof outputs === 'string') {
1700
+ if (typeof outputs === 'string' && !argv.run) {
1000
1701
  outputs = outputs.split(',')
1001
1702
  }
1002
1703
 
@@ -1041,8 +1742,11 @@ Documentation: https://jsee.org
1041
1742
  schema.inputs.forEach((inp, inp_index) => {
1042
1743
  if (inp.name) {
1043
1744
  const inputName = sanitizeName(inp.name)
1044
- if (inp.alias) {
1045
- argvAlias[inputName] = inp.alias
1745
+ const inputAliases = []
1746
+ if (inp.alias) inputAliases.push(...toArray(inp.alias))
1747
+ if (inp.name !== inputName) inputAliases.push(inp.name)
1748
+ if (inputAliases.length) {
1749
+ argvAlias[inputName] = inputAliases
1046
1750
  }
1047
1751
  // Use positional arguments as schema input defaults
1048
1752
  if (imported && argv._.length > inp_index) {
@@ -1065,6 +1769,7 @@ Documentation: https://jsee.org
1065
1769
  argv = minimist(pargv, {
1066
1770
  alias: argvAlias,
1067
1771
  default: argvDefault,
1772
+ boolean: booleanArgs
1068
1773
  })
1069
1774
 
1070
1775
  // Now deactivate the inputs present in argv
@@ -1083,7 +1788,7 @@ Documentation: https://jsee.org
1083
1788
  }
1084
1789
  log('Argv:', argv)
1085
1790
 
1086
- // Initially in argv.fetch branch
1791
+ // Bundle/offline generation
1087
1792
  // Check if schema has model, convert to array if needed
1088
1793
  if (!schema.model) {
1089
1794
  // console.error('No model found in schema')
@@ -1095,6 +1800,17 @@ Documentation: https://jsee.org
1095
1800
  schema.model = [schema.model]
1096
1801
  }
1097
1802
 
1803
+ if (argv.run) {
1804
+ const data = Object.assign(
1805
+ {},
1806
+ getSchemaInputDefaults(schema),
1807
+ getDataFromArgv(schema, argv, true)
1808
+ )
1809
+ const result = await runSchemaOnce(schema, data, schemaPath, cwd, log)
1810
+ emitRunOutputs(schema, result, argv, outputs, process.cwd())
1811
+ return result
1812
+ }
1813
+
1098
1814
  // Resolve server-side execution mode
1099
1815
  const hasOutputs = Array.isArray(outputs) ? outputs.length > 0 : Boolean(outputs)
1100
1816
  let shouldExecute = argv.execute
@@ -1121,18 +1837,7 @@ Documentation: https://jsee.org
1121
1837
  if (shouldExecute) {
1122
1838
  await Promise.all(schema.model.map(async m => {
1123
1839
  log('Preparing a model to run on the server side:', m.name, m.url)
1124
- const modelPath = path.join(schemaPath ? path.dirname(schemaPath) : cwd, m.url)
1125
- let target = require(modelPath)
1126
- // Handle browser-style model files (no module.exports) — eval the source
1127
- // to extract the named function, similar to how the worker does it
1128
- if (typeof target !== 'function' && (!target || Object.keys(target).length === 0)) {
1129
- const src = fs.readFileSync(modelPath, 'utf-8')
1130
- const fn = new Function(src + `\nreturn typeof ${m.name} === 'function' ? ${m.name} : undefined`)()
1131
- if (typeof fn === 'function') {
1132
- target = fn
1133
- }
1134
- }
1135
- modelFuncs[m.name] = await getModelFuncJS(m, target, {log})
1840
+ modelFuncs[m.name] = await loadCliModelFunction(m, schemaPath, cwd, log)
1136
1841
  m.type = 'post'
1137
1842
  m.url = `/${m.name}`
1138
1843
  m.worker = false
@@ -1176,7 +1881,8 @@ Documentation: https://jsee.org
1176
1881
 
1177
1882
  // Generate description block
1178
1883
  if (description) {
1179
- const descriptionMd = fs.readFileSync(path.join(cwd, description), 'utf8')
1884
+ const descriptionPath = path.isAbsolute(description) ? description : path.join(cwd, description)
1885
+ const descriptionMd = fs.readFileSync(descriptionPath, 'utf8')
1180
1886
  descriptionHtml = getMarkdownConverter().render(descriptionMd)
1181
1887
 
1182
1888
  if (descriptionMd.includes('---')) {
@@ -1194,8 +1900,9 @@ Documentation: https://jsee.org
1194
1900
  // Generate jsee code
1195
1901
  let jseeHtml = ''
1196
1902
  let hiddenElementHtml = ''
1197
- const runtimeMode = resolveRuntimeMode(argv.runtime, argv.fetch, hasOutputs)
1198
- if (argv.fetch) {
1903
+ const shouldBundle = Boolean(argv.fetch)
1904
+ const runtimeMode = resolveRuntimeMode(argv.runtime, shouldBundle, hasOutputs)
1905
+ if (shouldBundle) {
1199
1906
  // Fetch jsee code from the CDN or local server
1200
1907
  const jseeCode = await loadRuntimeCode(argv.version, schema)
1201
1908
  jseeHtml = `<script>${jseeCode}</script>`
@@ -1209,7 +1916,10 @@ Documentation: https://jsee.org
1209
1916
  }
1210
1917
  if (m.url) {
1211
1918
  // Fetch model from the local file system (remote URLs not yet supported here)
1212
- const modelCode = fs.readFileSync(path.join(cwd, m.url), 'utf8')
1919
+ const modelPath = path.join(cwd, m.url)
1920
+ if (!m.name) m.name = getModelExportName(m)
1921
+ const modelSource = fs.readFileSync(modelPath, 'utf8')
1922
+ const modelCode = await bundleModelCode(m, modelPath, modelSource)
1213
1923
  hiddenElementHtml += `<script type="text/plain" style="display: none;" data-src="${m.url}">${modelCode}</script>`
1214
1924
  }
1215
1925
  const imports = toArray(m.imports)
@@ -1342,20 +2052,17 @@ Documentation: https://jsee.org
1342
2052
 
1343
2053
  }
1344
2054
 
1345
- // Build serve bar (only when serving, not for -o output)
1346
- let serveBarHtml = ''
2055
+ // Build right-side header status/action. Serve mode shows only runtime status; bundled files can download themselves.
2056
+ let headerRightHtml = ''
1347
2057
  let schemaScript = ''
1348
2058
  const isServing = !hasOutputs
1349
2059
  if (isServing) {
1350
2060
  const toggleHtml = shouldExecute
1351
- ? '<label style="cursor:pointer"><input type="checkbox" id="exec-toggle" style="margin-right:4px">Browser</label>'
2061
+ ? '<label class="jsee-header-toggle"><input type="checkbox" id="exec-toggle">Browser</label>'
1352
2062
  : ''
1353
- serveBarHtml = `<div id="jsee-serve-bar" style="background:#f8f8f8;border-bottom:1px solid #e0e0e0;padding:6px 15px;font-size:13px;color:#828282;display:flex;align-items:center;gap:16px">
1354
- <span style="font-family:monospace">localhost:${argv.port}</span>
1355
- <span style="flex:1"></span>
1356
- ${toggleHtml}
1357
- <button id="save-html-btn" style="background:none;border:1px solid #ddd;border-radius:3px;padding:3px 10px;font-size:12px;color:#555;cursor:pointer" title="Save as self-contained HTML file">Save HTML</button>
1358
- </div>`
2063
+ headerRightHtml = `<div class="jsee-header-actions">${toggleHtml}<span class="jsee-header-badge">localhost:${argv.port}</span></div>`
2064
+ } else if (shouldBundle) {
2065
+ headerRightHtml = '<div class="jsee-header-actions"><button id="download-html-btn" class="jsee-header-button" type="button" title="Download this standalone HTML file">Download HTML</button></div>'
1359
2066
  }
1360
2067
  if (shouldExecute) {
1361
2068
  const clientSchema = JSON.parse(JSON.stringify(schema))
@@ -1373,7 +2080,7 @@ Documentation: https://jsee.org
1373
2080
  hiddenElementHtml: hiddenElementHtml,
1374
2081
  socialHtml: pad(socialHtml, 2, 1),
1375
2082
  orgHtml: pad(orgHtml, 2, 1),
1376
- serveBarHtml: serveBarHtml,
2083
+ headerRightHtml: headerRightHtml,
1377
2084
  schemaScript: schemaScript,
1378
2085
  })
1379
2086
 
@@ -1386,11 +2093,11 @@ Documentation: https://jsee.org
1386
2093
  if (o === 'stdout') {
1387
2094
  log(html)
1388
2095
  } else if (o.includes('.html')) {
1389
- fs.writeFileSync(resolveOutputPath(cwd, o), html)
2096
+ writeOutputFile(cwd, o, html)
1390
2097
  } else if (o.includes('.json')) {
1391
- fs.writeFileSync(resolveOutputPath(cwd, o), JSON.stringify(schema, null, 2))
2098
+ writeOutputFile(cwd, o, JSON.stringify(schema, null, 2))
1392
2099
  } else if (o.includes('.md')) {
1393
- fs.writeFileSync(resolveOutputPath(cwd, o), genMarkdownFromSchema(schema))
2100
+ writeOutputFile(cwd, o, genMarkdownFromSchema(schema))
1394
2101
  } else {
1395
2102
  console.error('Invalid output file:', o)
1396
2103
  }
@@ -1500,4 +2207,16 @@ module.exports.resolveLocalImportFile = resolveLocalImportFile
1500
2207
  module.exports.resolveFetchImport = resolveFetchImport
1501
2208
  module.exports.resolveRuntimeMode = resolveRuntimeMode
1502
2209
  module.exports.resolveOutputPath = resolveOutputPath
2210
+ module.exports.resolveJseePackageInput = resolveJseePackageInput
2211
+ module.exports.readJseePackageInput = readJseePackageInput
2212
+ module.exports.findPackageRoot = findPackageRoot
2213
+ module.exports.runPackage = runPackage
2214
+ module.exports.looksLikeMissingPackageInput = looksLikeMissingPackageInput
2215
+ module.exports.getPackageInputInstallHint = getPackageInputInstallHint
2216
+ module.exports.isPackageSpecifier = isPackageSpecifier
2217
+ module.exports.writeOutputFile = writeOutputFile
1503
2218
  module.exports.needsFullBundle = needsFullBundle
2219
+ module.exports.shouldBundleModelCode = shouldBundleModelCode
2220
+ module.exports.bundleModelCode = bundleModelCode
2221
+ module.exports.runSchemaOnce = runSchemaOnce
2222
+ module.exports.emitRunOutputs = emitRunOutputs