@lmjs/core 1.0.6 → 2.0.0

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.
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ // V2's own build pipeline — isolated from V1's index.php entirely, per the
3
+ // working-session decision (2026-09-13) to keep V2 fully separate rather
4
+ // than editing the live PHP CDN pipeline.
5
+ //
6
+ // Produces two bundles:
7
+ // - dist/lumenjs-core.js — dom-shim.js (a minimal jQuery-compatible
8
+ // layer) + the css/up workers + walk.js + _re.js + vendor/
9
+ // reconnecting-websocket.js + index-bootstrap.js + ws.js + lstnrs.js.
10
+ // No real jQuery, no plugins. This is the default every LumenJS V2
11
+ // page gets.
12
+ // - dist/lumenjs-core-with-plugins.js (+ dist/lumenjs-plugins.css) —
13
+ // 2026-09-14: the same file list, but with dom-shim.js swapped for
14
+ // real jQuery, plus the plugins confirmed by testing real production
15
+ // content (console.roxyon.com) end-to-end: Select2, Flickity,
16
+ // bootstrap-colorpicker, bootstrap-datetimepicker. See PROVENANCE.md
17
+ // for exactly how each one was identified (bea.js is minified with no
18
+ // identifying strings left — matched by API/option shape instead:
19
+ // `format: 'rgba'` for bootstrap-colorpicker, the DPGlobal/startView/
20
+ // minView/maxView/todayBtn signature for bootstrap-datetimepicker) and
21
+ // why these four and not the wider list an earlier version of this
22
+ // comment speculated (jQuery UI, tag-editor, jquery.lazy, ...) — none
23
+ // of those are actually called anywhere in any real project checked
24
+ // (10 real sites), and drag/sort (§6.4 of the spec) turned out to be
25
+ // custom-implemented, not jQuery-UI-based, so no jQuery UI dependency
26
+ // is needed at all.
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const less = require('less');
31
+ const esbuild = require('esbuild');
32
+ const pluginsRegistry = require('./plugins-registry.js');
33
+
34
+ const SRC = path.join(__dirname, '..', 'src');
35
+ const VENDOR = path.join(__dirname, '..', 'vendor');
36
+ const NODE_MODULES = path.join(__dirname, '..', 'node_modules');
37
+ const OUT_DIR = path.join(__dirname, '..', 'dist');
38
+
39
+ // 2026-09-16: this codebase's own source comments are deliberately
40
+ // detailed — they're the record of every real bug found this session
41
+ // (dated, narrative, sometimes referencing internal systems like
42
+ // console.roxyon.com) — genuinely valuable for maintaining THIS repo, but
43
+ // not appropriate to ship verbatim in a published npm package (this
44
+ // project has no other build/minify step, so without this, dist/*.js
45
+ // would carry every one of those comments as-is). esbuild's default
46
+ // transform (no minification flags at all) does NOT reliably strip every
47
+ // comment — confirmed empirically: a comment directly attached to a
48
+ // class method survives regardless of the `legalComments` setting
49
+ // (esbuild's printer treats it like retained method documentation).
50
+ // minifyWhitespace does reliably remove it (comments are folded into the
51
+ // same "removable formatting" pass as whitespace) — used here with
52
+ // minifyIdentifiers/minifySyntax left off, so real names stay readable
53
+ // for debugging; only formatting and comments are stripped. dist/*.js is
54
+ // already a generated, do-not-edit-directly build artifact, not
55
+ // something anyone reads directly, so the more compact output is a real
56
+ // bonus (smaller browser-shipped bundle) rather than a readability loss.
57
+ //
58
+ // Deliberately NOT applied inside read()/readVendor() themselves: the
59
+ // workers (workers/css.js, workers/up.js) are read as pre-escaped string
60
+ // PAYLOADS meant for embedding inside a template literal
61
+ // (`var _cssw = \`${read(...)}\`;`), not standalone parseable JS on their
62
+ // own — running them through a real JS parser fails outright (confirmed
63
+ // empirically: "Syntax error" on their own escaped backslash sequences).
64
+ // astring.js is already a minified third-party build with nothing to
65
+ // gain from re-stripping. Applied explicitly at each call site below
66
+ // instead, only for files that really are directly-concatenated,
67
+ // standalone top-level JS — and never to readModule() (real third-party
68
+ // npm packages), whose license headers must be preserved as-is.
69
+ function stripComments(src) {
70
+ return esbuild.transformSync(src, {
71
+ loader: 'js',
72
+ minifyWhitespace: true,
73
+ minifyIdentifiers: false,
74
+ minifySyntax: false,
75
+ }).code;
76
+ }
77
+
78
+ function read(relPath) {
79
+ return fs.readFileSync(path.join(SRC, relPath), 'utf8');
80
+ }
81
+
82
+ function readVendor(relPath) {
83
+ return fs.readFileSync(path.join(VENDOR, relPath), 'utf8');
84
+ }
85
+
86
+ function readModule(relPath) {
87
+ return fs.readFileSync(path.join(NODE_MODULES, relPath), 'utf8');
88
+ }
89
+
90
+ // The part of the bundle that's identical regardless of which jQuery layer
91
+ // (shim vs. real+plugins) sits underneath it. Takes the already-built
92
+ // jQuery layer as a string and appends everything else after it.
93
+ function buildCommonTail() {
94
+ const parts = [];
95
+
96
+ // WebWorker helper — a genuine small dependency, not jQuery-replaceable
97
+ // weight: lstnrs.js unconditionally does `new WebWorker('_cssw')` /
98
+ // `new WebWorker('_upw')` at load time. Depends on jQuery/our shim
99
+ // being defined already (its own IIFE takes it as an argument).
100
+ // Already minified third-party code — nothing to strip.
101
+ parts.push(readVendor('webworker-helper.js'));
102
+ // _re.js's walk() uses md5() to generate unique keys for :if/:for
103
+ // sections — a small, unrelated-to-jQuery utility, genuinely needed.
104
+ // Already minified third-party code — nothing to strip.
105
+ parts.push(readVendor('md5.js'));
106
+
107
+ // Workers are string payloads, not standalone JS — wrap exactly like
108
+ // production's index.php does (see PROVENANCE.md's wrapper note). Not
109
+ // run through stripComments(): these are pre-escaped string content
110
+ // meant for template-literal embedding, not directly-parseable JS on
111
+ // their own.
112
+ parts.push('var _cssw = `' + read('workers/css.js') + '`;');
113
+ parts.push('var _upw = `' + read('workers/up.js') + '`;');
114
+
115
+ // astring (2026-09-14, real bug fix): walk.js's getWatcher() calls
116
+ // astring.generate() to turn a rewritten AST back into source — but
117
+ // nothing was ever putting `astring` in scope for the shipped bundle
118
+ // (it's a plain npm package, referenced as a bare global, not
119
+ // require()'d). Any real .view <script> block would have hit
120
+ // "ReferenceError: astring is not defined" the moment it rendered.
121
+ // Vendored build (UMD, attaches `globalThis.astring`) from
122
+ // node_modules/astring/dist/astring.min.js — see PROVENANCE.md.
123
+ // Already minified third-party code — nothing to strip.
124
+ parts.push(readVendor('astring.js'));
125
+
126
+ parts.push(stripComments(read('walk.js')));
127
+ parts.push(stripComments(read('_re.js')));
128
+
129
+ // ws.js's `_Live` class needs ReconnectingWebSocket — a genuine small
130
+ // vendor dependency (not jQuery-related, nothing to shim), vendored
131
+ // locally so this build has no dependency on the old V1 mirror at all.
132
+ // This file is itself part-third-party (the real ReconnectingWebSocket,
133
+ // already minified with no comments) and part our own framework code
134
+ // (Reactor(), the setAttribute/getAttribute/removeAttribute patch,
135
+ // ... — see PROVENANCE.md) — stripComments() is safe here since the
136
+ // third-party portion has no comments to lose in the first place.
137
+ parts.push(stripComments(readVendor('reconnecting-websocket.js')));
138
+
139
+ // index-bootstrap.js runs here — right after the WHOLE vendor file
140
+ // above, not spliced into the middle of it (tried that first,
141
+ // 2026-09-14 — see git history if curious). This file defines
142
+ // `Reactor` early in its own source but other framework globals real
143
+ // index.js scripts commonly reference (`cookies`, `session`, ...) much
144
+ // later in the same file — no single splice point could satisfy both
145
+ // "run after everything index.js might need" and "run before this
146
+ // file's own $(document).ready() handler, which can fire
147
+ // synchronously". Running after the whole file satisfies the first
148
+ // constraint completely; the second is now handled by making that
149
+ // ready() handler itself wait for `appSettings` (see its own comment,
150
+ // right above `function _lumenReadyHandler()` in this vendor file)
151
+ // instead of assuming an execution-order guarantee that doesn't
152
+ // actually hold.
153
+ parts.push(stripComments(read('index-bootstrap.js')));
154
+
155
+ parts.push(stripComments(read('ws.js')));
156
+ parts.push(stripComments(read('lstnrs.js')));
157
+
158
+ return parts.join('\n');
159
+ }
160
+
161
+ function buildCore() {
162
+ const parts = [];
163
+ parts.push('/* LumenJS V2 core — generated by packages/core/build/bundle.js, do not edit directly */');
164
+ parts.push(stripComments(read('dom-shim.js')));
165
+ parts.push(buildCommonTail());
166
+ return parts.join('\n');
167
+ }
168
+
169
+ // 2026-09-16: registry-ified. Every library's npm path(s), ordering
170
+ // constraint, and build-time patch used to live inline here — now in
171
+ // build/plugins-registry.js, the single source of truth both this
172
+ // monolithic build AND a future per-project selective bundle (packages/cli)
173
+ // consume. This refactor is a pure mechanical transcription with no
174
+ // intended behavior change — see plugins-registry.js's own header for the
175
+ // exact reasoning (why a plain ordered array, not a topological sort) and
176
+ // PROVENANCE.md for the full per-library sourcing trail (why each library
177
+ // was picked, exact version matches, etc. — that detail lives there, not
178
+ // duplicated per-entry here anymore).
179
+ function buildCoreWithPlugins() {
180
+ pluginsRegistry.validateOrder(pluginsRegistry);
181
+ const parts = [];
182
+ parts.push('/* LumenJS V2 core (with real jQuery + plugins) — generated by packages/core/build/bundle.js, do not edit directly */');
183
+ for (const entry of pluginsRegistry) {
184
+ for (const npmPath of [].concat(entry.npm || [])) {
185
+ let src = readModule(npmPath);
186
+ if (entry.patch) src = entry.patch(src);
187
+ parts.push(src);
188
+ }
189
+ if (entry.code) parts.push(entry.code);
190
+ }
191
+ parts.push(buildCommonTail());
192
+ // 2026-09-15, real bug: joining with a bare '\n' let a missing trailing
193
+ // semicolon in one vendored file merge into the next one via automatic
194
+ // semicolon insertion — bootstrap-datetimepicker's source ends with
195
+ // `})(window.jQuery)` (no `;`), and jQuery UI's starts with
196
+ // `( function( factory ) {`, so ASI read the two as ONE continuous
197
+ // call chain (`(...)(...)(...)...`) instead of two separate IIFEs —
198
+ // exactly matching the resulting error, "(intermediate value)(...) is
199
+ // not a function". This is a real risk for ANY two files in this list,
200
+ // not just this one pair, since none of the ~18 vendored sources here
201
+ // are guaranteed to end with a semicolon. `;\n` as the join separator
202
+ // closes the whole class of bug at once — an extra leading `;` is
203
+ // always a harmless no-op statement in JS.
204
+ return parts.join(';\n');
205
+ }
206
+
207
+ async function buildPluginsCss() {
208
+ const parts = [];
209
+ parts.push('/* LumenJS V2 plugins CSS — generated by packages/core/build/bundle.js, do not edit directly */');
210
+ for (const entry of pluginsRegistry) {
211
+ if (entry.css) {
212
+ for (const cssPath of [].concat(entry.css)) {
213
+ let src = readModule(cssPath);
214
+ if (entry.cssPatch) src = entry.cssPatch(src);
215
+ parts.push(src);
216
+ }
217
+ }
218
+ if (entry.lessCss) {
219
+ // bootstrap-datetimepicker ships only LESS, no compiled CSS at
220
+ // all — compile it here rather than hand-vendoring a pre-built copy.
221
+ const lessSrc = readModule(entry.lessCss);
222
+ const lessOut = await less.render(lessSrc, {
223
+ filename: path.join(NODE_MODULES, entry.lessCss),
224
+ paths: [path.join(VENDOR, 'bootstrap2-less-stubs')],
225
+ });
226
+ parts.push(lessOut.css);
227
+ }
228
+ }
229
+ return parts.join('\n');
230
+ }
231
+
232
+ async function main() {
233
+ if (!fs.existsSync(OUT_DIR)) fs.mkdirSync(OUT_DIR, { recursive: true });
234
+
235
+ const core = buildCore();
236
+ const outPath = path.join(OUT_DIR, 'lumenjs-core.js');
237
+ fs.writeFileSync(outPath, core);
238
+ console.log('Wrote ' + outPath + ' (' + core.length + ' bytes)');
239
+ checkSyntax(core, outPath);
240
+
241
+ const withPlugins = buildCoreWithPlugins();
242
+ const withPluginsPath = path.join(OUT_DIR, 'lumenjs-core-with-plugins.js');
243
+ fs.writeFileSync(withPluginsPath, withPlugins);
244
+ console.log('Wrote ' + withPluginsPath + ' (' + withPlugins.length + ' bytes)');
245
+ checkSyntax(withPlugins, withPluginsPath);
246
+
247
+ const pluginsCss = await buildPluginsCss();
248
+ const pluginsCssPath = path.join(OUT_DIR, 'lumenjs-plugins.css');
249
+ fs.writeFileSync(pluginsCssPath, pluginsCss);
250
+ console.log('Wrote ' + pluginsCssPath + ' (' + pluginsCss.length + ' bytes)');
251
+ }
252
+
253
+ function checkSyntax(code, outPath) {
254
+ // Fail loudly rather than ship something broken.
255
+ try {
256
+ new (require('vm').Script)(code, { filename: outPath });
257
+ console.log('Syntax check: OK');
258
+ } catch (e) {
259
+ console.error('Syntax check FAILED:', e.message);
260
+ process.exit(1);
261
+ }
262
+ }
263
+
264
+ main();
@@ -0,0 +1,216 @@
1
+ // plugins-registry.js — the 18-library (+jQuery) `--with-plugins` set,
2
+ // factored out of build/bundle.js's buildCoreWithPlugins()/buildPluginsCss()
3
+ // (2026-09-16) into one data structure both the existing prebuilt monolith
4
+ // AND a future per-project selective bundle (packages/cli) can consume.
5
+ //
6
+ // Deliberately a plain ORDERED ARRAY, not something that computes an order
7
+ // via topological sort: this array's declaration order IS today's real,
8
+ // already-shipped, already-tested load order — transcribed mechanically
9
+ // from build/bundle.js's buildCoreWithPlugins(), not reconstructed from
10
+ // memory. A topo sort over `after` edges could legally reorder two
11
+ // independent no-dependency entries (e.g. jquery.easing vs jquery.numeric —
12
+ // no real dependency between them, but SOME specific order exists in the
13
+ // real, shipped bundle), which would change dist/lumenjs-core-with-plugins.js's
14
+ // bytes for no functional reason. `validateOrder()` below instead turns the
15
+ // ordering knowledge that used to live only in code comments into an
16
+ // enforced invariant: it throws if any entry's `after` key doesn't already
17
+ // appear earlier in the array, catching a broken reorder immediately
18
+ // instead of letting it silently ship.
19
+ //
20
+ // Each entry:
21
+ // key — the real npm package name (also what a project's own
22
+ // package.json dependency list is matched against, for the
23
+ // future per-project selective bundle — see PROVENANCE.md).
24
+ // npm — one path or an array of paths, relative to node_modules,
25
+ // exactly matching the readModule() calls this replaces.
26
+ // css — one path or an array of paths (relative to node_modules)
27
+ // for buildPluginsCss(), if this library ships compiled CSS.
28
+ // lessCss — a LESS source path to compile via the `less` package,
29
+ // for the one library (bootstrap-datetimepicker) that ships
30
+ // only LESS, no compiled CSS.
31
+ // after — keys that must already have been emitted before this one.
32
+ // patch(src) — transforms this entry's own JS source before it's
33
+ // emitted (the datetimepicker null-guard, the slick→crsl
34
+ // rename). Throws loudly if its patch anchor isn't found
35
+ // (package updated out from under it), exactly like the
36
+ // original inline checks did.
37
+ // cssPatch(src) — same, for this entry's CSS (crsl's rename, applied to
38
+ // its CSS too).
39
+ // code — a registration statement emitted verbatim after this
40
+ // entry's own source (the hasAttr polyfill, the jQueryBridget
41
+ // registration, GSAP's ScrollToPlugin registration).
42
+ // always — true for the one entry (jQuery) that's implicitly required
43
+ // by every other entry; not itself gated by `after` since
44
+ // every real entry already declares `after: ["jquery"]`.
45
+
46
+ function datetimepickerPatch(src) {
47
+ const patches = [
48
+ ["icon.addClass(this.timeIcon);", "if (icon) icon.addClass(this.timeIcon);"],
49
+ ["icon.removeClass(this.timeIcon);\n icon.addClass(this.dateIcon);", "if (icon) {\n icon.removeClass(this.timeIcon);\n icon.addClass(this.dateIcon);\n }"],
50
+ ];
51
+ for (const [from, to] of patches) {
52
+ if (src.indexOf(from) === -1) {
53
+ throw new Error('bootstrap-datetimepicker patch anchor not found: "' + from + '" — did the package update? Re-check the fix in plugins-registry.js against the new source.');
54
+ }
55
+ src = src.replace(from, to);
56
+ }
57
+ return src;
58
+ }
59
+
60
+ function crslRenamePatch(src) {
61
+ if (src.indexOf('slick') === -1) {
62
+ throw new Error('slick-carousel rename anchor "slick" not found in its own source — did the package change its own naming? Re-check this rename in plugins-registry.js.');
63
+ }
64
+ return src.split('Slick').join('Crsl').split('slick').join('crsl');
65
+ }
66
+
67
+ function crslCssPatch(src) {
68
+ return src.split('slick').join('crsl');
69
+ }
70
+
71
+ const registry = [
72
+ {
73
+ key: 'jquery',
74
+ npm: 'jquery/dist/jquery.js',
75
+ after: [],
76
+ always: true,
77
+ code: 'jQuery.fn.hasAttr = function (name) { return !!(this[0] && this[0].getAttribute(name) != null); };',
78
+ },
79
+ {
80
+ key: 'select2',
81
+ npm: 'select2/dist/js/select2.js',
82
+ css: 'select2/dist/css/select2.css',
83
+ after: ['jquery'],
84
+ // Only consumed by packages/cli's per-project selective esbuild
85
+ // bundle (plugins-esbuild-plugin.js), not by this file's own
86
+ // buildCoreWithPlugins() — that build is plain string
87
+ // concatenation, never triggers select2's UMD wrapper's CommonJS
88
+ // branch at all, so this quirk never applies to it. Select2's own
89
+ // UMD wrapper (dist/js/select2.js) is unusual among these 18
90
+ // libraries: its CommonJS branch exports a FACTORY FUNCTION
91
+ // (`module.exports = function (root, jQuery) {...}`) rather than
92
+ // self-attaching to jQuery.fn the moment it's required — real
93
+ // esbuild bundling (which wraps every dependency in a genuine
94
+ // module/exports closure) takes that branch, so a plain
95
+ // side-effect import alone never actually calls the factory;
96
+ // .select2 never gets attached. Confirmed by reading select2's
97
+ // real source directly, not guessed.
98
+ cjsFactory: true,
99
+ },
100
+ {
101
+ key: 'flickity',
102
+ npm: ['flickity/dist/flickity.pkgd.js', 'jquery-bridget/jquery-bridget.js'],
103
+ css: 'flickity/dist/flickity.css',
104
+ after: ['jquery'],
105
+ code: 'jQueryBridget("flickity", Flickity, jQuery);',
106
+ },
107
+ {
108
+ key: 'bootstrap-colorpicker',
109
+ npm: 'bootstrap-colorpicker/dist/js/bootstrap-colorpicker.js',
110
+ css: 'bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css',
111
+ after: ['jquery'],
112
+ },
113
+ {
114
+ key: 'bootstrap-datetimepicker',
115
+ npm: 'bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js',
116
+ lessCss: 'bootstrap-datetimepicker/src/less/bootstrap-datetimepicker.less',
117
+ after: ['jquery'],
118
+ patch: datetimepickerPatch,
119
+ },
120
+ {
121
+ key: 'jquery-ui-dist',
122
+ npm: 'jquery-ui-dist/jquery-ui.js',
123
+ css: 'jquery-ui-dist/jquery-ui.css',
124
+ after: ['jquery'],
125
+ },
126
+ {
127
+ key: 'jquery.easing',
128
+ npm: 'jquery.easing/jquery.easing.js',
129
+ after: ['jquery'],
130
+ },
131
+ {
132
+ key: 'nestedSortable',
133
+ npm: 'nestedSortable/jquery.mjs.nestedSortable.js',
134
+ after: ['jquery-ui-dist'],
135
+ },
136
+ {
137
+ key: 'jquery.numeric',
138
+ npm: 'jquery.numeric/jquery.numeric.js',
139
+ after: ['jquery'],
140
+ },
141
+ {
142
+ key: 'timeago',
143
+ npm: 'timeago/jquery.timeago.js',
144
+ after: ['jquery'],
145
+ },
146
+ {
147
+ key: 'slick-carousel',
148
+ npm: 'slick-carousel/slick/slick.js',
149
+ css: ['slick-carousel/slick/slick.css', 'slick-carousel/slick/slick-theme.css'],
150
+ after: ['jquery'],
151
+ patch: crslRenamePatch,
152
+ cssPatch: crslCssPatch,
153
+ },
154
+ {
155
+ key: '@iconify/iconify',
156
+ npm: '@iconify/iconify/dist/iconify.js',
157
+ after: [],
158
+ },
159
+ {
160
+ key: 'rellax',
161
+ npm: 'rellax/rellax.js',
162
+ after: [],
163
+ },
164
+ {
165
+ key: 'headroom.js',
166
+ npm: ['headroom.js/dist/headroom.js', 'headroom.js/dist/jQuery.headroom.js'],
167
+ after: ['jquery'],
168
+ },
169
+ {
170
+ key: 'in-view',
171
+ npm: 'in-view/dist/in-view.min.js',
172
+ after: [],
173
+ },
174
+ {
175
+ key: 'gsap',
176
+ npm: ['gsap/dist/gsap.js', 'gsap/dist/ScrollToPlugin.js'],
177
+ after: [],
178
+ code: 'gsap.registerPlugin(ScrollToPlugin);',
179
+ },
180
+ {
181
+ key: 'jquery.caret',
182
+ npm: 'jquery.caret/dist/jquery.caret.js',
183
+ after: ['jquery'],
184
+ },
185
+ {
186
+ key: 'jquery-tageditor',
187
+ npm: 'jquery-tageditor/jquery.tag-editor.js',
188
+ css: 'jquery-tageditor/jquery.tag-editor.css',
189
+ after: ['jquery.caret'],
190
+ },
191
+ {
192
+ key: 'jquery-form',
193
+ npm: 'jquery-form/src/jquery.form.js',
194
+ after: ['jquery'],
195
+ },
196
+ {
197
+ key: 'jquery-lazy',
198
+ npm: 'jquery-lazy/jquery.lazy.js',
199
+ after: ['jquery'],
200
+ },
201
+ ];
202
+
203
+ function validateOrder(entries) {
204
+ const seen = new Set();
205
+ for (const e of entries) {
206
+ for (const dep of e.after) {
207
+ if (!seen.has(dep)) {
208
+ throw new Error(`plugins-registry.js: "${e.key}" is declared before its "after" dependency "${dep}" — reorder the array.`);
209
+ }
210
+ }
211
+ seen.add(e.key);
212
+ }
213
+ }
214
+
215
+ module.exports = registry;
216
+ module.exports.validateOrder = validateOrder;