diff --git a/.gitignore b/.gitignore index d999c68a..d5201df9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ npm-debug.log yarn-error.log coverage .DS_Store +.tmp diff --git a/.travis.yml b/.travis.yml index fff65042..f0d7c1ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: node_js node_js: - - "13.5.0" + - "14.2.0" cache: npm @@ -11,7 +11,7 @@ os: env: matrix: - - NODE_VERSION="13" + - NODE_VERSION="14.2.0" matrix: fast_finish: true diff --git a/README.md b/README.md index 7cf78454..863179dc 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ this is the @magic core library. [![NPM version][npm-image]][npm-url] +[![License][license-image]][license-url] [![Node version][node-image]][node-url] [![Linux Build Status][travis-image]][travis-url] [![Windows Build Status][appveyor-image]][appveyor-url] [![Coverage Status][coveralls-image]][coveralls-url] -[![Greenkeeper badge][greenkeeper-image]][greenkeeper-url] [![Known Vulnerabilities][snyk-image]][snyk-url] builds a webapp with deeply nested component structure and globally/locally shared state and actions. @@ -18,152 +18,816 @@ to get more information, head to the [docs / demo](https://magic.github.io/core/) other related github organizations: -* [@magic-modules](https://magic-modules.github.io) - building blocks for your app -* [@magic-themes](https://magic-themes.github.io) - themes for your app -* [@magic-libraries](https://magic-libraries.github.io) - client libraries + +- [@magic-modules](https://magic-modules.github.io) - building blocks for your app +- [@magic-themes](https://magic-themes.github.io) - themes for your app +- [@magic-libraries](https://magic-libraries.github.io) - client libraries #### changelog ##### 0.0.1 + first commit ##### 0.0.2 + use @magic npm packages instead of github for installs ##### 0.0.3 -* update dependencies -* make @magic actually work in the client if @magic-modules/gdpr is not installed. + +- update dependencies +- make @magic actually work in the client if @magic-modules/gdpr is not installed. ##### 0.0.4 -* dev server now correctly redirects urls if the trailing slash is missing -* html and markdown files can include state and variables + +- dev server now correctly redirects urls if the trailing slash is missing +- html and markdown files can include state and variables ##### 0.0.5 -* @magic-themes can now add variables to the css colors without overwriting the default colors -* @magic-modules/messages gets autoloaded if it is installed. -* components now can be nested without array. div([p('text')]) can also be div(p('text')) -* Link({ to: '#hashLink' }) is local now -* very early blog implementation. it works, but the archive pages need more work. + +- @magic-themes can now add variables to the css colors without overwriting the default colors +- @magic-modules/messages gets autoloaded if it is installed. +- components now can be nested without array. div([p('text')]) can also be div(p('text')) +- Link({ to: '#hashLink' }) is local now +- very early blog implementation. it works, but the archive pages need more work. ##### 0.0.6 + always delete .tmp dir ##### 0.0.7 + fix 404 page regression -##### 0.0.8 - unreleased -* blog/year/month archive pages are not 404'ing. -* blog archive pages now have links in them if needed +##### 0.0.8 + +- blog/year/month archive pages are not 404'ing. +- blog archive pages now have links in them if needed (year page links to each month archive page) -* blog posts are wrapped in BlogPost automagically. +- blog posts are wrapped in BlogPost automagically. BlogPost adds header and description automagically. -* config.HOIST allows adding any module at the bottom of the page (gdpr, messages will use that) +- config.HOIST allows adding any module at the bottom of the page (gdpr, messages will use that) ##### 0.0.9 -* config.HOIST can be an array or a string + +- config.HOIST can be an array or a string ##### 0.0.10 -* config.HOIST defaults to empty array -* app.actions.gdpr will only be added if Gdpr is included in config.HOIST + +- config.HOIST defaults to empty array +- app.actions.gdpr will only be added if Gdpr is included in config.HOIST ##### 0.0.11 -* allow arbitrary, module and page level init effects for initialState + +- allow arbitrary, module and page level init effects for initialState ##### 0.0.12 -* add SkipLink for screen readers. hash links to page start -* libraries can now be named with a - in their name and get camelCased + +- add SkipLink for screen readers. hash links to page start +- libraries can now be named with a - in their name and get camelCased ##### 0.0.13 -* svg tags are actually usable now. except color-profile and switch. + +- svg tags are actually usable now. except color-profile and switch. ##### 0.0.14 -* update @magic/css which updates postcss -* color theme vars now export grey and bluegrey. copies gray and bluegray. -* config.BABEL is now being used for babel build -* config.THEME_VARS can be a function. gets passed { colors } if it is a function. + +- update @magic/css which updates postcss +- color theme vars now export grey and bluegrey. copies gray and bluegray. +- config.BABEL is now being used for babel build +- config.THEME_VARS can be a function. gets passed { colors } if it is a function. ##### 0.0.15 + regression: babel should minify files on build. ##### 0.0.16 -* modules/MenuItem: only add submenu ul if submenu has items.length > 0 -* link menu parent .to only gets prepended to link.to if link.to starts with - or #, not if it starts with /. -* add modules/MenuLink. use it in modules/MenuItem + +- modules/MenuItem: only add submenu ul if submenu has items.length > 0 +- link menu parent .to only gets prepended to link.to if link.to starts with - or #, not if it starts with /. +- add modules/MenuLink. use it in modules/MenuItem ##### 0.0.17 -* **commonjs support is gone.** -* **minimum node version bumped to 13.5.0** -* modules/Router.mjs: Link module now accepts action as prop -* modules/Router.mjs: scroll page back to top if the clicked link points to current page root + +- **commonjs support is gone.** +- **minimum node version bumped to 13.5.0** +- modules/Router.mjs: Link module now accepts action as prop +- modules/Router.mjs: scroll page back to top if the clicked link points to current page root ##### 0.0.18 + do not error if docs/sri-hashes.json does not exist ##### 0.0.19 + **minimum node version of dependencies bumped to 13.5.0** ##### 0.0.20 -* update dependencies -* update theme to show list-style for ordered lists (ol) -* better error handling -* remove lib/fs, use @magic/fs everywhere -* is @magic/types for isUpperCase and isLowerCase + +- update dependencies +- update theme to show list-style for ordered lists (ol) +- better error handling +- remove lib/fs, use @magic/fs everywhere +- is @magic/types for isUpperCase and isLowerCase ##### 0.0.21 + add viewport and ie=edge html meta tags. ##### 0.0.22 + tasks/prepare/css: also load themes directly from node_modules/${theme_name}. ##### 0.0.23 -* all hail eris! -* css styles now inherit THEME_VARS from all possible source before transpiling styles. -* colors.bluegrey now is alias for colors.bluegray instead of undefined. -* client side routing scrolls to top in all cases, but is not animated. + +- all hail eris! +- css styles now inherit THEME_VARS from all possible source before transpiling styles. +- colors.bluegrey now is alias for colors.bluegray instead of undefined. +- client side routing scrolls to top in all cases, but is not animated. scroll-behavior + window.scroll() = bug ##### 0.0.24 -* Router: set document.title on pushstate if possible -* Seo: add image for social previews + +- Router: set document.title on pushstate if possible +- Seo: add image for social previews ##### 0.0.25 -* fix: do not error if none of the pages has state.title set. + +- fix: do not error if none of the pages has state.title set. ##### 0.0.26 + config.IGNORED_STATIC is an array. can include path ('/file.ext') or extension ('.ext') strings. ##### 0.0.27 + do not create sri-hashes for config.IGNORED_STATIC files ##### 0.0.28 + themes can export Modules now, which can overwrite builtins. ##### 0.0.29 + fix some edgecases with exported theme modules ##### 0.0.30 + local config.THEME_VARS get deep merged over theme provided THEME_VARS now. ##### 0.0.31 + if multiple themes are installed, the last imported theme will overwrite earlier imported themes and their vars. ##### 0.0.32 + fix race condition in case of multiple themes. modules did not always get overwritten in the correct order. ##### 0.0.33 + MenuItem: fix link handling for rooted links. initial / will not be removed from links. -##### 0.0.34 - unreleased -* themes: merge state, actions, effects, and subscriptions into app. -* maybeApp import: only suppress error if it is E_MODULE_NOT_FOUND. +##### 0.0.34 + +- themes: merge state, actions, effects, and subscriptions into app. +- maybeApp import: only suppress error if it is E_MODULE_NOT_FOUND. + +##### 0.0.35 + +fix: hash routing in webkit works (chromium, brave tested) + +##### 0.0.36 + +modules/app: correctly merge maybeApp and theme state, actions, effects and subscriptions. + +##### 0.0.37 + +- config: IGNORED_STATIC can be a string. +- config: items in IGNORED_STATIC do not have to start with a dot. will be added if it is missing. +- babel errors tell us to run `npm run format` to find the source of the error. Sourcemaps tbd +- build worker process should always exit with process.exit(1), not panic. + +##### 0.0.38 + +- babel: do not mangle toplevel names. broke builds, adds 1kb+ to js. investigate, exterminate. + +##### 0.0.39 + +- only load Modules from theme if their names are CamelCased. +- handle state as a function in all imports, call it with config param +- @magic/transform handles @magic-modules now, which makes pure markdown pages possible. + +##### 0.0.40 + +fix: HOISTing broke in 0.0.39 + +##### 0.0.41 + +hoisting happens in the footer. + +##### 0.0.42 + +fix: theme modules are now getting imported again. + +##### 0.0.43 + +update @magic/transmute: @magic-modules in markdown are no longer wrapped in p tags + +##### 0.0.44 + +add Credit module + +##### 0.0.45 + +add b, strong, i, and em styles (bold and italic) + +##### 0.0.46 + +- no more race conditions when importing multiple themes. +- Logo link works again. + +##### 0.0.47 + +modules/app: page.state gets inherited properly, markdown pages can see global state now. + +##### 0.0.48 + +import originalState from transmuted bundle. + +##### 0.0.49 + +FIX: process would hang if a markdown or html page had no state. + +##### 0.0.50 + +document.title does not error in client if only one page exists. + +##### 0.0.51 + +##### FULL @magic-module api available from markdown. + +- nested state variables in template strings eg {{state.test.deep.var}} now work. +- replace parse5 with posthtml. +- custom markdown and custom html renderers added. +- @magic-modules in markdown and html can have keys that are arrays or objects now. +- state variables in template strings can be arrays (eg state.description). + will be join(' ')ed + +##### 0.0.52 + +fix: remove admin for now + +##### 0.0.53 + +- transmute now returns empty strings instead of empty arrays and objects. + +##### 0.0.54 + +- transmute/render/markdown.codespan: do not show line numbers for code spans. +- do not pass modules to transmute. + +##### 0.0.55 + +@magic/transmute now correctly unescapes quot entities. + +##### 0.0.56 + +- Credits module does not error if style variables are not set. + +##### 0.0.57 + +tasks/prepare: module.state extends, but does not overwrite app.state when importing modules + +##### 0.0.58 + +update @magic/transmute, which now actually replaces all occurrences of html entities in a string, +not just the first one. + +##### 0.0.59 + +- tasks/serve/handler: use array to collect req.body data. + strings break unicode characters if the chunks split a character on transit +- only create sri-hashes for static files in conf.INCLUDED_HASH_EXTENSIONS +- update dependencies => minimist vuln + +##### 0.0.60 + +- modules/Router/Link.onclick: now also resets window.location.hash if the hash did not change. +- tasks/serve/handler: response data chunks are now always buffers. + +##### 0.0.61 + +update babel and @magic/transmute + +##### 0.0.62 + +update babel jsx and @magic/transmute + +##### 0.0.63 + +modules/Seo now allows arbitrary header fields. + +##### 0.0.64 + +modules/Header passes state.theme to Logo + +##### 0.0.65 + +magic can now serve a log of mime-types. see @magic/mime-types for full list + +##### 0.0.66 + +update dependencies + +##### 0.0.67 + +update @magic/transmute + +##### 0.0.68 + +@magic/transmute does not add p tags around Link and Img elements. + +##### 0.0.69 + +update dependencies + +##### 0.0.70 + +update dependencies + +##### 0.0.71 + +- update dependencies +- sri-hashes.json is a flat object: key = path, value = hash + +##### 0.0.72 + +- bump required node version +- update dependencies +- cli help expanded +- cli flags get passed to config +- better lib handling + +##### 0.0.73 + +update dependencies. + +##### 0.0.74 + +- src/index: export runCluster +- pkg: add type: module, add main field + +##### 0.0.75 + +- magic build --no-minify flag now works +- themes/theme_name.mjs is now a valid theme path +- apis can be served by @grundstein/gas and created on page, app and module level + +##### 0.0.76 + +- update dependencies +- hyperapp-render is a proper module now + +##### 0.0.77 + +- add config.API_DIR. load all files in that dir and use their default exports as lambdas. +- update babel build task + +##### 0.0.78 + +update babel + +##### 0.0.79 + +- FIX: sri-hashes for magic.js and magic.css get added to html again. +- update dependencies + +##### 0.0.80 + +update @magic/css + +##### 0.0.81 + +- Img module now has loading=lazy as default prop. +- update @magic/css + +##### 0.0.82 + +FIX: regression, reenable custom state for pages. + +##### 0.0.83 + +update lodash to mitigate development environment security issue. + +##### 0.0.84 + +update deps + +##### 0.0.85 + +icon sizes can be customized + +##### 0.0.86 + +update icon size usage + +##### 0.0.87 + +themes/reset.css: add icon animation for hoisted icons + +##### 0.0.88 + +update dependencies + +##### 0.0.89 + +update babel + +##### 0.0.90 + +- remove unused imports +- formatting +- split tasks/globals into separate files +- update api to allow self-contained lambdas which load their dependencies using await import +- update dependencies + +##### 0.0.91 + +update dependencies + +##### 0.0.92 + +update dependencies + +##### 0.0.93 + +update dependencies + +##### 0.0.94 + +update dependencies + +##### 0.0.95 + +- serve/prepareApi: remove console.log +- allow addition of third party scripts using ADD_SCRIPTS config variable +- update dependencies + +##### 0.0.96 + +git tagged but not published + +##### 0.0.97 + +- do not error if page is not a javascript module +- update dependencies +- add app.build task +- update dependencies + +##### 0.0.98 + +- if the app.mjs import fails because one of the app dependencies is missing, + throw an error instead of silently not importing the app. +- update dependencies + +##### 0.0.99 + +- use @swc/core for dev builds to make them 10 times faster. +- update dependencies +- only duplicate unique page.state into app.state, 20-30% bundle size reduction. +- cluster/build task recovers from build errors, no restart needed. + +##### 0.0.100 + +- config.API_DIR now actually gets used +- api routes now 404 if they do not exist (redirected to WEB_ROOT before) +- update deps + +##### 0.0.101 + +- Router: on navigation scroll to top works in newer firefox, tradeoff: probably not in older. +- npm i -g magic now works and loads local node_modules as well as global + +##### 0.0.102 + +- further fixes regarding npm i -g magic +- bump required node version to 14.15.4 +- update dependencies + +##### 0.0.103 + +update dependencies + +##### 0.0.104 + +update dependencies + +##### 0.0.105 + +- Header, Menu, MenuItem and Logo arguments updated + +##### 0.0.106 + +update dependencies (marked) + +##### 0.0.107 + +- update dependencies +- swc now handles links in pages correctly (dev) +- babel now handles links in pages correctly (production) +- prepare task now handles all urls in the state object, + no need to handle it in babel/swc or in modules. + +##### 0.0.108 + +debump hyperapp-render from 4.0.0 to 3.5.0 + +##### 0.0.109 + +- fix error in modules/Logo +- tasks/prepare/stateLinks now handles #hash and -expand links without parents +- check for broken links on build and dev +- better link handling, should now include all images and links in pages too + +##### 0.0.110 + +- update dependencies +- restructure most of the app tasks and remove global.config. + this will allow us to seamlessly use @magic/core in the @magic/test library +- libs can now be static function instead of imports +- export renderToString + +##### 0.0.111 + +- magic.init can now be an actual array of code, instead of string only +- update dependencies +- more robust checkLinks testing + +##### 0.0.112 + +- libraries can be actual code or a string pointing to a file +- update hyperapp and use 2.0.12 +- update hyperapp-render to 4.0.0 +- update dependencies +- href link checkers handle mailto: links +- import hyperapp memo instead of Lazy +- starting to remove usages of config without explicit destructuring +- replaceSlashSlash all urls in robots.txt and sitemap.xml +- add ADD_TAGS to append arbitrary html after the #magic root +- the watch task can now watch files +- the watch task watches the config file + +##### 0.0.113 + +- FIX: tasks/prepare/page: change variable name to avoid "access before init" error when pages do not build successfully. +- update dependencies + +##### 0.0.114 + +- to keep dev builds fast, external links only get checked on build. +- deprecate config.mjs in favor of magic.(m)js +- magic.js config import will now error and exit if config does not exist +- if no config file is found, output an example config +- show a warning if config.mjs is still being used +- only checkLinks external links in production env + +##### 0.0.115 + +- swc handles OptionalChainingExpression and Computed +- nicer error messages +- add Permissions-Policy interest-cohort=() to maybe avoid FLOC tracking in chrome. + +##### 0.0.116 + +- add @magic/hyperapp bundle that exports hyperapp and hyperapp-render + +##### 0.0.117 + +correctly export renderToString from index (for @magic/test) + +##### 0.0.118 + +add silent option to silence log output and apply to config. + +##### 0.0.119 + +- update dependencies +- conf.ADD_STATIC can be used to import static files from multiple directories +- component handles prerendered hyperapp objects correctly again. +- html pages have their href and src elements prefixed with WEB_ROOT + +##### 0.0.120 + +- runConfig: bail early if config file is not found +- lib/findConfigFile: only process.exit if args.silent is false + +##### 0.0.121 + +update dependencies + +##### 0.0.122 + +- update @babel/preset-env to avoid security issue + +- config: add PREPEND_SCRIPT and APPEND_SCRIPT to allow adding js script tags before and after the magic js +- config: add PREPEND_JS and APPEND_JS to allow adding js contents before and after the magic js, directly into magic.js +- config: add PREPEND_CSS and APPEND_CSS to allow adding css before and after magic js +- config: add PREPEND_TAGS and APPEND_TAGS to allow adding html tags before and after #magic tag + +##### 0.0.123 + +- BUG: do not try to return an undefined hashes var if PREPEND_SCRIPTS or APPEND_SCRIPTS is empty +- swc: handle EmptyStatement +- update dependencies + +##### 0.0.124 + +make sure all app.static[key] keys start with a slash (config.ADD_STATIC) + +##### 0.0.125 + +- cluster.watch now also watches config.DIR.STATIC directories +- update dependencies + +##### 0.0.126 + +update dependencies + +##### 0.0.127 + +update dependencies to avoid circular dependency in @magic/types + +##### 0.0.128 + +update dependencies + +##### 0.0.129 + +update dependencies + +##### 0.0.130 + +update dependencies + +##### 0.0.131 + +update dependencies + +##### 0.0.132 + +update dependencies + +##### 0.0.133 + +- update dependencies +- theme: move css from html to body to prevent duplicate scrollbars in blink/webkit + +##### 0.0.134 + +- update dependencies +- config.DIR.LIB is an array and imports lib files from each of them + +##### 0.0.135 + +- theme imports now throw MODULE_NOT_FOUND errors in their internal imports, they got swallowed before +- CHECK_PROPS can be silenced +- update dependencies + +##### 0.0.136 + +update dependencies + +##### 0.0.137 + +- allow .js files for modules and libraries +- update dependencies + +##### 0.0.138 + +- better encoding for ' characters in state strings +- update dependencies + +##### 0.0.139 + +- update dependencies + +##### 0.0.140 + +- update dependencies +- @magic/test can now test @magic/core again + +##### 0.0.141 + +update dependencies + +##### 0.0.142 + +update dependencies + +##### 0.0.143 + +update dependencies + +##### 0.0.144 + +- update dependencies +- remove babel, swc can handle it by now + (bundle js size 88k babel to 90k swc for this page, 18 byte difference if using zopfli). +- config.BABEL deprecation warning +- app.helpers deprecation warning +- propTypes no longer need to be namespaced + + ``` + // Module.mjs + export const propTypes = [//...propTypes] + + // will turn into: + export const propTypes = { Module: [//..propTypes]} + ``` + +##### 0.0.145 + +- update dependencies +- swc will prepend links in the return statements of modules with WEB_ROOT if needed. +- CHECK_PROPS supports oneOf and someOf. added some tests. + +##### 0.0.146 + +- update dependencies +- FIX from 0.0.145: CHECK_PROPS now can handle logging and errors. +- FIX from 0.0.145: swc can handle string literals in img src and srcset parameters + +##### 0.0.147 + +swc will now remove CHECK_PROPS from magic.js production bundles. + +##### 0.0.148 + +- update dependencies +- checkLinks supports NO_CHECK_LINKS_LIST in magic.js, which allows us to ignore known broken links, for example when pages block our http requests. + +##### 0.0.149 + +- update dependencies +- better error message if a module can not be required +- global html tags will always be defined in order to make sure they exist before requiring other Modules + +##### 0.0.150 + +update dependencies + +##### 0.0.151 + +- update dependencies +- FIX: bug when assigning local state of modules, only the last item was written, now they all get added to state.componentname. +- swc: add AwaitExpression and set swc.jsc.target to es2017, leading to async-await making it into the final bundle. +- module.global can now also be called module.globals +- tasks/prepare/js now makes sure that `import`ed modules work. +- swc: add ForOfStatement + +##### 0.0.152 + +- downgrade swc + +##### 0.0.153 + +- magic now seems to work on windows. + +##### 0.0.154 + +- update dependencies +- link WEB_ROOT prefixing ignores Identifier, TemplateLiteral and BinaryExpression ast nodes, they are dynamic and should not be manipulated +- Img module will not automatically set role="presentation" when alt="", props.loading will always be set to lazy unless provided +- CheckLinks will not error if TemplateLiterals are used in the url +- Magic exports replaceSlashSlash from library +- modules/app will not error if state of a page is undefined in src/app.mjs + +##### 0.0.155 + +- reduce @swc/core version back to 1.3.41 to prevent errors when transpiling + +##### 0.0.156 + +update dependencies + +##### 0.0.157 + +update dependencies + +##### 0.0.158 + +update @magic/test and fix spec tests + +##### 0.0.159 - unreleased -##### 0.0.35 - unreleased ... [npm-image]: https://img.shields.io/npm/v/@magic/core.svg [npm-url]: https://www.npmjs.com/package/@magic/core +[license-image]: https://img.shields.io/npm/l/@magic/core.svg +[license-url]: https://www.npmjs.com/package/@magic/core [node-image]: https://img.shields.io/node/v/@magic/core/latest [node-url]: https://www.npmjs.com/package/@magic/core [travis-image]: https://img.shields.io/travis/com/magic/core/master @@ -172,7 +836,5 @@ MenuItem: fix link handling for rooted links. initial / will not be removed from [appveyor-url]: https://ci.appveyor.com/project/magic/core/branch/master [coveralls-image]: https://coveralls.io/repos/github/magic/core/badge.svg [coveralls-url]: https://coveralls.io/github/magic/core -[greenkeeper-image]: https://badges.greenkeeper.io/magic/core.svg -[greenkeeper-url]: https://badges.greenkeeper.io/magic/core.svg [snyk-image]: https://snyk.io/test/github/magic/core/badge.svg [snyk-url]: https://snyk.io/test/github/magic/core diff --git a/appveyor.yml b/appveyor.yml index f5ac4c99..cda15e87 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,7 @@ matrix: install: # Build - - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild 13.5.0) $env:platform + - ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild 14.2.0) $env:platform - npm install test_script: diff --git a/docs/404.html b/docs/404.html index 18a31b49..26c73704 100644 --- a/docs/404.html +++ b/docs/404.html @@ -1 +1 @@ -@magic/core documentation
404 - not found
\ No newline at end of file +@magic/core documentation

@magic

404 - not found.
\ No newline at end of file diff --git a/docs/appendCss.css b/docs/appendCss.css new file mode 100644 index 00000000..382da661 --- /dev/null +++ b/docs/appendCss.css @@ -0,0 +1,3 @@ +#AppendCss { + color: green; +} diff --git a/docs/appendJs.js b/docs/appendJs.js new file mode 100644 index 00000000..7736c0a4 --- /dev/null +++ b/docs/appendJs.js @@ -0,0 +1,3 @@ +console.log( + 'config.APPEND_JS works and appends after the magic.js. CONSOLE.LOG GETS REMOVED IN PROD, making this script not do anything.', +) diff --git a/docs/appendScript.js b/docs/appendScript.js new file mode 100644 index 00000000..06551fd4 --- /dev/null +++ b/docs/appendScript.js @@ -0,0 +1,4 @@ +console.log('config APPEND_SCRIPTS works.') + +const h = 'Overwriting h in APPEND_SCRIPTS does not break the page...' +console.log(h) diff --git a/docs/build/index.html b/docs/build/index.html new file mode 100644 index 00000000..844768b5 --- /dev/null +++ b/docs/build/index.html @@ -0,0 +1 @@ +@magic/core documentation

@magic

external data

This is some static data, loaded via example/app.mjs#build and from example/assets/data/index.json.

state.data

{  "data": {    "title": "external data",    "description": "This is some static data, loaded via example/app.mjs#build and from example/assets/data/index.json."  }}

\ No newline at end of file diff --git a/docs/concepts/index.html b/docs/concepts/index.html index 8d13bdf3..35096c60 100644 --- a/docs/concepts/index.html +++ b/docs/concepts/index.html @@ -1 +1,17 @@ -@magic/core concepts

@magic/core concepts

magic concepts. These are the building blocks of every module in a magic app

@magic-modules

modules are the main building block of magic.

a page is a module, a button is a module, a link is a module, an image is a module. a @magic app contains modules containing modules that contain modules. this can lead to inception.

module building blocks

state

state is a javascript object.

state can be mutated by actions or effects.

every rendering step, the state determines the output of the views

actions

actions are an object containing functions

those functions get the state and their props and may return a new full, but changed, state.

effects

effects are an object containing functions, just like actions.

they behave like actions, get a state and props and may return a new full, but changed, state.

the big difference? effects may be impure and trigger sideeffects outside of hyperapp.

views

views render the state to html

whenever an action triggers a change in the state, this then triggers a view change.

styles

every module can have a style object attached to it.

magic will automagically merge all styles into one global css file.

in the future, it will also remove unused styles for you.

style merge order from lowest to highest, last overwrites first:

module.style < page.style < app.style < theme.style

@magic/css

internally, magic uses it's own css-in-js library.

to find out more, click the following link:

@magic/css

global

every module can set a global object, containing state and action properties.

every state and/or action name in the global object with a value that equals true gets merged into the main app state/actions.

server lambdas

this is the serverside magic.

you can define functions that get transpiled into serverside lambdas.

server side lambdas will be available for GET and/or POST requests.

the server side function signature is (req, res) => {}, as in any nodejs http server, with the addition of req.body being async => awaited before execution of the lambda.

\ No newline at end of file +@magic/core documentation

@magic

@magic/core concepts

magic concepts. these are the building blocks of every module in a magic app

@magic-modules

modules are the main building block of magic.

a page is a module, a button is a module, a link is a module, an image is a module. +even a theme is a module.

a @magic app contains modules containing modules that contain modules. +this can lead to inception.

module building blocks

state

state is a javascript object. +state can be mutated by actions, +effects and subscriptions can call actions to change the state. +every rendering frame, the state determines the output of the html views.

const state = {  shown: false,  count: 0,}

actions

actions are an object containing functions

those functions get the state and their props and may return a new full, but changed, state.

const actions = {  toggleModal: state => {    return {      ...state,      shown: !state.shown,    }  },  calculator: {    plus: state => ({ ...state, count: state.count + 1 }),    minus: state => ({ ...state, count: state.count - 1 }),  },}

effects

effects are an object containing functions, just like actions.

effects can only change the state by calling actions.

effects may also be impure and trigger side-effects outside of hyperapp.

const effects = {  waitASec: [state, async state => {    await new Promise((r) => setTimeout(r, 1000))    return actions.plus(state)  }],}

subscriptions

at the moment, subscriptions can be defined using strings as function names.

also, if a module exports a subscriptions array, those subscriptions will be added to the app.

// in a module:export const lib = {  subscribe: (dispatch, action) => {    setInterval(() => {      dispatch(action, { arg: Math.random() * 100 })    })  },}export const actions = {  onSubscription: (state, e) => ({    ...state,    arg: e.arg,  })}export const subscriptions = ['lib.subscribe', 'actions.onSubscription']

views

views render the state to html +whenever an action triggers a change in the state, this then triggers a view change.

export const View = state => div(JSON.stringify(state))

styles

every module can export a style object. +magic will automagically merge all styles into one global css file. +in the future, it will also remove unused styles for you.

styles merge order, last overwrites first: +module.style < page.style < app.style < theme.style

this allows css to be inherited in both directions.

a module automatically creates it's own css namespace, +that same namespace can be used in page, app and theme css styles +to overwrite module specific styles.

@magic/css

internally, magic uses it's own css-in-js library.

to find out more, click the following link: +@magic/css

globals

every module can set a global object, containing state and action properties.

every state and/or action name in the global object with a value that equals true gets merged into the main app state/actions, instead of into the module namespace.

// in module.mjsexport const state = {  internal: 0,  external: 0,}export const actions = {  internal: state => ({    ...state,    module: {      internal: state.module.internal + 1,    },  }),  external: state => ({    ...state,    external: state.external + 1,  }),}export const global = {  state: {    external: true  },  actions: {    external: true,  },}// in the view, we can use both the global and local actions and state variablesexport const View = state => div([  'view',  p({ onclick: actions.module.internal }, ['internal:', state.module.internal]),  p({ onclick: actions.external }, ['external:', state.external]),])

server lambdas

this is the serverside magic. +you can define functions that get transpiled into serverside lambdas. +server side lambdas will be available for GET and/or POST requests.

the server side function signature is (req, res) => {}, +as it is in most nodejs http servers, +with the addition of req.body being async => awaited before execution of the lambda.

\ No newline at end of file diff --git a/docs/files/index.html b/docs/files/index.html index 902c00f7..84612e6a 100644 --- a/docs/files/index.html +++ b/docs/files/index.html @@ -1 +1 @@ -@magic/core files

@magic/core files

There are multiple magic files and directories.

  • /pages - files in the page directory map to urls in your app.
  • /assets - custom components, @magic-modules get imported here
  • /assets/static - static files
  • /assets/themes - theme directory, @magic-themes get imported here
  • /assets/lib.mjs - imports npm and local but external packages into your app
  • /app.mjs - gets merged into the app, can set state, actions, style here
  • /config.mjs - custom config for your app
  • /assets/Menu.mjs - custom Menu for your app

/pages

the pages dir contains the pages of your webapp.

each page has it's own state and actions, but also inherits the global state and actions from the app and it's dependencies

pages directory to url map, for the domain mag.ic:

/pages/index.mjs === http://mag.ic//pages/pageName.mjs === http://mag.ic/pageName//pages/page-name.mjs === http://mag.ic/page-name//pages/page_name.mjs === http://mag.ic/page_name//pages/dir/index.mjs === http://mag.ic/dir//pages/dir/name.mjs === http://mag.ic/dir/name/

example page:

export default {  state: {    variable: 'test',  },  actions: {    changeVar: () => ({ variable: 'changed' }),  },  style: {    '.cl': {      color: 'green',    },  },  View: state => div({ class: 'cl' }, [    'this is the page content.',    state.variable,  ]),}

/assets

the assets dir contains custom components of your app.

you can additionally import @magic-modules here

example /assets/index.mjs

export default {  Custom: () => div('custom component'),  Pre: require('@magic-modules/pre),'}

/assets/static

the static dir contains all of your static assets.

every file in this directory gets copied to the public dir.

image and svg files get minified using imagemin

text and binary files get compressed using the optional node-zopfli-es (if it is installed)

/assets/themes

the themes directory contains... themes.

a magic theme is an object of css rules, see @magic/css for more examples and documentation.

example theme

export default {  'body': {    color: 'blue',  },}

/assets/app.mjs

the /app.mjs file allows you to set global state, actions, and styles

example /app.mjs

export default {  state: {    globalStateVar: 'globally available',  },  actions: {    globalAction: () => ({ globalStateVar: 'overwritten.' }),  },  style: {    'body': {      color: 'green',    },  },}

/config.mjs

the /config.mjs file allows you to set various aspects of your app

example /config.mjs

export default {  ROOT: 'example',  THEME: 'blue',  WEB_ROOT: '/core/',  // this option adds the  // 'X-Clacks-Overhead', 'GNU Terry Pratchet'  // http header  // see http://www.gnuterrypratchett.com/  FOR_DEATH_CAN_NOT_HAVE_HIM: true,  // default CLIENT_LIB_NAME, overwrite to change names of transpiled css and js files  CLIENT_LIB_NAME: 'magic',}
Menu.mjs on github
\ No newline at end of file +@magic/core documentation

@magic

@magic/core files

There are multiple magic files and directories.

  • /pages - files in the page directory map to urls in your app.
  • /assets - custom components, @magic-modules get imported here
  • /assets/static - static files
  • /assets/themes - theme directory, @magic-themes get imported here
  • /assets/lib.mjs - imports npm and local but external packages into your app
  • /app.mjs - gets merged into the app, can set state, actions, style here
  • /config.mjs - custom config for your app
  • /assets/Menu.mjs - custom Menu for your app

/pages

the pages dir contains the pages of your webapp.

each page has it's own state and actions, but also inherits the global state and actions from the app and it's dependencies

pages directory to url map

for the domain mag.ic:

/pages/index.mjs === http://mag.ic//pages/pageName.mjs === http://mag.ic/pageName//pages/page-name.mjs === http://mag.ic/page-name//pages/page_name.mjs === http://mag.ic/page_name//pages/dir/index.mjs === http://mag.ic/dir//pages/dir/name.mjs === http://mag.ic/dir/name/

example page

Pages can use javascript, html or markdown

javascript example

A magic javascript page is a @magic-module. The only difference is that pages get exposed via http

export const View = state =>  div({ class: 'cl' }, [    'this is the page content.',    state.variable,  ])export const state = {  variable: 'test',}export const actions = {  changeVar: state => ({ ...state, variable: 'changed' }),}export const style = {  '.cl': {    color: 'green',  },}

html example

html pages can only export state and View.

---@state{  "title": "html file example",    "description": "this module gets imported from a html file."}---<h2>&#36;{ state.title }</h4><p>{{ state.description }}</p>

markdown example

markdown pages can only export state and View.

---@state {  "title": "markdown file example",  "description": "markdown file description"}---## &#36;{state.title}&#36;{state.description}

assets

the assets dir contains custom components of your app.

you can import additional @magic-modules here

/assets/index.mjs

export default {  Custom: () => div('custom component'),  Pre: require('@magic-modules/pre),}

/assets/static

the static dir contains all of your static assets.every file in this directory gets copied to the public dir.image and svg files get minified using imagemin.

text and binary files get compressed using the optional node-zopfli-es (if it is installed)

/assets/themes

the themes directory contains...themes.

a magic theme is an object of css rules, see @magic/css for more examples and documentation.

example theme

export default {  'body': {    color: 'blue',  },}

/app.mjs

the /app.mjs file allows you to set global state, actions, and styles

/example/app.mjs

export const state = {  globalStateVar: 'globally available',}export const actions = {  globalAction: () => ({ globalStateVar: 'overwritten.' }),}export const style = {  'body': {    color: 'green',  },}

/config.mjs

the /config.mjs file allows you to set various aspects of your app

/config.mjs example

export default {  // the local root directory of the magic app  ROOT: 'example',  // gets suffixed to the url. in this case domain.com/core/ will be the page root.  WEB_ROOT: '/core/',  // the theme in use, lives in {ROOT}/assets/themes/{name} or can be installed as either '@magic-themes/{name}' or 'magic-theme-{name}'  THEME: 'docs',  // the full url of the page.  URL: 'magic.github.io/core/',  // the public dir the bundles get written to  PUBLIC: 'docs',  // the directory in the pages directory that holds the blog posts  // if this is not set, no blog is created.  BLOG_DIR: 'news',  // append these modules after the app loaded and only if javascript is enabled.  // can be used to add features that won't do anything without js anyways.  HOIST: ['LightSwitch', 'NoSpy'],  // default CLIENT_LIB_NAME, overwrite to change names of transpiled css and js files  CLIENT_LIB_NAME: 'magic',  // this is set to be able to test broken link behaviour.  // DO NOT ENABLE IN YOUR APP (unless you need to because reasons...)  NO_CHECK_LINKS_EXIT: true,  // tags written to the html before the #magic container  PREPEND_TAGS: [{ name: 'div', props: { id: 'PREPENDTag' } }],  // tags written to the html after the #magic container  APPEND_TAGS: [{ name: 'div', props: { id: 'APPENDTag' } }],  // script files, embedded before the magic.js script  PREPEND_SCRIPTS: ['/prependScript.js'],  // script files, embedded after the magic.js script  APPEND_SCRIPTS: ['/appendScript.js'],  // js files that get written directly into the magic.js script, before the magic code.  PREPEND_JS: ['/prependJs.js'],  // js files that get written directly into the magic.js script, after the magic code.  APPEND_JS: ['/appendJs.js'],  // css written into the magic.css file, before the magic css  // use this to set defaults that get overwritten by the generated app css  PREPEND_CSS: ['/prependCss.css'],  // css written into the magic.css file, after the magic css  // use this to overwrite the generated app css  APPEND_CSS: ['/appendCss.css'],  // add additional static file directories that will get added to app.static[name]  ADD_STATIC: ['node_modules/dir/name/'],  // this option adds the  // 'X-Clacks-Overhead', 'GNU Terry Pratchet'  // http header  // see http://www.gnuterrypratchett.com/  FOR_DEATH_CAN_NOT_HAVE_HIM: true,}

Menu.mjs on github

\ No newline at end of file diff --git a/docs/index.html b/docs/index.html index efbf78d0..b459ad97 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1 +1,20 @@ -@magic/core docs

@magic/core docs

welcome to the magic docs.

the goal of this document is to give you a rough @magical overview.

philosophy

@magic aims to make it easy to stitch together any kind of webapp. by providing simple, well documented and self contained modules, @magic makes it possible to create stunningly fast webpages with minimal cognitive overhead.

privacy

@magic does not spy.

not only do we try to be legally compliant by default, but we also aim to be ethical which means prioritizing your rights over our needs, but also means we prioritize the rights of your users over your needs. we believe that this is the best compromise.

why should i use magic?

features

  • static html pages with opengraph seo.
  • pages are hosted for free using gitlab, github or any other git-pages style hosting.
  • static css output with selector and rule deduplication.
  • no javascript required where possible.
  • minimal client boilerplate.
  • no spyware included.
  • WIP: lambda faas and graphql api generator.
  • WIP: server side rendering (if needed).

@magic is tiny

~4 kb javascript boilerplate. usually, all the javascript in your homepage will be 30-60kb big (after unpacking), 10-30kb get transmitted from the server to the client. this documentation page you are reading loads about 15kb of javascript, which, when parsing, turns into 40kb of uncompressed javascript.

@magic works without javascript

most of the functionality works without javascript, some buttons and realtime user interactions obviously won't, but @magic always tries to provide a non-javascript fallback via css.

@magic generates static pages

this makes free hosting (using github or gitlab pages) possible. and it's easy.

@magic publishes to github, gitlab and any other git-pages enabled hosting service.

serverless / faas

automagically generates serverless lambdas, derived from the @magic-modules you use in your pages. this makes visitor statistics, user authentication and authorization, chat, and all other server side services possible.

\ No newline at end of file +@magic/core documentation

@magic

@magic/core documentation

welcome to the magic docs.

@magic/core documentation. tells you why, how and when to use @magic. also provides an overview of all @magic functionality this ecosystem provides.

the goal of this document is to give you a rough @magical overview.

philosophy

@magic aims to make it easy to stitch together any kind of webapp. +by providing simple, well documented and self contained modules, +@magic makes it possible to create stunningly fast +webpages with minimal cognitive overhead.

privacy

@magic does not spy.

not only do we try to be legally compliant by default, +but we also aim to be ethical +which means prioritizing your rights over our needs, +but also means we prioritize the rights of your users over your needs. +we believe that this is the best compromise.

why should i use magic?

features

  • static html pages with opengraph seo.
  • pages are hosted for free using gitlab, github or any other git-pages style hosting.
  • static css output with selector and rule deduplication.
  • no javascript required where possible.
  • minimal client boilerplate.
  • no spyware included.
  • WIP: lambda faas and graphql api generator.
  • WIP: server side rendering (if needed).

@magic is tiny

~4 kb javascript boilerplate. +usually, all the javascript in your homepage will be 30-60kb big (after unpacking), +10-30kb get transmitted from the server to the client. +this documentation page you are reading loads 20kb of gzipped javascript, +which, when parsing, turns into ~88kb of uncompressed javascript. +this includes all content of all subpages, no additional server requests needed for any navigations in the page.

WIP: code splitting for bigger pages.

@magic works without javascript

most of the functionality works without javascript, +some buttons and realtime user interactions obviously won't, +but @magic always tries to provide a non-javascript fallback via css.

@magic generates static pages

this makes free hosting (using github or gitlab pages) possible. and it's easy.

@magic publishes to github, gitlab, +and any other git-pages enabled hosting service.

serverless / faas

automagically generates +serverless lambdas, derived from the +@magic-modules +you use in your pages.

this makes visitor statistics, user authentication and authorization, +chat, and all other server side services possible.

\ No newline at end of file diff --git a/docs/libraries/index.html b/docs/libraries/index.html index a484ae66..0471104e 100644 --- a/docs/libraries/index.html +++ b/docs/libraries/index.html @@ -1 +1,14 @@ -@magic/core library docs

@magic/core library docs

@magic/core libraries allow you to include client side functionality in your app.

libraries

what would javascript be without the millions of dependencies that you can easily install and make the average webpage slow[ly] (pun intended) grow towards a footprint of 5 megabytes.

we think that all of that bloat is unneeded, unheeded and, frankly, not optimal.

magic has one external client side dependency, hyperapp, [~400 lines that provide our ui state machine]. thats it. and it won't change.

we also have the tendency to write libraries specialized for our usecase, see @magic/css @magic/test @magic/cli and others.

once there is a lib key in at least one component, window.lib (browser) and global.lib (nodejs) will be set, aliasing lib as a global variable in both environments

lib dir or file

if you need libraries in multiple otherwise independent modules, it might be easier to keep your library dependencies in a central place. to achieve this, one can simply create /assets/lib.mjs and export an object from it. this object will get merged into the globalThis.lib object making it available as "lib" throughout your app.

alternatively, you can create a /assets/modules directory and place UpperCaseNamed.mjs files in there. their names will be deferred from the filename and they will be available in your app without importing them. if you do not use one of the modules, dead code elimination will simply remove it in production.

export default { name: () => {} }
will turn into
lib.name = () => {}

@magic-libraries from npm

all @magic-libraries/* and all npm packages starting with magic-library-* will be loaded automatically.

Example

first, install a @magic-library

npm install --save --save-exact @magic-libraries/is

then, in javascript

export const View = props => div([  'value is ',  lib.is(props.value, 'string') ? '' : 'not',  ' a string'])

@magic-libraries

below is a collection of the available @magic client libraries.
\ No newline at end of file +@magic/core documentation

@magic

@magic/core library docs

@magic/core libraries allow you to include client side functionality in your app.

libraries

what would javascript be without the millions of dependencies +that you can easily install and make the average webpage +slow[ly] (pun intended) grow towards a footprint of 5 megabytes.

we think that all of that bloat is unneeded, unheeded and, frankly, not optimal.

magic has one external client side dependency, +hyperapp, +[~500 lines that provide our ui state machine]. thats it. and it won't change.

we also have the tendency to write libraries specialized for our usecase, see +@magic/css, +@magic/test, +@magic/cli +and others.

once there is a lib key in at least one component, +window.lib (browser) and global.lib (nodejs) will be set, +aliasing lib as a global variable in both environments

lib dir or file

if you need libraries in multiple otherwise independent modules, +it might be easier to keep your library dependencies in a central place.

to achieve this, one can simply create /assets/lib.mjs and export an object from it. +this object will get merged into the globalThis.lib object, +making it available as "lib" throughout your app.

export default { name: () => {} }

will turn into

lib.name = () => {}

npm

all @magic-libraries and all npm packages starting with magic-library-* will be loaded automatically

Example

first, install a @magic-library

npm install --save --save-exact @magic-libraries/is

then, in javascript

export const View = props => div([  'value is ',  lib.is(props.value, 'string') ? '' : 'not',  ' a string',])

@magic-libraries

@magic libraries are pieces of client side functionality. they are not intended to be used in markdown, instead, a developer will use them when creating @magic-modules, which can then be used from markdown pages.
\ No newline at end of file diff --git a/docs/logo.png b/docs/logo.png index 01dc77a7..a0fa6ed6 100644 Binary files a/docs/logo.png and b/docs/logo.png differ diff --git a/docs/magic.css b/docs/magic.css index 4bd186a3..3edcaef7 100644 --- a/docs/magic.css +++ b/docs/magic.css @@ -1 +1 @@ -html{scroll-behavior:smooth;overflow-y:scroll}h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,img,picture,source,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video,div,body{border:0;box-sizing:border-box;font:inherit;font-size:100%;margin:0;padding:0;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1.5;margin:0;max-width:100vw;overflow-x:hidden;padding:0;font-family:"Ubuntu Narrow",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,sans-serif}ul{display:inline-block;list-style:none}ol{display:inline-block;list-style:inside decimal}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}a{cursor:pointer;outline:none;white-space:nowrap;color:#eeeeee;text-decoration:underline;transition:color 500ms}button,.button,input[type="reset"],input[type="button"],input[type="submit"],input[type="file"] > input[type="button"]{background-color:#9E9E9E;color:#212121;cursor:pointer;padding:0.5em}button:hover,.button:hover,input[type="reset"]:hover,input[type="button"]:hover,input[type="submit"]:hover,input[type="file"] > input[type="button"]:hover{background-color:#616161;color:#F5F5F5}button::-moz-focus-inner,.button::-moz-focus-inner,input[type="reset"]::-moz-focus-inner,input[type="button"]::-moz-focus-inner,input[type="submit"]::-moz-focus-inner,input[type="file"] > input[type="button"]::-moz-focus-inner{border:0 none;padding:0}#Magic{height:100%;min-height:100vh;width:100%;background-color:#232323;color:#cccccc;transition:color 300ms,background-color 300ms;font-weight:400}.Wrapper{margin:0 auto;max-width:1300px;width:90%}.Header{display:inline-block;width:90%}.Header .Logo{display:inline-block}.Header .Logo img{float:left;margin:0.3em 0 0}.Header .Logo span{float:left;font-size:1.1em;margin:.7em .2em}.Page h1{padding:1em 0 .2em}.Footer{width:100%;padding:2em 0 .5em;position:relative}h1,h2,h3,h4,h5{padding:1em 0 .2em}h1{font-size:1.4em}h2{font-size:1.3em}h3{font-size:1.2em}.Footer .Container{text-align:center}.Footer .Credits{display:block;clear:both}.Gdpr{-webkit-animation:showGdpr 1s 1s forwards;animation:showGdpr 1s 1s forwards;bottom:0.5em;left:3%;max-width:94%;opacity:0;position:fixed;text-align:center}.Gdpr .Container{background-color:#212121;border:1px solid;border-radius:.5em;color:#F5F5F5;display:inline-block;padding:1em;position:relative;text-align:left}.light .Gdpr .Container{background-color:#F5F5F5;color:#212121}.Gdpr .ShowHide{position:fixed;left:1em;bottom:1em;width:2em;height:2em;cursor:pointer;color:#9E9E9E;stroke:#9E9E9E;fill:#9E9E9E}.Gdpr .ShowHide:hover{color:#616161;stroke:#616161;fill:#616161}.Gdpr h3{padding:0;margin:0}.Gdpr h4{display:inline;font-weight:bold}.Gdpr input[type=button]{display:inline-block;margin:0.5em 0;width:100%}.Gdpr ul{display:block}@-webkit-keyframes showGdpr{0%{opacity:0}100%{opacity:1}}@keyframes showGdpr{0%{opacity:0}100%{opacity:1}}.GitBadges{display:inline-block}.GitBadges img{height:20px}.GitBadges li{margin:0 .5em 0 0;display:inline-block;float:left}.GitList h3{margin:0}.GitList li{padding:1.5em 0 0}.GitList li li{padding:0.2em 0 0}.GitList li a{display:block}.LightSwitch{background:none;border:none;color:inherit;cursor:pointer;fill:#9E9E9E;font:inherit;height:2em;outline:inherit;padding:0;width:2em;left:auto;position:fixed;right:0.5em;top:0.5em}.LightSwitch:hover{background:none;fill:#616161}.Menu{float:none;margin:1.5em 0 0;position:relative;display:block;clear:both}.Menu li{float:none;margin:0 .5em 0 0;display:block}.Menu li.active > a{text-decoration:underline;color:#fefefe}.Menu li a{display:block;text-decoration:none}.Menu ul ul{position:relative;left:0;border-left:1px dotted;display:block;margin:0 0 0 0.2em;padding:0 0 0 0.3em}.Mod{border:1px solid;border-color:green;margin:0 0 1em;padding:0.5em}.Mod h3{margin:0;color:undefined}.ModComponent{border:1px solid orange}.Pre{background-color:#121212;border:1px solid #666;border-radius:5px;color:#eee;counter-reset:line;display:block;font-family:monospace;line-height:1.3;margin:1em 0;padding:1em .5em 0 .2em;position:relative;transition:color 300ms,background-color 300ms;white-space:pre}.Pre.lines pre > code{counter-increment:line;padding:0}.Pre.lines pre > code:last-child{padding:0 0 1em}.Pre.lines pre > code:before{content:counter(line);display:inline-block;padding:0 .5em 0 0;text-align:right;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;width:3ch}.Pre .actions{color:blueviolet}.Pre .boolean{color:#7979e4}.Pre .builtin{color:cadetblue}.Pre .colon{color:#8eef8e}.Pre .comment{opacity:0.7;font-style:italic}.Pre .html{color:#008800}.Pre .keyword{color:violet}.light .Pre{background-color:#ddd;color:#111}.light .Pre > code:before{color:#666}.light .Pre .html{color:#008800}.light .Pre .keyword{color:purple}.light .Pre .builtin{color:cadetblue}.light .Pre .string{color:#d15100}.light .Pre .colon{color:#016301}.light .Pre .boolean{color:blue}.light .Pre .actions{color:blueviolet}.light .Pre .state{color:cornflowerblue}.light .Pre .comment{color:#555;opacity:0.8;font-style:italic}.light .Pre .object{color:#016301}.light .Pre .property{color:#2a952a}.light .Pre a{color:#d15100}.light .Pre a:hover{color:turquoise}.Pre .menu{position:absolute;margin-top:-2em;right:0}.Pre .object{color:#00ff00}.Pre .property{color:#8eef8e}.Pre .state{color:cornflowerblue}.Pre .string{color:#dd8f00}.Pre > code:before{color:#666}.Pre a{color:turquoise}.Pre a:hover{color:#d15100}.Pre button{background-color:#666;border-radius:3px;color:#fefefe;margin:0 .2em;padding:1px 3px;border:0 none}.Pre pre{overflow-x:auto}.Pre pre > code{display:block;padding:0 1em 0}.Pre pre > code:last-child{padding:0 1em 1em}.SkipLink{height:1px;left:0;overflow:hidden;position:absolute;top:0;width:1px}#Magic.light{background-color:#eeeeee;color:#232323}h1:target,h2:target,h3:target,h4:target,h5:target{text-decoration:underline}:target > h1:first-child,:target > h2:first-child,:target > h3:first-child,:target > h4:first-child,:target > h5:first-child{text-decoration:underline}a:hover{color:#fefefe}.light a{color:#232323}.light a:hover{color:#232323}.Page{margin-bottom:3em}h4{font-size:1.1em}p{line-height:1.8}.light .Menu li.active > a{color:#232323}.Menu li.active li a{text-decoration:none}@media screen and (min-width:900px){.Gdpr input[type=button]{margin:1em 3% 0 0;max-width:30%;width:auto}}@media screen and (min-width:1000px){.Header{width:210px;position:fixed;float:left;max-height:100vh;height:100vh;overflow-y:auto;overflow-x:hidden;scrollbar-width:thin;box-sizing:border-box}.Menu{max-width:210px;padding:0 0 2em}.Page{float:right;width:calc(100% - 220px);min-height:83vh}.Footer{clear:both}} \ No newline at end of file +h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,img,picture,source,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video,div,body{border:0;box-sizing:border-box;font:inherit;font-size:100%;margin:0;padding:0;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1.5;margin:0;max-width:100vw;overflow-x:hidden;overflow-y:scroll;padding:0;scroll-behavior:smooth;font-family:"Ubuntu Narrow",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,sans-serif}ul{display:inline-block;list-style:none}ol{display:inline-block;list-style:inside decimal}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}a{cursor:pointer;outline:none;white-space:nowrap;color:#eeeeee;text-decoration:underline;transition:color 500ms}b,strong{font-weight:700}i,em{font-style:italic}button,.button,input[type="reset"],input[type="button"],input[type="submit"],input[type="file"] > input[type="button"]{background-color:#9E9E9E;color:#212121;cursor:pointer;padding:0.5em}button:hover,.button:hover,input[type="reset"]:hover,input[type="button"]:hover,input[type="submit"]:hover,input[type="file"] > input[type="button"]:hover{background-color:#616161;color:#F5F5F5}button::-moz-focus-inner,.button::-moz-focus-inner,input[type="reset"]::-moz-focus-inner,input[type="button"]::-moz-focus-inner,input[type="submit"]::-moz-focus-inner,input[type="file"] > input[type="button"]::-moz-focus-inner{border:0 none;padding:0}#Magic{height:100%;min-height:100vh;width:100%;background-color:#232323;color:#cccccc;transition:color 300ms,background-color 300ms;font-weight:400}#Magic .icon{width:1.5em;height:auto;animation:showHoisted 500ms 0s forwards;opacity:0}.Wrapper{margin:0 auto;max-width:1300px;width:90%}.Header{display:inline-block;width:100%;padding:.5em 0 0}.Header .Logo{display:inline-block}.Header .Logo img{float:left}.Header .Logo span{float:left;font-size:1.5em;margin:.7em .2em}.Page h1{padding:1em 0 .2em}.Footer{width:100%;padding:2em 0 .5em;position:relative}h1,h2,h3,h4,h5{padding:1em 0 .2em}h1{font-size:1.4em}h2{font-size:1.3em}h3{font-size:1.2em}@keyframes showHoisted{0%{opacity:0}100%{opacity:1}}.Component{border:1px solid orange}.Credits{clear:both;display:block;margin:2em 0 1em}.Credits a{color:#cccccc;text-decoration:underline}.Credits a:hover{color:#c4c4c4}.Footer .Container{text-align:center}.GitBadges{display:inline-block;margin:15px 0}.GitBadges img{height:23px}.GitBadges li{margin:0 .5em 0 0;display:inline-block;float:left}.GitList h3{margin:0}.GitList li{padding:1.5em 0 0}.GitList li li{padding:0.2em 0 0}.Header .Menu{display:inline-block;width:100%}.Header p{font-size:1.5em}.LightSwitch{bottom:2.5em;cursor:pointer;fill:#9E9E9E;left:0.5em;padding:0;position:fixed}.LightSwitch:hover{fill:#616161}.Logo{float:left;height:2.4em}.Logo .stroke{fill:none;stroke:#663695;stroke-width:15;stroke-linecap:round;stroke-linejoin:round}.Logo .stroke.white{stroke:#ffffff}.Logo svg{display:inline-block;width:3em;height:auto}.Menu{float:none;margin:1.5em 0 0;position:relative;display:block;clear:both}.Menu li{float:none;margin:0 .5em 0 0;display:block}.Menu li.active > a{text-decoration:underline;color:#fefefe}.Menu li a{display:block;text-decoration:none}.Menu ul ul{position:relative;left:0;border-left:1px dotted;display:block;margin:0 0 0 0.2em;padding:0 0 0 0.3em}.Mod{border:1px solid;border-color:green;margin:0 0 1em;padding:0.5em}.Mod h3{margin:0;color:#cccccc}.NoSpy{bottom:0.5em;left:0.5em;position:fixed}.NoSpy .Background{height:100vh;width:100vw;overflow:hidden;left:0;top:0;position:fixed;padding:0;margin:0}.NoSpy .Container{background-color:#212121;border:1px solid;border-radius:.5em;color:#F5F5F5;display:inline-block;padding:1em;position:absolute;text-align:left;left:0;bottom:0;width:20em;max-width:calc(100vw - 1em)}.light .NoSpy .Container{background-color:#F5F5F5;color:#212121}.NoSpy .icon{position:absolute;left:0;bottom:0;cursor:pointer;color:#9E9E9E;stroke:#9E9E9E;fill:#9E9E9E}.NoSpy .icon:hover{color:#616161;stroke:#616161;fill:#616161}.NoSpy h3{padding:0;margin:0}.NoSpy input[type=button]{display:inline-block;margin:0.5em 0;width:100%}.Pre{background-color:#121212;border:1px solid #666;border-radius:5px;color:#eee;counter-reset:line;display:block;font-family:monospace;line-height:1.3;margin:1em 0;padding:1em .5em 0 .2em;position:relative;text-align:left;transition:color 300ms,background-color 300ms;white-space:pre}.Pre.lines pre > code{counter-increment:line;padding:0}.Pre.lines pre > code:last-child{padding:0 0 1em}.Pre.lines pre > code:before{content:counter(line);display:inline-block;padding:0 .5em 0 0;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:3ch}.Pre .actions{color:blueviolet}.Pre .boolean{color:#7979e4}.Pre .builtin{color:cadetblue}.Pre .colon{color:#8eef8e}.Pre .comment{opacity:0.7;font-style:italic}.Pre .html{color:#008800}.Pre .keyword{color:violet}.light .Pre{background-color:#ddd;color:#111}.light .Pre > code:before{color:#666}.light .Pre .html{color:#008800}.light .Pre .keyword{color:purple}.light .Pre .builtin{color:cadetblue}.light .Pre .string{color:#d15100}.light .Pre .colon{color:#016301}.light .Pre .boolean{color:blue}.light .Pre .actions{color:blueviolet}.light .Pre .state{color:cornflowerblue}.light .Pre .comment{color:#555;opacity:0.8;font-style:italic}.light .Pre .object{color:#016301}.light .Pre .property{color:#2a952a}.light .Pre a{color:#d15100}.light .Pre a:hover{color:turquoise}.Pre .menu{position:absolute;margin-top:-1em;right:0}.Pre .object{color:#00ff00}.Pre .property{color:#8eef8e}.Pre .state{color:cornflowerblue}.Pre .string{color:#dd8f00}.Pre > code:before{color:#666}.Pre a{color:turquoise}.Pre a:hover{color:#d15100}.Pre button{background-color:#666;border-radius:3px;color:#fefefe;margin:0 .2em;padding:1px 3px;border:0 none}.Pre pre{overflow-x:auto}.Pre pre > code{display:block;padding:0 1em 0}.Pre pre > code:last-child{padding:0 1em 1em}.SkipLink{height:1px;left:0;overflow:hidden;position:absolute;top:0;width:1px}*{box-sizing:border-box}#Magic.light{background-color:#eeeeee;color:#232323}h1:target,h2:target,h3:target,h4:target,h5:target{text-decoration:underline}:target > h1:first-child,:target > h2:first-child,:target > h3:first-child,:target > h4:first-child,:target > h5:first-child{text-decoration:underline}a:hover{color:#fefefe}.light a{color:#232323}.light a:hover{color:#232323}.Page{margin-bottom:3em}h4{font-size:1.1em}p{line-height:1.8}.light .Menu li.active > a{color:#232323}.Menu li.active li a{text-decoration:none}@media screen and (min-width:1024px){.Header{box-sizing:border-box;height:100vh;max-height:100vh;overflow-y:auto;overflow-x:hidden;position:fixed;scrollbar-width:thin;width:210px}.Header p{font-size:1em}.Header .Menu{max-width:210px;padding:0 0 2em}.Logo{float:none}.NoSpy input[type=button]{margin:1em 3% 0 0;max-width:30%;width:auto}.Menu{max-width:210px;padding:0 0 2em}.Page{float:right;width:calc(100% - 220px);min-height:83vh}.Footer{clear:both}.Menu{max-width:210px;padding:0 0 2em}.Page{float:right;width:calc(100% - 220px);min-height:83vh}.Footer{clear:both}}@media screen and (min-width:500px){.Header .Menu{float:left;width:auto}} \ No newline at end of file diff --git a/docs/magic.js b/docs/magic.js index 3b023923..40a3c5b3 100644 --- a/docs/magic.js +++ b/docs/magic.js @@ -1 +1,554 @@ -"use strict";function e(e){return u(e)||t(e)||m()}function t(e){if(Symbol.iterator in Object(e)||"[object Arguments]"===Object.prototype.toString.call(e))return Array.from(e)}function o(e,t){if(null==e)return{};var o,a,n=s(e,t);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);for(a=0;aM)for(;j<=C;)o.insertBefore(u(v[j]=x(v[j++]),n,l),(m=w[k])&&m.node);else if(j>C)for(;k<=M;)o.removeChild(w[k++].node);else{for(var T=k,E={},A={};T<=M;T++)null!=(d=w[T].key)&&(E[d]=w[T]);for(;j<=C;){if(d=h(m=w[k]),p=h(v[j]=x(v[j],m)),A[d]||null!=p&&p===h(w[k+1])){null==d&&o.removeChild(m.node),k++;continue}null==p||1===a.type?(null==d&&(y(o,m&&m.node,m,v[j],n,l),j++),k++):(d===p?(y(o,m.node,m,v[j],n,l),A[p]=!0,k++):null==(c=E[p])?y(o,m&&m.node,null,v[j],n,l):(y(o,o.insertBefore(c.node,m&&m.node),c,v[j],n,l),A[p]=!0),j++)}for(;k<=M;)null==h(m=w[k++])&&o.removeChild(m.node);for(var T in E)null==A[T]&&o.removeChild(E[T].node)}}return s.node=o},b=function(e,t){for(var o in e)if(e[o]!==t[o])return!0;for(var o in t)if(e[o]!==t[o])return!0},w=function(e){return"object"===f(e)?e:k(e)},x=function(t,o){return t.type===e?((!o||o.type!==e||b(o.lazy,t.lazy))&&((o=w(t.lazy.view(t.lazy))).lazy=t.lazy),o):t},v=function(e,t,o,a,i,s){return{name:e,props:t,children:o,node:a,type:s,key:i}},k=function(e,i){return v(e,o,a,i,void 0,t)},j=function(e){return e.nodeType===t?k(e.nodeValue,e):v(e.nodeName.toLowerCase(),o,i.call(e.childNodes,j),e,void 0,1)};return{h:function h(e,t){for(var a,n=[],l=[],r=arguments.length;2 value store using localstorage"},{name:"gql",description:["the @magic-libraries/gql module"," encodes template strings to graphql queries ready to be sent to a server."]},{name:"is",description:"the @magic-libraries/is module unifies the javascript type testing apis."},{name:"json",description:["the @magic-libraries/json module parses and stringifies json."," it also returns errors instead of throwing them."]},{name:"prevent-default",description:["the @magic-libraries/prevent-default module"," invokes event.preventDefault as a hyperapp effect."]},{name:"uri",description:["the @magic-libraries/uri module"," encodes objects to uri strings and decodes uri strings to objects."]}]})},ne=function(){0 {},"," as in any nodejs http server, with the addition of req.body being async => awaited before execution of the lambda."])])]},"/core/files/":function coreFiles(e){var t={page:"\nexport default {\n state: {\n variable: 'test',\n },\n actions: {\n changeVar: () => ({ variable: 'changed' }),\n },\n style: {\n '.cl': {\n color: 'green',\n },\n },\n View: state => div({ class: 'cl' }, [\n 'this is the page content.',\n state.variable,\n ]),\n}",assets:"\nexport default {\n Custom: () => div('custom component'),\n Pre: require('@magic-modules/pre),\n}",app:"\nexport default {\n state: {\n globalStateVar: 'globally available',\n },\n actions: {\n globalAction: () => ({ globalStateVar: 'overwritten.' }),\n },\n style: {\n 'body': {\n color: 'green',\n },\n },\n}",config:"\nexport default {\n ROOT: 'example',\n THEME: 'blue',\n WEB_ROOT: '/core/',\n\n // this option adds the\n // 'X-Clacks-Overhead', 'GNU Terry Pratchet'\n // http header\n // see http://www.gnuterrypratchett.com/\n FOR_DEATH_CAN_NOT_HAVE_HIM: true,\n\n // default CLIENT_LIB_NAME, overwrite to change names of transpiled css and js files\n CLIENT_LIB_NAME: 'magic',\n}",theme:"\nexport default {\n 'body': {\n color: 'blue',\n },\n}"};return[g(e.title),B("There are multiple magic files and directories."),F([H("/pages - files in the page directory map to urls in your app."),H("/assets - custom components, @magic-modules get imported here"),H("/assets/static - static files"),H("/assets/themes - theme directory, @magic-themes get imported here"),H("/assets/lib.mjs - imports npm and local but external packages into your app"),H("/app.mjs - gets merged into the app, can set state, actions, style here"),H("/config.mjs - custom config for your app"),H("/assets/Menu.mjs - custom Menu for your app")]),C({id:"pages"},"/pages"),B("the pages dir contains the pages of your webapp."),B(["each page has it's own state and actions, ","but also inherits the global state and actions from the app and it's dependencies"]),T({id:"pages-dir-structure"},"pages directory to url map, for the domain mag.ic:"),he("\n/pages/index.mjs === http://mag.ic/\n/pages/pageName.mjs === http://mag.ic/pageName/\n/pages/page-name.mjs === http://mag.ic/page-name/\n/pages/page_name.mjs === http://mag.ic/page_name/\n/pages/dir/index.mjs === http://mag.ic/dir/\n/pages/dir/name.mjs === http://mag.ic/dir/name/\n"),T({id:"pages-example"},"example page:"),he(t.page),C({id:"assets"},"/assets"),B("the assets dir contains custom components of your app."),B("you can additionally import @magic-modules here"),T({id:"assets-example"},"example /assets/index.mjs"),he(t.assets),C({id:"static"},"/assets/static"),B("the static dir contains all of your static assets."),B("every file in this directory gets copied to the public dir."),B("image and svg files get minified using imagemin"),B(["text and binary files get compressed using the optional ",le({to:"https://github.com/jaeh/node-zopfli-es"},"node-zopfli-es")," (if it is installed)"]),C({id:"themes"},"/assets/themes"),B("the themes directory contains... themes."),B(["a magic theme is an object of css rules, see ",le({to:"https://github.com/magic/css/"},"@magic/css")," for more examples and documentation."]),T({id:"themes-example"},"example theme"),he(t.theme),C({id:"app"},"/assets/app.mjs"),B("the /app.mjs file allows you to set global state, actions, and styles"),T({id:"app-example"},"example /app.mjs"),he(t.app),C({id:"config"},"/config.mjs"),B("the /config.mjs file allows you to set various aspects of your app"),T({id:"config-example"},"example /config.mjs"),he(t.config),le({to:"https://github.com/magic/core/blob/master/src/modules/Menu.mjs"},"Menu.mjs on github")]},"/core/libraries/":function coreLibraries(e){return[k([g(e.title),B(e.description),C({id:"abstract"},"libraries"),B(["what would javascript be without the millions of dependencies"," that you can easily install and make the average webpage "," slow[ly] (pun intended) grow towards a footprint of 5 megabytes."]),B("we think that all of that bloat is unneeded, unheeded and, frankly, not optimal."),B(["magic has one external client side dependency, ",le({to:"https://github.com/jorgebucharan/hyperapp"},"hyperapp"),", [~400 lines that provide our ui state machine]. thats it. and it won't change."]),B(["we also have the tendency to write libraries specialized for our usecase, see ",le({href:"https://github.com/magic/css"},"@magic/css")," ",le({href:"https://github.com/magic/test"},"@magic/test")," ",le({href:"https://github.com/magic/cli"},"@magic/cli")," and others."]),B(["once there is a lib key in at least one component,"," window.lib (browser) and global.lib (nodejs) will be set,"," aliasing lib as a global variable in both environments"]),T({id:"dir-or-file"},"lib dir or file"),B(["if you need libraries in multiple otherwise independent modules,"," it might be easier to keep your library dependencies in a central place."," to achieve this, one can simply create /assets/lib.mjs and export an object from it."," this object will get merged into the globalThis.lib object making it available as \"lib\" throughout your app."]),B(["alternatively, you can create a /assets/modules directory and place UpperCaseNamed.mjs files in there."," their names will be deferred from the filename and they will be available in your app without importing them."," if you do not use one of the modules, dead code elimination will simply remove it in production."]),he("export default { name: () => {} }"),"will turn into",he("lib.name = () => {}"),T({id:"npm"},"@magic-libraries from npm"),B("all @magic-libraries/* and all npm packages starting with magic-library-* will be loaded automatically. "),E({id:"example"},"Example"),B("first, install a @magic-library"),he("\nnpm install --save --save-exact @magic-libraries/is\n"),B("then, in javascript"),he("\nexport const View = props => div([\n 'value is ',\n lib.is(props.value, 'string') ? '' : 'not',\n ' a string'\n])\n "),se()])]},"/core/modules/":function coreModules(e){return[g(e.title),B("magic modules are predefined modules for webapps."),C({id:"definition"},"module definition:"),B("the minimal module is a function that returns some html."),he("\n// /assets/ModuleName.mjs\n\n// simplest module\nexport const View = () => div('hello, world')\n\n// complete signature\nexport const View = (props = {}, children = []) => div('hello, world')\n"),C({id:"usage"},"usage"),B(["if the npm package name starts with @magic-modules/ or magic-module-, it will get imported automagically."," the name of the Module will be set to a PascalCased version of the remainder of the module name."," @magic-modules/git-badges, for example, turns into GitBadges."," the same is true for all uppercased files in your /assets/ directory and subdirectories."," in the rare case where you want to install a npm module that can not be found, you can import it in /assets/index.mjs"]),he("\n// /assets/index.mjs\nexport default {\n // ...otherModules\n\n // load module from node_modules\n NpmModule: require('not-standard-named-magic-module-from-npm'),\n}"),B("after this, the module will be a global in your app and can be used like any other component."),he("\n// any page or module\nexport default state => div([\n // module without props\n Mod(),\n 'modules that need props: ',\n Mod({ state, customProp: true }),\n"),ge()]},"/core/modules/example/":function coreModulesExample(e){return[g("example module"),C({id:"custom-module"},"Mod and Mod.Component:"),pe(e),pe.Component({title:"Mod Component Title, passed via props"}),T("Mod sourcecode:"),he("\n export const View = state =>\n div({ class: 'Mod' }, [\n h3('Mod.Mod'),\n p([\n 'this is Mod. it gets loaded from ',\n Link({ to: 'https://github.com/magic/core/blob/master/example/assets/modules/Mod.mjs' }, '/assets/modules/Mod.mjs'),\n ' automatically, no need to import it.',\n ]),\n p(['the state of this module: ', JSON.stringify(state.module)]),\n ])\n\nexport const state = {\n module: {\n test: 'testing',\n },\n}\n\nexport const style = {\n margin: '0 0 1em',\n padding: '0.5em',\n border: '1px solid',\n borderColor: 'green',\n\n h3: {\n margin: 0,\n },\n}\n\nexport const global = {\n state: {\n module: true,\n },\n}\n\nexport const Component = props => {\n props = typeof props === 'string' ? { header: props } : props\n CHECK_PROPS(props, propTypes, 'ModComponent')\n const header = props.header || props.title\n\n return div({ class: 'ModComponent' }, [\n header && h5(header),\n p([\n 'Mod.Component, a second component in ',\n Link({ to: 'https://github.com/magic/core/example/assets/module.mjs' }, '/assets/module.mjs'),\n ]),\n ])\n}\n\nComponent.style = {\n border: '1px solid orange',\n}\n\nexport const propTypes = {\n ModComponent: [{ key: 'header', type: ['string', 'array'], required: ['title'] }],\n}\n")]},"/core/modules/html/":function coreModulesHtml(){return[C("html file example"),B("this module gets imported from a html file."),B(["see ",le({to:"https://github.com/magic/core/blob/master/example/pages/modules/html.html"},"this html file")," for an example."]),B("all html valid in can be used, excluding the

example module

Mod and Mod.Component:

Mod.Mod

this is Mod. it gets loaded from /assets/modules/Mod.mjs

the state of this module: {"test":"testing"}

Mod Component Title, passed via props

Mod.Component, a second component in /assets/modules/Mod.mjs

Mod sourcecode:

export const View = state =>  div({ class: 'Mod' }, [    h3('Mod.Mod'),    p([      'this is Mod. it gets loaded from ',      Link({ to: 'https://github.com/magic/core/blob/master/example/assets/modules/Mod.mjs' }, '/assets/modules/Mod.mjs'),      ' automatically, no need to import it.',    ]),    p(['the state of this module: ', JSON.stringify(state.module)]),  ])export const state = {  module: {    test: 'testing',  },}export const style = {  margin: '0 0 1em',  padding: '0.5em',  border: '1px solid',  borderColor: 'green',  h3: {    margin: 0,  },}export const global = {  state: {    module: true,  },}export const Component = props => {  props = typeof props === 'string' ? { header: props } : props  CHECK_PROPS(props, propTypes, 'ModComponent')  const header = props.header || props.title  return div({ class: 'ModComponent' }, [    header && h5(header),    p([      'Mod.Component, a second component in ',      Link({ to: 'https://github.com/magic/core/example/assets/module.mjs' }, '/assets/module.mjs'),    ]),  ])}Component.style = {  border: '1px solid orange',}export const propTypes = {  ModComponent: [{ key: 'header', type: ['string', 'array'], required: ['title'] }],}
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

@magic-modules/example

custom module

Mod.Mod

this is Mod. it gets loaded from /assets/modules/Mod.mjs

the state of this module:

Mod Component Title, passed via props

Component, a second component in /assets/modules/Component.mjs

Mod sourcecode:

export const View = state =>  div({ class: 'Mod' }, [    h3('Mod.Mod'),    p([      'this is Mod. it gets loaded from ',      Link({ to: 'https://github.com/magic/core/blob/master/example/assets/modules/Mod.mjs' }, '/assets/modules/Mod.mjs'),      ' automatically, no need to import it.',    ]),    p(['the state of this module: ', JSON.stringify(state.module)]),  ])export const state = {  module: {    test: 'testing',  },}export const style = {  margin: '0 0 1em',  padding: '0.5em',  border: '1px solid',  borderColor: 'green',  h3: {    margin: 0,  },}export const global = {  state: {    module: true,  },}

Component sourcecode:

export const View = props => {  props = typeof props === 'string' ? { header: props } : props  CHECK_PROPS(props, propTypes, 'Component')  const header = props.header || props.title  return div({ class: 'Component' }, \[    header && h5(header),    p([      'Component, a second component in ',      Link(        { to: 'https://github.com/magic/core/blob/master/example/assets/modules/Component.mjs' },        '/assets/modules/Component.mjs',      ),    ]),  ])}export const style = {  border: '1px solid orange',}export const propTypes = {  Component: [{ key: 'header', type: ['string', 'array'], required: ['title'] }],}
made with a few bits of magic
\ No newline at end of file diff --git a/docs/modules/html/index.html b/docs/modules/html/index.html index b655f3bf..f8b9c272 100644 --- a/docs/modules/html/index.html +++ b/docs/modules/html/index.html @@ -1,8 +1 @@ -html file example

html file example

this module gets imported from a html file.

see this html file for an example.

all html valid in <body> can be used, excluding the <script> and <style> tags.

the state looks like the following, and any valid json is allowed

---
-@state
-{
-  "title": "markdown file example",
-  "description": "markdown file description"
-}
----
-
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

html file example

this module gets imported from a html file.

see this html file for an example.

all html valid in <body> can be used, excluding the <script> and <style> tags.

the state looks like the following, and any valid json is allowed

---@state{  "title": "markdown file example",  "description": "markdown file description"}---
made with a few bits of magic
\ No newline at end of file diff --git a/docs/modules/index.html b/docs/modules/index.html index 887e4512..ad28299e 100644 --- a/docs/modules/index.html +++ b/docs/modules/index.html @@ -1 +1,6 @@ -@magic-modules

@magic-modules

magic modules are predefined modules for webapps.

module definition:

the minimal module is a function that returns some html.

// /assets/ModuleName.mjs// simplest moduleexport const View = () => div('hello, world')// complete signatureexport const View = (props = {}, children = []) => div('hello, world')

usage

if the npm package name starts with @magic-modules/ or magic-module-, it will get imported automagically. the name of the Module will be set to a PascalCased version of the remainder of the module name. @magic-modules/git-badges, for example, turns into GitBadges. the same is true for all uppercased files in your /assets/ directory and subdirectories. in the rare case where you want to install a npm module that can not be found, you can import it in /assets/index.mjs

// /assets/index.mjsexport default {  // ...otherModules  // load module from node_modules  NpmModule: require('not-standard-named-magic-module-from-npm'),}

after this, the module will be a global in your app and can be used like any other component.

// any page or moduleexport default state => div([  // module without props  Mod(),  'modules that need props: ',  Mod({ state, customProp: true }),

@magic-modules

modules are the grimoires of @magic. modules add both client and server functionality to your @magic app.
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

@magic-modules

magic modules are predefined modules for webapps.

module definition

the minimal module is a function that returns some html.

// /assets/ModuleName.mjs// simplest moduleexport const View = () => div('hello, world')// complete signatureexport const View = (props = {}, children = []) => div('hello, world')

usage

if the npm package name starts with @magic-modules/ or magic-module-, it will get imported automagically. +the name of the Module will be set to a PascalCased version of the remainder of the module name. +@magic-modules/git-badges, for example, turns into GitBadges. +the same is true for all uppercased files in your /assets/ directory and subdirectories. +in the rare case where you want to install a npm module that can not be found, +you can import it in /assets/index.mjs

// /assets/index.mjsimport NpmModule from 'non-standard-named-magic-module-from-npm'export default {  // ...otherModules  // load module from node_modules  NpmModule,}

after this, the module will be a global in your app and can be used like any other component.

// any page or moduleexport default state => div([  // module without props  Mod(),  'modules that need props: ',  Mod({ state, customProp: true }),

@magic-modules

modules are the building blocks of @magic. modules can be used to add both client and server functionality to your @magic app.
made with a few bits of magic
\ No newline at end of file diff --git a/docs/modules/markdown/index.html b/docs/modules/markdown/index.html index 894615d4..22212f9d 100644 --- a/docs/modules/markdown/index.html +++ b/docs/modules/markdown/index.html @@ -1,10 +1,4 @@ -markdown file example

markdown file example

markdown file description

this module gets imported from a markdown file.

see this file in the example dir for an example.

any kind of markdown can be used here, +@magic/core documentation

@magic

markdown file example

markdown file description

this module gets imported from a markdown file.

see this file in the example dir for an example.

any kind of markdown can be used here, but if you use html natively, -only tags valid in a html5 body, excluding <script> and <style> tags, are accepted.

this markdown file also starts with a magic @state declaration. -it is used internally to, for example, add the title and meta rel="description" tags to the head of this html file.

the state looks like the following, and any valid json is allowed

---
-@state
-{
-  "title": "markdown file example",
-  "description": "markdown file description"
-}
----
made with a few bits of magic
\ No newline at end of file +only tags valid in a html5 body, excluding <script> and <style> tags, are accepted.

a list:

state

this markdown file also starts with a magic @state declaration. +it is used internally to, for example, add the title and meta rel="description" tags to the head of this html file.

the state looks like the following, and any valid json is allowed

---@state{  "title": "markdown file example",  "description": "markdown file description"}---
made with a few bits of magic
\ No newline at end of file diff --git a/docs/modules/preinstalled/index.html b/docs/modules/preinstalled/index.html index b85be988..51aab2ae 100644 --- a/docs/modules/preinstalled/index.html +++ b/docs/modules/preinstalled/index.html @@ -1 +1,8 @@ -@magic-modules

@magic modules

preinstalled

magic has some preinstalled modules that will be used in most pages.

app

this is the main app module. it has magically inherited properties and all of it is customizable.

to add actions/state/style to the app you can just create an /assets/app.mjs file.the contents of this file get deep .merged into the app

// /assets/app.mjsexport const state = {  merge: 'gets merged into state',}export const actions = {  mergedActions: state => ({ ...state, merge: 'merged action executed' }),}

the Menu module provides... menus.

export const View = state => {  const items = [    { to: '/example-page', text: 'example page' },    { to: 'https://example.com', text: 'example.com' },    { to: 'https://example.com', nofollow: true, noreferrer: true, target: 'utopia', text: 'nofollow' and noref'' },  ]  return Menu({ items, collapse: false })}// output:<nav class='Menu'>  <ul>    <li>      <a onclick='actions.go' href='{{ WEB_ROOT }}example-page'>example page</a>    </li>    <li>      <a href="https://example.com" target='_blank' rel="noopener">example.com</a>    </li>    <li>      <a href="https://example.com" target='utopia' rel='noopener' nofollow noreferrer'>nofollow' and noref</a>    </li>  </ul></nav>}

the Menu module allows multiple props to be passed when instantiating the Menu

by default, the menu will only show submenu items if their parent link is active. to force submenu items to show at all times, just pass a collapse: false prop

Menu({  // if false, menu will always show all submenu items  collapse: false, // (default: true)})

every MenuItem accepts props the same props as a link does. additionally a MenuItem accepts a items prop with sub menu items.

const menuItem = ({  to: '/url',  text: 'link text',  items: [MenuItems],  noreferrer: true, // set rel='noreferrer'  nofollow: true, // set rel='nofollow'})

to define a submenu, simply define a .items array on the menu item

// assets/app.mjsexport default {  state: {    // ...state    menuName: [      {        to: '/example-page',        text: 'example page',        items: [          { to: '/example-page/#sub', text: 'example sub page' },      ] },    ],  },  // ... rest of app.mjs}

the link module allows you to link to things.

// in any page or module Viewexport default () => [  Link({ to: '/page', text: 'page' }),  // output: <a href='/page' onclick='actions.go'>page</a>  Link({ to: 'https://example.com', text: 'page' }),  // output: <a href="https://example.com" target='_blank' rel='noopener'>page</a>  Link({ to: '/page', text: 'page', nofollow: true, noreferrer: true }),  // output: <a href="https://example.com" target='_blank' rel='nofollow' noreferrer noopener'>page</a>'  // you can also use children syntax instead of the text prop:  Link({ to: '/' }, 'home'),  // Link also supports # hash links  Link({ to: '/#hash' }, 'home with hash'),]

img

the img module adds some sane default values to your images.

// in any page or module Viewexport default () => [  Img('/image.png'),  // output: <img src='/image.png' alt='' role='presentation'/>  Img({ src: '/image.png }),'  // output: <img src='/image.png' alt='' role='presentation'/>  Img({ src: '/image.png', alt: 'image description' }),  // output: <img src='/image.png alt='image description' />'  Img({ src: '/image.png', title: 'image title', }),  // output: <img src='/image.png' title='image title' alt='image title'/>  Img({ src: '/image.png', title: 'image title', alt: 'image alt' }),  // output: <img src='/image.png' title='image title' alt='image alt'/>]

the footer module contains a small info text and a link to the magic github repository.

to overwrite this behaviour, just place a Footer.mjs file in your assets and require it in /assets/index.mjs.

// /assets/Footer.mjs:const Footer = () =>footer({ class: 'main' }, [  div({ class: 'wrapper' }, [    'made with a few bits of ',    Link({ href: 'https://github.com/magic/core', target: '_blank', rel: 'noopener' }, 'magic'),  ]),])Footer.style: {  'footer.main': {    position: 'relative',    textAlign: 'center',    padding: '5em 0 .5em',  },}export default Footer
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

@magic-modules

preinstalled

magic has some preinstalled modules that will be used in most pages.

app

this is the main app module. +it has magically inherited properties and all of it is customizable.

to add actions/state/style to the app you can just create an /assets/app.mjs file. +the contents of this file get +deep .merged +into the app

// /src/app.mjsexport const state = {  merge: 'gets merged into state',}export const actions = {  mergedActions: state => ({ ...state, merge: 'merged action executed' }),}

the Menu module provides... menus.

export const View = state => {  const items = [    { to: '/', text: 'example page' },    { to: 'https://example.com', text: 'example.com' },    { to: 'https://example.com', nofollow: true, noreferrer: true, target: 'utopia', text: 'nofollow and noref" },  ]  return Menu({ items, collapse: false })}// output:<nav class="Menu">  <ul>    <li>      <a onclick="actions.go" href="/">example page    </li>    <li>      <a href="https://example.com" target="_blank" rel="noopener">example.com    </li>    <li>      <a href="https://example.com" target="utopia" rel="noopener nofollow noreferrer">nofollow and noref    </li>  </ul></nav>

the Menu module allows multiple props to be passed when instantiating the Menu

by default, the menu will only show submenu items if their parent link is active. +to force submenu items to show at all times, just pass a collapse: false prop

Menu({  // if false, menu will always show all submenu items  collapse: false, // (default: true)})

every MenuItem accepts the same props as a link does. +additionally a MenuItem accepts an items prop with sub menu items.

const menuItem = ({  to: '/url',  text: 'link text',  items: [MenuItems],  noreferrer: true, // set rel='noreferrer'  nofollow: true, // set rel='nofollow'})

to define a submenu, simply define a .items array on the menu item

// assets/app.mjsexport default {  state: {    // ...state    menuName: [      {        to: '/example-page',        text: 'example page',        items: [          { to: '/example-page/#sub', text: 'example sub page' },      ] },    ],  },  // ... rest of app.mjs}

the link module allows you to link to things.

// in any page or module Viewexport default () => [  Link({ to: '/', text: 'page' }),  // output: <a href="/" onclick="actions.go">page  Link({ to: 'https://example.com', text: 'page' }),  // output: <a href="https://example.com" target="_blank" rel="noopener">page  Link({ to: '/', text: 'page', nofollow: true, noreferrer: true }),  // output: <a href="https://example.com" target="_blank" rel="nofollow noreferrer noopener">page  // you can also use children syntax instead of the text prop:  Link({ to: '/' }, 'home'),  // Link also supports # hash links  Link({ to: '/#hash' }, 'home with hash'),]

img

the img module adds some sane default values to your images.

// in any page or module Viewexport default () => [  Img('/image.png'),  // output: <img src="/image.png" alt="" role="presentation"/>  Img({ src: '/image.png' }),  // output: <img src="/image.png" alt="" role="presentation"/>  Img({ src: '/image.png', alt: 'image description' }),  // output: <img src="/image.png" alt="image description" />  Img({ src: '/image.png', title: 'image title', }),  // output: <img src="/image.png" title="image title" alt="image title"/>  Img({ src: '/image.png', title: 'image title', alt: 'image alt' }),  // output: <img src="/image.png" title="image title" alt="image alt"/>]

the footer module contains a small info text and a link to the magic github repository.

to overwrite this behaviour, just place a Footer.mjs file in your assets +and require it in /assets/index.mjs.

// /assets/Footer.mjs:const Footer = () =>footer({ class: 'main' }, [  div({ class: 'wrapper' }, [    'made with a few bits of ',    Link({ href: 'https://github.com/magic/core', target: '_blank', rel: 'noopener' }, 'magic'),  ]),])Footer.style: {  'footer.main': {    position: 'relative',    textAlign: 'center',    padding: '5em 0 .5em',  },}export default Footer
made with a few bits of magic
\ No newline at end of file diff --git a/docs/modules/propTypes/index.html b/docs/modules/propTypes/index.html index f2a33dc2..46ba4171 100644 --- a/docs/modules/propTypes/index.html +++ b/docs/modules/propTypes/index.html @@ -1 +1 @@ -@magic-modules/propTypes

@magic-modules/propTypes

CHECK_PROPS

@magic-modules can export a .propTypes object with an array of prop types.

example

export const View = (prop1, prop2, prop3) => [  p(prop1),  p(prop2),  p(prop3),]export const propTypes = [  { name: 'prop1', type: 'string' },  { name: 'prop2', type: 'number' },  { name: 'prop3', type: 'array', items: 'string' },  {    name: 'prop4',    type: 'object',    items: [      { name: 'prop4prop1', type: 'string' },      { name: 'prop4prop2', type: 'number' },  ] },]
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

@magic-modules/prop-types

CHECK_PROPS

@magic-modules can export a .propTypes object with an array of prop types.

example

export const View = (prop1, prop2, prop3) => [  p(prop1),  p(prop2),  p(prop3),]export const propTypes = [  { name: 'prop1', type: 'string' },  { name: 'prop2', type: 'number' },  { name: 'prop3', type: 'array', items: 'string' },  {    name: 'prop4',    type: 'object',    items: [      { name: 'prop4prop1', type: 'string' },      { name: 'prop4prop2', type: 'number' },  ] },]
made with a few bits of magic
\ No newline at end of file diff --git a/docs/news/2019/12/22/blogging/index.html b/docs/news/2019/12/22/blogging/index.html index a75be0b1..ebeeb832 100644 --- a/docs/news/2019/12/22/blogging/index.html +++ b/docs/news/2019/12/22/blogging/index.html @@ -1,5 +1,5 @@ -blogging...

blogging...

@magic has a blog now.

so i guess i should start using it...

it's pretty rough, +@magic/core documentation

@magic

blogging...

@magic has a blog now.

so i guess i should start using it...

it's pretty rough, the index pages for yearly and monthly archives are not polished, but can be overwritten by adding them to the config.BLOG_DIR dir of your @magic app.

to use the blog, -create an archive dir, for example

src/blog/2019/12/22/

then just add the blogposts in that directory structure.

@magic will automagically build a blog directory for you, -including the archives for yearly, monthly and overall blog posts.

more information following soon.

Blog Archives:

year: 2019

month: 12 2019

made with a few bits of magic
\ No newline at end of file +create an archive dir, for example

src/blog/2019/12/22/

then just add the blogposts in that directory structure.

@magic will automagically build a blog directory for you, +including the archives for yearly, monthly and overall blog posts.

more information following soon.

Blog Archives:

year: 2019

month: 12 2019

made with a few bits of magic
\ No newline at end of file diff --git a/docs/news/2019/12/index.html b/docs/news/2019/12/index.html index 6845d43b..8168ee7e 100644 --- a/docs/news/2019/12/index.html +++ b/docs/news/2019/12/index.html @@ -1 +1 @@ -@magic/core documentation

12 - 2019

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

12 - 2019

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file diff --git a/docs/news/2019/index.html b/docs/news/2019/index.html index be9b45c7..17184219 100644 --- a/docs/news/2019/index.html +++ b/docs/news/2019/index.html @@ -1 +1 @@ -@magic/core documentation

2019

12

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

2019

12

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file diff --git a/docs/news/index.html b/docs/news/index.html index 167cd0fc..384b913e 100644 --- a/docs/news/index.html +++ b/docs/news/index.html @@ -1 +1 @@ -@magic/core documentation

2019

12

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

2019

12

22-12-2019 - blogging...

@magic has a blog now.

made with a few bits of magic
\ No newline at end of file diff --git a/docs/prependCss.css b/docs/prependCss.css new file mode 100644 index 00000000..4c1fd759 --- /dev/null +++ b/docs/prependCss.css @@ -0,0 +1,3 @@ +#PrependCss { + color: green; +} diff --git a/docs/prependJs.js b/docs/prependJs.js new file mode 100644 index 00000000..8202b133 --- /dev/null +++ b/docs/prependJs.js @@ -0,0 +1,3 @@ +console.log( + 'config.PREPEND_JS works and appends before the magic.js. CONSOLE.LOG GETS REMOVED IN PROD, making this script not do anything.', +) diff --git a/docs/prependScript.js b/docs/prependScript.js new file mode 100644 index 00000000..33fa255f --- /dev/null +++ b/docs/prependScript.js @@ -0,0 +1,4 @@ +console.log('config PREPEND_SCRIPTS works.') + +const app = 'Overwriting app in PREPEND_SCRIPTS does not break the page...' +console.log(app) diff --git a/docs/robots.txt b/docs/robots.txt index fbc78604..1059f38d 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -1,3 +1,3 @@ user-agent: * allow: / -sitemap: https://magic.github.io/core/sitemap.xml +sitemap: https:/magic.github.io/core/sitemap.xml diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 843faa4a..07d5c28e 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -7,112 +7,126 @@ https://magic.github.io/core/ - 2020-02-23 + 2026-06-26 weekly 0.5 https://magic.github.io/core/404/ - 2020-02-23 + 2026-06-26 + weekly + 0.5 + + + + https://magic.github.io/core/build/ + 2026-06-26 weekly 0.5 https://magic.github.io/core/concepts/ - 2020-02-23 + 2026-06-26 weekly 0.5 https://magic.github.io/core/files/ - 2020-02-23 + 2026-06-26 weekly 0.5 https://magic.github.io/core/libraries/ - 2020-02-23 + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/ - 2020-02-23 + https://magic.github.io/core/news/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/example/ - 2020-02-23 + https://magic.github.io/core/test/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/html/ - 2020-02-23 + https://magic.github.io/core/themes/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/markdown/ - 2020-02-23 + https://magic.github.io/core/modules/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/preinstalled/ - 2020-02-23 + https://magic.github.io/core/modules/example/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/modules/propTypes/ - 2020-02-23 + https://magic.github.io/core/modules/html/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/news/ - 2020-02-23 + https://magic.github.io/core/modules/markdown/ + 2026-06-26 weekly 0.5 - https://magic.github.io/core/themes/ - 2020-02-23 + https://magic.github.io/core/modules/preinstalled/ + 2026-06-26 + weekly + 0.5 + + + + https://magic.github.io/core/modules/propTypes/ + 2026-06-26 weekly 0.5 https://magic.github.io/core/news/2019/ - 2020-02-23 + 2026-06-26 weekly 0.5 https://magic.github.io/core/news/2019/12/ - 2020-02-23 + 2026-06-26 weekly 0.5 https://magic.github.io/core/news/2019/12/22/blogging/ - 2020-02-23 + 2026-06-26 weekly 0.5 diff --git a/docs/sri-hashes.json b/docs/sri-hashes.json index a27b41d8..542c17d3 100644 --- a/docs/sri-hashes.json +++ b/docs/sri-hashes.json @@ -1,28 +1,24 @@ { - "css": "sha384-1DvvqyXE3UZTQ1Ap9jh4I4lALD8nmISVax+z6v/jYQ/mCyk3oo+7+42VNzYcLvok", - "js": "sha384-jXPPv6MdDo16l1H4fjAtqOfsehfbXuumaHE1EebLoC9SqBWq5DlrXajP8rWkSNvU", - "pages": { - "/core/": "sha384-Edeg1l+p3WEB+B9YE4TONnoTys0XAQjyqekwIZXpVf+DIDX3GeAzdfGnTtdcm++u", - "/core/404/": "sha384-73TSbbbTQTyvbM3R+FEcpC2WK/L2DfajDAkDkTo3sErMOa7e5MBu39r+EASEHZeo", - "/core/concepts/": "sha384-fiFftId0BLx2j04G8bOUIWRzlnheyu8LLdhrklntjpSPHrOyqvH7UDChAvbfykU4", - "/core/files/": "sha384-wYbOUr3DgaQctSVSuuJTKj2K0GLm70lQ2fCVsSX/FZejvnsuw8PMp63SixEl5wa/", - "/core/libraries/": "sha384-PovD7zs+Bpi0by6b4xCSYpR/Pxn/8SmBz/so03pfTNVMCO9EwCeP+H7sedi/vbBV", - "/core/modules/": "sha384-YPARA/6vOIP8H5upLJlq91EDNL2FOdQt5gEojo9v+dmVEmnQ7cai7/gz9b4293V6", - "/core/modules/example/": "sha384-Hy6hY7VmJXNLT7Y5+YHUkMGN47TjoMrFSsc8gq3C3AlIIQ10puOtGAkVE/nZHsFF", - "/core/modules/html/": "sha384-YVbgBT57a+qwQXCp0PtKgRhZyXqHXwZ+g7i/62VAkiHiAhrlleVvbAF4b5sdWILi", - "/core/modules/markdown/": "sha384-GAAFrRN5C2MtJKjgn47/z45TL55rYZrCSHvJPPA+OtHY3JssWSvo3pd74zj/D3/a", - "/core/modules/preinstalled/": "sha384-J8TPuFD6BA6EZl56h73IQqecIeu7Ma0AtUQcjfAvjZK+6MUkJtbigYHpWHZ1j5xB", - "/core/modules/propTypes/": "sha384-aa5HgDACDidLYmHXjHyAD84PsjZXVGy16Jz26ME2AaXAtX+Zj9Sbp/AVG8oJ5UIw", - "/core/news/": "sha384-bAvTfOibylVfX/HFDPudx4/f0bja5trEc/cM7nI3iTL5hr8p4gFOHQJxnhW6mjp+", - "/core/news/2019/": "sha384-1g49S7ywDStTgDY8+ntR/Flu9JpTtNFu8tuv9J9ahQiG+8D75oMiSYd/kpNjwGmQ", - "/core/news/2019/12/": "sha384-nA0EGJyZf/2eGldP7TEQXBhrpEp5bbMakI1il05Ee6s+QBJJL2czFP59wwrhYrBc", - "/core/news/2019/12/22/blogging/": "sha384-F+NLSj2rjCWRdnp1ro73HrajMy3l4I5DRsWFgLgkSFqQoZDcH3qoE1qqOr6CTFQh", - "/core/themes/": "sha384-V7MThWlmx4cKUji+Zcgl5ITpA5IwF3nsKbNrjzpbPhyYuEsOyDPCcYIc0TMM3+Bt" - }, - "static": { - "/favicon.ico": "sha384-pf51ay6pZCj3+XlVCPmCKLDFpLj7QP+2heR+/n08WgS7Bve7JMkgubHOkwLzaTr+", - "/logo.png": "sha384-yRxX8nlWNCvk7+PidWDtP1jLxTmtbHm7tCW6TszNfZ7R5DTDMmXyxhKC/JmX3r1u", - "/robots.txt": "sha384-UJ99rKaWQNOb1b2yL4/2XX6B6Gjr6ZeSk6CJfZ5lw/AUGmwgmUfFx1v0bnK+q/0O", - "/sitemap.xml": "sha384-zpPNyCpQHq20FCyGJLAsW2ByGAgM6LoQoNDbPh+xYcnZoYzKzvUxEF07B/ukkeVT" - } + "/magic.css": "sha384-KPBFf3rjGtMy5jaZPwO++3DgeJfxAFpGYFXKceUFjsgAbulT1uC6dQEjWFrQxzd0", + "/magic.js": "sha384-kSOB/AQawkz4NrMgQkuqwTKkhkHSa3k4Xub3rH4Oxm4XIds48+F+ViGt8na90zS3", + "/core/": "sha384-T5cohV7mUPb33rTxwSowTydVyYlA7pr80nRWZ9eH2R3dgJFMRc/L9dl7PN8XNlBt", + "/core/404/": "sha384-55yyesnXZEtgceWZIminoW2xCQgVSgU6YXM4mBLEjofau3LMoZbEd0Saj+zEv9a+", + "/core/build/": "sha384-8Lu8LdTSZ1gjZG6uf/bHwBbEJDOQugzTBQnSzRZ44io8Y5sbxN7XQkXJM1itwYD6", + "/core/concepts/": "sha384-W4QgbMk+dbJNojG4GqIWAbxfhLGoV+2qWNjE9AuSW6xD03mMBiKdFWpF3pOmetqD", + "/core/files/": "sha384-0wHXE5iIyan7GLX/c+xG9N6OwhiHumM3dCrJuW5Hw7hlt3vNqu321DYZ1itr8Ewq", + "/core/libraries/": "sha384-9YWBDQ37IHYb9PjXKczGr6nxomxJpyGf7JCo8x0lFjaIRyLaZO3kX/K5c6/BNBzu", + "/core/modules/": "sha384-s2y786F25yeBfdoSmfnnAfVdmbwQI/ZdGmg3OPTWkWJduRPklIzGpMh2las+mQbV", + "/core/modules/example/": "sha384-/qD3BerIbN9TAtTBUCimj6wKbp9zZe4fYR2KxrvbUJNB1fbyTA/CgcQ1+JBBngP3", + "/core/modules/html/": "sha384-rwtGOg5Ge2aIjK/Akbc6x5pfWu8Spbu1paNpIeFvmCt5h9YStmj3ByQ1a8Gq3tyF", + "/core/modules/markdown/": "sha384-j9c3s1i6Yk+dIrb7KlxVcum1wWDdR8uVav7Eq2+SSx/BX/inRm0u92bLtGKYbV9c", + "/core/modules/preinstalled/": "sha384-myyC9LoY+9fdqgq9wBPG0Xi5FZhJdAp0fzH/qywJQLfWmhRdNONjYi/tPl5npYJ4", + "/core/modules/propTypes/": "sha384-N915U4JWTOdC/7W0IMzdflqexuIF9N5g8R60z2l74d3yHG6RgIw74kyJLU9tPvQy", + "/core/news/": "sha384-xk2HfkVE0aODjDSThnbKdNfUIowxsqqTaNYYAJj6MoUHuIkd/+rq2f/ayG2MR82B", + "/core/news/2019/": "sha384-+sUApYgfentAwaKW6AX8u8b9gKGboBRcHWcfNxolDn8JWt7So851iGoUhn7C5Dn5", + "/core/news/2019/12/": "sha384-wvdP9/EOr1vrdfmQTVUXd9VBjmlFq7ZkGZ33NF84SVX0lqxQSVho6EbUy6dbwaa/", + "/core/news/2019/12/22/blogging/": "sha384-uPraOSl0UpXkqi8/gBEnNLexslYuIztgvSxoOvx+yV2eKftcsCF/zgBZm6RWGp7Y", + "/core/test/": "sha384-JMy7cpdIZ6VWfIILQSXu9nlxU3c/U0oXowIzELm53fy8roFe1IybktQFZVCD30kf", + "/core/themes/": "sha384-mnKMJyZmsZPecaKVR+KhZtYENCIbl5r7Sxh0ZshAZzvAC5vUvNjnmlTkJIHxyfba", + "/robots.txt": "sha384-tWbTh3/OLqe9aI96yHe23bao25MnJSVaojfRszIkfoPeBL4a4ubKU4fPTW6fyWmY", + "/sitemap.xml": "sha384-zhr3U+1zpF3sU4VblvEvymo07aHGD8/moHVAZni0bhuHhj9hF1fKvvIuIYjUhMO0" } \ No newline at end of file diff --git a/docs/test/index.html b/docs/test/index.html new file mode 100644 index 00000000..756d36c9 --- /dev/null +++ b/docs/test/index.html @@ -0,0 +1 @@ +@magic/core documentation

@magic

Test page

This page shows various features and tests of magic functionality

Link Tests:

Broken link tests:

the following links are broken, and it's intentional for magic to warn us on every rebuild.

Image test

while at it, let's test an image, 2 times with a working src:

  • Magic Logo
  • Magic Logo

and once with a broken src:

Broken Magic Logo

Links in modules

Link in a paragraph

made with a few bits of magic
\ No newline at end of file diff --git a/docs/themes/index.html b/docs/themes/index.html index 03e2b8eb..04b3adec 100644 --- a/docs/themes/index.html +++ b/docs/themes/index.html @@ -1 +1,2 @@ -@magic-themes

@magic-themes

magic themes are themes for magic apps. you decide which theme to load by specifying the theme name in config.THEME

// /config.mjsexport default {  // ...rest of config,  THEME: 'blue',}

theme load order

themes get loaded from multiple places. last in the list overwrites earlier entries.

// ...default module styles get inserted here/node_modules/@magic/core/src/themes/THEME/index.mjs/node_modules/@magic-themes/THEME/assets/themes/THEME/index.mjs

@magic-themes

below is a collection of the available @magic app themes.
made with a few bits of magic
\ No newline at end of file +@magic/core documentation

@magic

@magic-themes

magic themes are themes for magic apps. +you decide which theme to load by specifying the theme name in config.THEME

// /config.mjsexport default {  // ...rest of config,  THEME: 'blue',}

theme load order

themes get loaded from multiple places. last in the list overwrites earlier entries.

// ...default module styles get inserted here/node_modules/@magic/core/src/themes/THEME/index.mjs/node_modules/@magic-themes/THEME/assets/themes/THEME/index.mjs

@magic-themes

below is a collection of the available @magic app themes. all of the themes below are designed to be usable on their own, but they can also be combined if needed.
made with a few bits of magic
\ No newline at end of file diff --git a/example/app.mjs b/example/app.mjs index 95e29238..e0277ab9 100644 --- a/example/app.mjs +++ b/example/app.mjs @@ -1,9 +1,14 @@ +import fs from '@magic/fs' +import path from 'path' + export const state = config => ({ seo: { name: '@magic/core documentation', url: `https://${config.URL}`, about: '@magic framework core.', - image: `https://${config.URL}${config.WEB_ROOT}logo.png`, + // this will lead to a double slash before logo.png. + // should be removed automagically. + image: `https://${config.URL}/logo.png`, author: { '@type': 'person', name: 'Jascha Ehrenreich', @@ -13,6 +18,17 @@ export const state = config => ({ }, }, + title: '@magic/core documentation', + + description: [ + '@magic/core documentation.', + 'tells you why, how and when to use @magic.', + 'also provides an overview of all @magic functionality this ecosystem provides.', + ], + + logo: '/logo.png', + logotext: '@magic', + menu: [ { to: '/concepts/', @@ -21,6 +37,8 @@ export const state = config => ({ { to: '#modules', text: 'modules' }, { to: '#state', text: 'state' }, { to: '#actions', text: 'actions' }, + { to: '#effects', text: 'effects' }, + { to: '#subscriptions', text: 'subscriptions' }, { to: '#views', text: 'views' }, { to: '#styles', text: 'styles' }, { to: '#globals', text: 'global' }, @@ -52,7 +70,7 @@ export const state = config => ({ }, { to: '#app', - text: '/assets/app.mjs', + text: 'app.mjs', items: [{ to: '-example', text: 'example' }], }, { @@ -114,7 +132,46 @@ export const state = config => ({ ], }, { to: '/news/', text: 'news' }, + { + to: '/test/', + text: 'test', + }, ], - logo: '/logo.png', - logotext: '@magic', }) + +// export const server = { +// lambda1: () => ({ +// code: 200, +// body: 'Ok, lambda1', +// }), +// lambda2: () => ({ +// code: 200, +// body: 'Ok, lambda2', +// }), +// } + +export const build = async ({ app, config }) => { + const dataPath = path.join(config.DIR.ASSETS, 'data', 'index.json') + const buildData = await fs.readFile(dataPath) + const json = JSON.parse(buildData) + + // add the json data to the state. + app.state.data = json.data + + app.pages = app.pages || [] + + // this adds the /build/ page to this app + app.pages.push({ + View: state => + div([ + h3(state.title), + p(state.description), + p([h4('state.data'), Pre(JSON.stringify({ data: state.data }, null, 2))]), + ]), + name: `${config.WEB_ROOT}build/`, + path: `${config.WEB_ROOT}build/index.html`, + state: json.data, + }) + + return app +} diff --git a/example/assets/data/index.json b/example/assets/data/index.json new file mode 100644 index 00000000..3b80433c --- /dev/null +++ b/example/assets/data/index.json @@ -0,0 +1,6 @@ +{ + "data": { + "title": "external data", + "description": "This is some static data, loaded via example/app.mjs#build and from example/assets/data/index.json." + } +} diff --git a/example/assets/lib/export-default.mjs b/example/assets/lib/export-default.mjs index 31e47663..d61c2bd4 100644 --- a/example/assets/lib/export-default.mjs +++ b/example/assets/lib/export-default.mjs @@ -1,2 +1,2 @@ const string = 'this string gets exported using export default,' -export default a => b(`${string} the argument it received was ${a}`) +export default a => `${string} the argument it received was ${a}` diff --git a/example/assets/modules/Component.mjs b/example/assets/modules/Component.mjs new file mode 100644 index 00000000..79d84e2e --- /dev/null +++ b/example/assets/modules/Component.mjs @@ -0,0 +1,24 @@ +export const View = props => { + props = typeof props === 'string' ? { header: props } : props + CHECK_PROPS(props, propTypes, 'Component') + const header = props.header || props.title + + return div({ class: 'Component' }, [ + header && h5(header), + p([ + 'Component, a second component in ', + Link( + { to: 'https://github.com/magic/core/blob/master/example/assets/modules/Component.mjs' }, + '/assets/modules/Component.mjs', + ), + ]), + ]) +} + +export const style = { + border: '1px solid orange', +} + +export const propTypes = { + Component: [{ key: 'header', type: ['string', 'array'], required: ['title'] }], +} diff --git a/example/assets/modules/Mod.mjs b/example/assets/modules/Mod.mjs index ffe0d3a9..b6fafceb 100644 --- a/example/assets/modules/Mod.mjs +++ b/example/assets/modules/Mod.mjs @@ -25,7 +25,7 @@ export const style = vars => ({ h3: { margin: 0, - color: vars.textColor, + color: vars.text.dark, }, }) @@ -35,27 +35,12 @@ export const global = { }, } -export const Component = props => { - props = typeof props === 'string' ? { header: props } : props - CHECK_PROPS(props, propTypes, 'ModComponent') - const header = props.header || props.title +// export const server = (req, res) => { +// const random = Math.floor(Math.random() * 1000) +// const body = `hello, world. here is some pseudorandom: ${random}` - return div({ class: 'ModComponent' }, [ - header && h5(header), - p([ - 'Mod.Component, a second component in ', - Link( - { to: 'https://github.com/magic/core/blob/master/example/assets/modules/Mod.mjs' }, - '/assets/modules/Mod.mjs', - ), - ]), - ]) -} - -Component.style = { - border: '1px solid orange', -} - -export const propTypes = { - ModComponent: [{ key: 'header', type: ['string', 'array'], required: ['title'] }], -} +// return { +// code: 200, +// body, +// } +// } diff --git a/example/assets/static/appendCss.css b/example/assets/static/appendCss.css new file mode 100644 index 00000000..382da661 --- /dev/null +++ b/example/assets/static/appendCss.css @@ -0,0 +1,3 @@ +#AppendCss { + color: green; +} diff --git a/example/assets/static/appendJs.js b/example/assets/static/appendJs.js new file mode 100644 index 00000000..7736c0a4 --- /dev/null +++ b/example/assets/static/appendJs.js @@ -0,0 +1,3 @@ +console.log( + 'config.APPEND_JS works and appends after the magic.js. CONSOLE.LOG GETS REMOVED IN PROD, making this script not do anything.', +) diff --git a/example/assets/static/appendScript.js b/example/assets/static/appendScript.js new file mode 100644 index 00000000..06551fd4 --- /dev/null +++ b/example/assets/static/appendScript.js @@ -0,0 +1,4 @@ +console.log('config APPEND_SCRIPTS works.') + +const h = 'Overwriting h in APPEND_SCRIPTS does not break the page...' +console.log(h) diff --git a/example/assets/static/logo.png b/example/assets/static/logo.png index 01dc77a7..a0fa6ed6 100644 Binary files a/example/assets/static/logo.png and b/example/assets/static/logo.png differ diff --git a/example/assets/static/prependCss.css b/example/assets/static/prependCss.css new file mode 100644 index 00000000..4c1fd759 --- /dev/null +++ b/example/assets/static/prependCss.css @@ -0,0 +1,3 @@ +#PrependCss { + color: green; +} diff --git a/example/assets/static/prependJs.js b/example/assets/static/prependJs.js new file mode 100644 index 00000000..8202b133 --- /dev/null +++ b/example/assets/static/prependJs.js @@ -0,0 +1,3 @@ +console.log( + 'config.PREPEND_JS works and appends before the magic.js. CONSOLE.LOG GETS REMOVED IN PROD, making this script not do anything.', +) diff --git a/example/assets/static/prependScript.js b/example/assets/static/prependScript.js new file mode 100644 index 00000000..33fa255f --- /dev/null +++ b/example/assets/static/prependScript.js @@ -0,0 +1,4 @@ +console.log('config PREPEND_SCRIPTS works.') + +const app = 'Overwriting app in PREPEND_SCRIPTS does not break the page...' +console.log(app) diff --git a/example/news/2019/12/22/blogging.md b/example/news/2019/12/22/blogging.md index c5461287..8757c264 100644 --- a/example/news/2019/12/22/blogging.md +++ b/example/news/2019/12/22/blogging.md @@ -13,9 +13,10 @@ but can be overwritten by adding them to the config.BLOG_DIR dir of your @magic to use the blog, create an archive dir, for example -``` + +
 src/blog/2019/12/22/
-```
+
then just add the blogposts in that directory structure. diff --git a/example/pages/concepts.md b/example/pages/concepts.md new file mode 100644 index 00000000..cc301d2b --- /dev/null +++ b/example/pages/concepts.md @@ -0,0 +1,193 @@ +--- +@state { + "title": "@magic/core concepts", + "description": [ + "@magic/core conceptual information.", + "explains the main concepts that make the @magic work." + ] +} +--- + +# @magic/core concepts + +magic concepts. these are the building blocks of every module in a magic app + +

@magic-modules

+ +modules are the main building block of magic. + +a page is a module, a button is a module, a link is a module, an image is a module. +even a theme is a module. + +a @magic app contains modules containing modules that contain modules. +this can lead to inception. + +## module building blocks + +### state + +state is a javascript object. +state can be mutated by actions, +effects and subscriptions can call actions to change the state. +every rendering frame, the state determines the output of the html views. + +``` +const state = { + shown: false, + count: 0, +} + +``` + +## actions + +actions are an object containing functions + +those functions get the state and their props and may return a new full, but changed, state. + +``` +const actions = { + toggleModal: state => { + return { + ...state, + shown: !state.shown, + } + }, + + calculator: { + plus: state => ({ ...state, count: state.count + 1 }), + minus: state => ({ ...state, count: state.count - 1 }), + }, +} +``` + +## effects + +effects are an object containing functions, just like actions. + +effects can only change the state by calling actions. + +effects may also be impure and trigger side-effects outside of hyperapp. + +``` +const effects = { + waitASec: [state, async state => { + await new Promise((r) => setTimeout(r, 1000)) + + return actions.plus(state) + }], +} +``` + +## subscriptions + +at the moment, subscriptions can be defined using strings as function names. + +also, if a module exports a subscriptions array, those subscriptions will be added to the app. + +``` +// in a module: + +export const lib = { + subscribe: (dispatch, action) => { + setInterval(() => { + dispatch(action, { arg: Math.random() * 100 }) + }) + }, +} + +export const actions = { + onSubscription: (state, e) => ({ + ...state, + arg: e.arg, + }) +} + +export const subscriptions = ['lib.subscribe', 'actions.onSubscription'] +``` + +## views + +views render the state to html +whenever an action triggers a change in the state, this then triggers a view change. + +``` +export const View = state => div(JSON.stringify(state)) +``` + +## styles + +every module can export a style object. +magic will automagically merge all styles into one global css file. +in the future, it will also remove unused styles for you. + +styles merge order, last overwrites first: +module.style < page.style < app.style < theme.style + +this allows css to be inherited in both directions. + +a module automatically creates it's own css namespace, +that same namespace can be used in page, app and theme css styles +to overwrite module specific styles. + +### #styles- @magic/css + +internally, magic uses it's own css-in-js library. + +to find out more, click the following link: +[@magic/css](https://magic.github.io/css/) + +## globals + +every module can set a global object, containing state and action properties. + +every state and/or action name in the global object with a value that equals true gets merged into the main app state/actions, instead of into the module namespace. + +``` +// in module.mjs + +export const state = { + internal: 0, + external: 0, +} + +export const actions = { + internal: state => ({ + ...state, + module: { + internal: state.module.internal + 1, + }, + }), + external: state => ({ + ...state, + external: state.external + 1, + }), +} + +export const global = { + state: { + external: true + }, + actions: { + external: true, + }, +} + +// in the view, we can use both the global and local actions and state variables + +export const View = state => div([ + 'view', + p({ onclick: actions.module.internal }, ['internal:', state.module.internal]), + p({ onclick: actions.external }, ['external:', state.external]), +]) +``` + +## #lambdas server lambdas + +this is the serverside magic. +you can define functions that get transpiled into serverside lambdas. +server side lambdas will be available for GET and/or POST requests. + +the server side function signature is (req, res) => {}, +as it is in most nodejs http servers, +with the addition of req.body being async => awaited before execution of the lambda. diff --git a/example/pages/concepts.mjs b/example/pages/concepts.mjs deleted file mode 100644 index 2e833625..00000000 --- a/example/pages/concepts.mjs +++ /dev/null @@ -1,95 +0,0 @@ -export const state = { - title: '@magic/core concepts', - description: [ - '@magic/core conceptual information.', - 'explains the main concepts that make the @magic work.', - ], -} - -export const View = state => [ - h1(state.title), - p('magic concepts. These are the building blocks of every module in a magic app'), - - div([ - h2({ id: 'modules' }, '@magic-modules'), - - p('modules are the main building block of magic.'), - - p([ - 'a page is a module, a button is a module, a link is a module, an image is a module.', - ' a @magic app contains modules containing modules that contain modules.', - ' this can lead to inception.', - ]), - ]), - - h2('module building blocks'), - - div([ - h2({ id: 'state' }, 'state'), - div([ - p('state is a javascript object.'), - p('state can be mutated by actions or effects.'), - p('every rendering step, the state determines the output of the views'), - ]), - ]), - - div([ - h2({ id: 'actions' }, 'actions'), - p('actions are an object containing functions'), - p( - 'those functions get the state and their props and may return a new full, but changed, state.', - ), - ]), - - div([ - h2({ id: 'effects' }, 'effects'), - p('effects are an object containing functions, just like actions.'), - p( - 'they behave like actions, get a state and props and may return a new full, but changed, state.', - ), - p([ - 'the big difference? ', - 'effects may be impure and trigger sideeffects outside of hyperapp.', - ]), - ]), - - div([ - h2({ id: 'views' }, 'views'), - p('views render the state to html'), - p('whenever an action triggers a change in the state, this then triggers a view change.'), - ]), - - div([ - h2({ id: 'styles' }, 'styles'), - p('every module can have a style object attached to it.'), - p('magic will automagically merge all styles into one global css file.'), - p('in the future, it will also remove unused styles for you.'), - - p('style merge order from lowest to highest, last overwrites first:'), - p('module.style < page.style < app.style < theme.style'), - - h3({ id: 'styles-magic-css' }, '@magic/css'), - p("internally, magic uses it's own css-in-js library."), - p('to find out more, click the following link:'), - Link({ to: 'https://magic.github.io/css/' }, '@magic/css'), - ]), - - div([ - h2({ id: 'globals' }, 'global'), - p('every module can set a global object, containing state and action properties.'), - p( - 'every state and/or action name in the global object with a value that equals true gets merged into the main app state/actions.', - ), - ]), - - div([ - h2({ id: 'lambdas' }, 'server lambdas'), - p('this is the serverside magic.'), - p('you can define functions that get transpiled into serverside lambdas.'), - p('server side lambdas will be available for GET and/or POST requests.'), - p([ - 'the server side function signature is (req, res) => {},', - ' as in any nodejs http server, with the addition of req.body being async => awaited before execution of the lambda.', - ]), - ]), -] diff --git a/example/pages/files.mjs b/example/pages/files.mjs index 8ec62ae0..50f05c5e 100644 --- a/example/pages/files.mjs +++ b/example/pages/files.mjs @@ -3,144 +3,260 @@ export const state = { description: '@magic/core directory docs.', } -export const View = state => { - const examples = { - page: ` -export default { - state: { - variable: 'test', - }, - actions: { - changeVar: () => ({ variable: 'changed' }), - }, - style: { - '.cl': { - color: 'green', - }, - }, - View: state => div({ class: 'cl' }, [ +export const View = state => [ + h1(state.title), + + p('There are multiple magic files and directories.'), + + ul([ + li('/pages - files in the page directory map to urls in your app.'), + li('/assets - custom components, @magic-modules get imported here'), + li('/assets/static - static files'), + li('/assets/themes - theme directory, @magic-themes get imported here'), + li('/assets/lib.mjs - imports npm and local but external packages into your app'), + li('/app.mjs - gets merged into the app, can set state, actions, style here'), + li('/config.mjs - custom config for your app'), + li('/assets/Menu.mjs - custom Menu for your app'), + ]), + + h2({ id: 'pages' }, '/pages'), + + p('the pages dir contains the pages of your webapp.'), + + p([ + "each page has it's own state and actions, ", + "but also inherits the global state and actions from the app and it's dependencies", + ]), + + h3({ id: 'pages-dir-structure' }, 'pages directory to url map'), + + p('for the domain mag.ic:'), + + Pre(` +/pages/index.mjs === http://mag.ic/ +/pages/pageName.mjs === http://mag.ic/pageName/ +/pages/page-name.mjs === http://mag.ic/page-name/ +/pages/page_name.mjs === http://mag.ic/page_name/ +/pages/dir/index.mjs === http://mag.ic/dir/ +/pages/dir/name.mjs === http://mag.ic/dir/name/ +`), + + h1({ id: 'pages-example' }, 'example page'), + + p('Pages can use javascript, html or markdown'), + + h2({ id: 'pages-example-js' }, 'javascript example'), + + p( + 'A magic javascript page is a @magic-module. The only difference is that pages get exposed via http', + ), + + Pre(` +export const View = state => + div({ class: 'cl' }, [ 'this is the page content.', state.variable, - ]), -}`, - assets: ` + ]) + +export const state = { + variable: 'test', +} + +export const actions = { + changeVar: state => ({ ...state, variable: 'changed' }), +} + +export const style = { + '.cl': { + color: 'green', + }, +} +`), + + h2({ id: 'pages-example-html' }, 'html example'), + + p('html pages can only export state and View.'), + + Pre(` +--- +@state +{ + "title": "html file example", + "description": "this module gets imported from a html file." +} +--- + +

${ state.title }

+ +

{{ state.description }}

+`), + + h2({ id: 'pages-example-markdown' }, 'markdown example'), + + p('markdown pages can only export state and View.'), + + Pre(` +--- +@state { + "title": "markdown file example", + "description": "markdown file description" +} +--- + +## ${state.title} + +${state.description} +`), + + h2({ id: 'assets' }, 'assets'), + + p('the assets dir contains custom components of your app.'), + + p('you can import additional @magic-modules here'), + + h3({ id: 'assets-example' }, '/assets/index.mjs'), + + Pre(` export default { Custom: () => div('custom component'), Pre: require('@magic-modules/pre), -}`, - app: ` +} +`), + + h2({ id: 'static' }, '/assets/static'), + + p([ + 'the static dir contains all of your static assets.', + 'every file in this directory gets copied to the public dir.', + 'image and svg files get minified using imagemin.', + ]), + + p([ + 'text and binary files get compressed using the optional ', + Link({ to: 'https://github.com/jaeh/node-zopfli-es', text: 'node-zopfli-es' }), + ' (if it is installed)', + ]), + + h2({ id: 'themes' }, '/assets/themes'), + + p('the themes directory contains...themes.'), + + p([ + 'a magic theme is an object of css rules, see ', + Link({ text: '@magic/css', to: 'https://github.com/magic/css/' }), + ' for more examples and documentation.', + ]), + + h3({ id: 'themes-example' }, 'example theme'), + + Pre(` export default { - state: { - globalStateVar: 'globally available', - }, - actions: { - globalAction: () => ({ globalStateVar: 'overwritten.' }), + 'body': { + color: 'blue', }, - style: { - 'body': { - color: 'green', - }, +} +`), + + h2({ id: 'app' }, '/app.mjs'), + + p('the /app.mjs file allows you to set global state, actions, and styles'), + + h3({ id: 'app-example' }, '/example/app.mjs'), + + Pre(` +export const state = { + globalStateVar: 'globally available', +} + +export const actions = { + globalAction: () => ({ globalStateVar: 'overwritten.' }), +} + +export const style = { + 'body': { + color: 'green', }, -}`, - config: ` +} +`), + h2({ id: 'config' }, '/config.mjs'), + + p('the /config.mjs file allows you to set various aspects of your app'), + + h3({ id: 'config-example' }, '/config.mjs example'), + + Pre(` export default { + // the local root directory of the magic app ROOT: 'example', - THEME: 'blue', + + // gets suffixed to the url. in this case domain.com/core/ will be the page root. WEB_ROOT: '/core/', + // the theme in use, lives in {ROOT}/assets/themes/{name} or can be installed as either '@magic-themes/{name}' or 'magic-theme-{name}' + THEME: 'docs', + + // the full url of the page. + URL: 'magic.github.io/core/', + + // the public dir the bundles get written to + PUBLIC: 'docs', + + // the directory in the pages directory that holds the blog posts + // if this is not set, no blog is created. + BLOG_DIR: 'news', + + // append these modules after the app loaded and only if javascript is enabled. + // can be used to add features that won't do anything without js anyways. + HOIST: ['LightSwitch', 'NoSpy'], + + // default CLIENT_LIB_NAME, overwrite to change names of transpiled css and js files + CLIENT_LIB_NAME: 'magic', + + // this is set to be able to test broken link behaviour. + // DO NOT ENABLE IN YOUR APP (unless you need to because reasons...) + NO_CHECK_LINKS_EXIT: true, + + // tags written to the html before the #magic container + PREPEND_TAGS: [{ name: 'div', props: { id: 'PREPENDTag' } }], + + // tags written to the html after the #magic container + APPEND_TAGS: [{ name: 'div', props: { id: 'APPENDTag' } }], + + // script files, embedded before the magic.js script + PREPEND_SCRIPTS: ['/prependScript.js'], + + // script files, embedded after the magic.js script + APPEND_SCRIPTS: ['/appendScript.js'], + + // js files that get written directly into the magic.js script, before the magic code. + PREPEND_JS: ['/prependJs.js'], + + // js files that get written directly into the magic.js script, after the magic code. + APPEND_JS: ['/appendJs.js'], + + // css written into the magic.css file, before the magic css + // use this to set defaults that get overwritten by the generated app css + PREPEND_CSS: ['/prependCss.css'], + + // css written into the magic.css file, after the magic css + // use this to overwrite the generated app css + APPEND_CSS: ['/appendCss.css'], + + // add additional static file directories that will get added to app.static[name] + ADD_STATIC: ['node_modules/dir/name/'], + // this option adds the // 'X-Clacks-Overhead', 'GNU Terry Pratchet' // http header // see http://www.gnuterrypratchett.com/ FOR_DEATH_CAN_NOT_HAVE_HIM: true, - - // default CLIENT_LIB_NAME, overwrite to change names of transpiled css and js files - CLIENT_LIB_NAME: 'magic', -}`, - theme: ` -export default { - 'body': { - color: 'blue', - }, -}`, - } - - return [ - h1(state.title), - p('There are multiple magic files and directories.'), - ul([ - li('/pages - files in the page directory map to urls in your app.'), - li('/assets - custom components, @magic-modules get imported here'), - li('/assets/static - static files'), - li('/assets/themes - theme directory, @magic-themes get imported here'), - li('/assets/lib.mjs - imports npm and local but external packages into your app'), - li('/app.mjs - gets merged into the app, can set state, actions, style here'), - li('/config.mjs - custom config for your app'), - li('/assets/Menu.mjs - custom Menu for your app'), - ]), - - h2({ id: 'pages' }, '/pages'), - p('the pages dir contains the pages of your webapp.'), - p([ - "each page has it's own state and actions, ", - "but also inherits the global state and actions from the app and it's dependencies", - ]), - - h3({ id: 'pages-dir-structure' }, 'pages directory to url map, for the domain mag.ic:'), - Pre(` -/pages/index.mjs === http://mag.ic/ -/pages/pageName.mjs === http://mag.ic/pageName/ -/pages/page-name.mjs === http://mag.ic/page-name/ -/pages/page_name.mjs === http://mag.ic/page_name/ -/pages/dir/index.mjs === http://mag.ic/dir/ -/pages/dir/name.mjs === http://mag.ic/dir/name/ +} `), - h3({ id: 'pages-example' }, 'example page:'), - Pre(examples.page), - - h2({ id: 'assets' }, '/assets'), - p('the assets dir contains custom components of your app.'), - p('you can additionally import @magic-modules here'), - h3({ id: 'assets-example' }, 'example /assets/index.mjs'), - Pre(examples.assets), - - h2({ id: 'static' }, '/assets/static'), - p('the static dir contains all of your static assets.'), - p('every file in this directory gets copied to the public dir.'), - p('image and svg files get minified using imagemin'), - p([ - 'text and binary files get compressed using the optional ', - Link({ to: 'https://github.com/jaeh/node-zopfli-es' }, 'node-zopfli-es'), - ' (if it is installed)', - ]), - - h2({ id: 'themes' }, '/assets/themes'), - p('the themes directory contains... themes.'), - p([ - 'a magic theme is an object of css rules, see ', - Link({ to: 'https://github.com/magic/css/' }, '@magic/css'), - ' for more examples and documentation.', - ]), - - h3({ id: 'themes-example' }, 'example theme'), - Pre(examples.theme), - - h2({ id: 'app' }, '/assets/app.mjs'), - p('the /app.mjs file allows you to set global state, actions, and styles'), - - h3({ id: 'app-example' }, 'example /app.mjs'), - Pre(examples.app), - - h2({ id: 'config' }, '/config.mjs'), - p('the /config.mjs file allows you to set various aspects of your app'), - - h3({ id: 'config-example' }, 'example /config.mjs'), - Pre(examples.config), - - Link( - { to: 'https://github.com/magic/core/blob/master/src/modules/Menu.mjs' }, - 'Menu.mjs on github', - ), - ] -} + p( + Link({ + text: 'Menu.mjs on github', + to: 'https://github.com/magic/core/blob/master/src/modules/Menu.mjs', + }), + ), +] diff --git a/example/pages/index.md b/example/pages/index.md new file mode 100644 index 00000000..dee89409 --- /dev/null +++ b/example/pages/index.md @@ -0,0 +1,73 @@ +# ${ state.title } + +## welcome to the magic docs. + +${state.description} + +the goal of this document is to give you a rough @magical overview. + +@magic/core + +## philosophy + +@magic aims to make it easy to stitch together any kind of webapp. +by providing simple, well documented and self contained modules, +@magic makes it possible to create stunningly fast +webpages with minimal cognitive overhead. + +## privacy + +@magic does not spy. + +not only do we try to be legally compliant by default, +but we also aim to be ethical +which means prioritizing your rights over our needs, +but also means we prioritize the rights of your users over your needs. +we believe that this is the best compromise. + +## #buzzwords why should i use magic? + +### features + +- static html pages with opengraph seo. +- pages are hosted for free using gitlab, github or any other git-pages style hosting. +- static css output with selector and rule deduplication. +- no javascript required where possible. +- minimal client boilerplate. +- no spyware included. +- WIP: lambda faas and graphql api generator. +- WIP: server side rendering (if needed). + +### @magic is tiny + +~4 kb javascript boilerplate. +usually, all the javascript in your homepage will be 30-60kb big (after unpacking), +10-30kb get transmitted from the server to the client. +this documentation page you are reading loads 20kb of gzipped javascript, +which, when parsing, turns into ~88kb of uncompressed javascript. +this includes all content of all subpages, no additional server requests needed for any navigations in the page. + +WIP: code splitting for bigger pages. + +### @magic works without javascript + +most of the functionality works without javascript, +some buttons and realtime user interactions obviously won't, +but @magic always tries to provide a non-javascript fallback via css. + +### @magic generates static pages + +this makes free hosting (using github or gitlab pages) possible. and it's easy. + +@magic publishes to [github](https://github.com), [gitlab](https://about.gitlab.com/), +and any other git-pages enabled hosting service. + +### serverless / faas + +automagically generates +serverless lambdas, derived from the +[@magic-modules](https://github.com/magic-modules/) +you use in your pages. + +this makes visitor statistics, user authentication and authorization, +chat, and all other server side services possible. diff --git a/example/pages/index.mjs b/example/pages/index.mjs deleted file mode 100644 index 7e33cb73..00000000 --- a/example/pages/index.mjs +++ /dev/null @@ -1,88 +0,0 @@ -export const state = { - title: '@magic/core docs', - description: [ - '@magic/core documentation.', - 'tells you why, how and when to use @magic.', - 'also provides an overview of all @magic functionality this ecosystem provides.', - ], -} - -export const View = state => [ - h1(state.title), - div([ - h2('welcome to the magic docs.'), - p('the goal of this document is to give you a rough @magical overview.'), - - GitBadges('magic/core'), - - h2({ id: 'philosophy' }, 'philosophy'), - - p([ - '@magic aims to make it easy to stitch together any kind of webapp.', - ' by providing simple, well documented and self contained modules,', - ' @magic makes it possible to create stunningly fast', - ' webpages with minimal cognitive overhead.', - ]), - - h2({ id: 'privacy' }, 'privacy'), - p('@magic does not spy.'), - p([ - 'not only do we try to be legally compliant by default,', - ' but we also aim to be ethical', - ' which means prioritizing your rights over our needs,', - ' but also means we prioritize the rights of your users over your needs.', - ' we believe that this is the best compromise.', - ]), - - h2({ id: 'buzzwords' }, 'why should i use magic?'), - - h3('features'), - ul([ - li('static html pages with opengraph seo.'), - li('pages are hosted for free using gitlab, github or any other git-pages style hosting.'), - li('static css output with selector and rule deduplication.'), - li('no javascript required where possible.'), - li('minimal client boilerplate.'), - li('no spyware included.'), - li('WIP: lambda faas and graphql api generator.'), - li('WIP: server side rendering (if needed).'), - ]), - - h3('@magic is tiny'), - p([ - '~4 kb javascript boilerplate.', - ' usually, all the javascript in your homepage will be 30-60kb big (after unpacking),', - ' 10-30kb get transmitted from the server to the client.', - ' this documentation page you are reading loads about 15kb of javascript,', - ' which, when parsing, turns into 40kb of uncompressed javascript.', - ]), - - h3('@magic works without javascript'), - p([ - 'most of the functionality works without javascript,', - " some buttons and realtime user interactions obviously won't,", - ' but @magic always tries to provide a non-javascript fallback via css.', - ]), - - h3('@magic generates static pages'), - p(['this makes free hosting (using github or gitlab pages) possible.', " and it's easy."]), - - p([ - '@magic publishes to ', - Link({ to: 'https://github.com' }, 'github'), - ', ', - Link({ to: 'https://gitlab.com' }, 'gitlab'), - ' and any other git-pages enabled hosting service.', - ]), - - h3('serverless / faas'), - p([ - 'automagically generates ', - ' serverless lambdas, derived from the ', - Link({ to: 'https://github.com/magic-modules/' }, '@magic-modules'), - ' you use in your pages.', - ' this makes visitor statistics, user authentication and authorization,', - ' chat, and all other server side services possible.', - ]), - ]), -] diff --git a/example/pages/libraries.md b/example/pages/libraries.md new file mode 100644 index 00000000..ae20d8a8 --- /dev/null +++ b/example/pages/libraries.md @@ -0,0 +1,71 @@ +--- +@state { + "title": "@magic/core library docs", + "description": "@magic/core libraries allow you to include client side functionality in your app." +} +--- + +# @magic/core library docs + +@magic/core libraries allow you to include client side functionality in your app. + +## #abstract libraries + +what would javascript be without the millions of dependencies +that you can easily install and make the average webpage +slow[ly] (pun intended) grow towards a footprint of 5 megabytes. + +we think that all of that bloat is unneeded, unheeded and, frankly, not optimal. + +magic has one external client side dependency, +[hyperapp](https://github.com/jorgebucaran/hyperapp/), +[~500 lines that provide our ui state machine]. thats it. and it won't change. + +we also have the tendency to write libraries specialized for our usecase, see +[@magic/css](https://github.com/magic/css), +[@magic/test](https://github.com/magic/test), +[@magic/cli](https://github.com/magic/cli) +and others. + +once there is a lib key in at least one component, +window.lib (browser) and global.lib (nodejs) will be set, +aliasing lib as a global variable in both environments + +### #dir-or-file lib dir or file + +if you need libraries in multiple otherwise independent modules, +it might be easier to keep your library dependencies in a central place. + +to achieve this, one can simply create /assets/lib.mjs and export an object from it. +this object will get merged into the globalThis.lib object, +making it available as "lib" throughout your app. + +
export default { name: () => {} }
+ +will turn into + +
lib.name = () => {}
+ +### npm + +all @magic-libraries and all npm packages starting with magic-library-\* will be loaded automatically + +#### Example + +first, install a @magic-library + +
+npm install --save --save-exact @magic-libraries/is
+
+ +then, in javascript + +
+export const View = props => div([
+  'value is ',
+  lib.is(props.value, 'string') ? '' : 'not',
+  ' a string',
+])
+
+ + diff --git a/example/pages/libraries.mjs b/example/pages/libraries.mjs deleted file mode 100644 index abbb6043..00000000 --- a/example/pages/libraries.mjs +++ /dev/null @@ -1,77 +0,0 @@ -export const state = { - title: '@magic/core library docs', - description: '@magic/core libraries allow you to include client side functionality in your app.', -} - -export const View = state => [ - div([ - h1(state.title), - p(state.description), - - h2({ id: 'abstract' }, 'libraries'), - p([ - 'what would javascript be without the millions of dependencies', - ' that you can easily install and make the average webpage ', - ' slow[ly] (pun intended) grow towards a footprint of 5 megabytes.', - ]), - p('we think that all of that bloat is unneeded, unheeded and, frankly, not optimal.'), - p([ - 'magic has one external client side dependency, ', - Link({ to: 'https://github.com/jorgebucharan/hyperapp' }, 'hyperapp'), - ", [~400 lines that provide our ui state machine]. thats it. and it won't change.", - ]), - p([ - 'we also have the tendency to write libraries specialized for our usecase, see ', - Link({ href: 'https://github.com/magic/css' }, '@magic/css'), - ' ', - Link({ href: 'https://github.com/magic/test' }, '@magic/test'), - ' ', - Link({ href: 'https://github.com/magic/cli' }, '@magic/cli'), - ' and others.', - ]), - - p([ - 'once there is a lib key in at least one component,', - ' window.lib (browser) and global.lib (nodejs) will be set,', - ' aliasing lib as a global variable in both environments', - ]), - - h3({ id: 'dir-or-file' }, 'lib dir or file'), - p([ - 'if you need libraries in multiple otherwise independent modules,', - ' it might be easier to keep your library dependencies in a central place.', - ' to achieve this, one can simply create /assets/lib.mjs and export an object from it.', - ' this object will get merged into the globalThis.lib object making it available as "lib" throughout your app.', - ]), - p([ - 'alternatively, you can create a /assets/modules directory and place UpperCaseNamed.mjs files in there.', - ' their names will be deferred from the filename and they will be available in your app without importing them.', - ' if you do not use one of the modules, dead code elimination will simply remove it in production.', - ]), - - Pre('export default { name: () => {} }'), - 'will turn into', - Pre('lib.name = () => {}'), - - h3({ id: 'npm' }, '@magic-libraries from npm'), - p( - 'all @magic-libraries/* and all npm packages starting with magic-library-* will be loaded automatically. ', - ), - - h4({ id: 'example' }, 'Example'), - p('first, install a @magic-library'), - Pre(` -npm install --save --save-exact @magic-libraries/is -`), - p('then, in javascript'), - Pre(` -export const View = props => div([ - 'value is ', - lib.is(props.value, 'string') ? '' : 'not', - ' a string' -]) - `), - - LibraryList(), - ]), -] diff --git a/example/pages/modules/example.mjs b/example/pages/modules/example.md similarity index 52% rename from example/pages/modules/example.mjs rename to example/pages/modules/example.md index 7575afb5..e5c9b31f 100644 --- a/example/pages/modules/example.mjs +++ b/example/pages/modules/example.md @@ -1,21 +1,22 @@ -export const state = { - title: '@magic-modules/example', - description: '@magic-modules example module.', +--- +@state { + "title": "@magic-modules/example", + "description": "@magic-modules example module." } +--- -export const View = state => [ - h1('example module'), +# ${state.title} - h2({ id: 'custom-module' }, 'Mod and Mod.Component:'), +## custom module - Mod(state), + - Mod.Component({ title: 'Mod Component Title, passed via props' }), + - h3('Mod sourcecode:'), +### Mod sourcecode: - Pre(` - export const View = state => +
+export const View = state =>
   div({ class: 'Mod' }, [
     h3('Mod.Mod'),
     p([
@@ -48,27 +49,33 @@ export const global = {
     module: true,
   },
 }
+
+ +### Component sourcecode: -export const Component = props => { +
+export const View = props => {
   props = typeof props === 'string' ? { header: props } : props
-  CHECK_PROPS(props, propTypes, 'ModComponent')
+  CHECK_PROPS(props, propTypes, 'Component')
   const header = props.header || props.title
 
-  return div({ class: 'ModComponent' }, [
+  return div({ class: 'Component' }, \\[
     header && h5(header),
     p([
-      'Mod.Component, a second component in ',
-      Link({ to: 'https://github.com/magic/core/example/assets/module.mjs' }, '/assets/module.mjs'),
+      'Component, a second component in ',
+      Link(
+        { to: 'https://github.com/magic/core/blob/master/example/assets/modules/Component.mjs' },
+        '/assets/modules/Component.mjs',
+      ),
     ]),
   ])
 }
 
-Component.style = {
+export const style = {
   border: '1px solid orange',
 }
 
 export const propTypes = {
-  ModComponent: [{ key: 'header', type: ['string', 'array'], required: ['title'] }],
+  Component: [{ key: 'header', type: ['string', 'array'], required: ['title'] }],
 }
-`),
-]
+
diff --git a/example/pages/modules/html.html b/example/pages/modules/html.html index 46d5fa88..e62af451 100644 --- a/example/pages/modules/html.html +++ b/example/pages/modules/html.html @@ -8,7 +8,7 @@

${state.title}

-

${state.description}

+

{{state.description}}

see this html file for an example.

@@ -16,7 +16,7 @@

${state.title}

the state looks like the following, and any valid json is allowed

-
+
 \-\-\-
 @state
 {
@@ -24,4 +24,4 @@ 

${state.title}

"description": "markdown file description" } \-\-\- -
+
diff --git a/example/pages/modules/index.md b/example/pages/modules/index.md new file mode 100644 index 00000000..225eb6d5 --- /dev/null +++ b/example/pages/modules/index.md @@ -0,0 +1,58 @@ +--- +@state { + "title": "@magic-modules", + "description": "@magic-modules documentation." +} +--- + +# ${state.title} + +magic modules are predefined modules for webapps. + +

module definition

+ +the minimal module is a function that returns some html. + +
+// /assets/ModuleName.mjs
+
+// simplest module
+export const View = () => div('hello, world')
+
+// complete signature
+export const View = (props = {}, children = []) => div('hello, world')
+
+ +

usage

+ +if the npm package name starts with @magic-modules/ or magic-module-, it will get imported automagically. +the name of the Module will be set to a PascalCased version of the remainder of the module name. +@magic-modules/git-badges, for example, turns into GitBadges. +the same is true for all uppercased files in your /assets/ directory and subdirectories. +in the rare case where you want to install a npm module that can not be found, +you can import it in /assets/index.mjs + +
+// /assets/index.mjs
+import NpmModule from 'non-standard-named-magic-module-from-npm'
+
+export default {
+  // ...otherModules
+
+  // load module from node_modules
+  NpmModule,
+}
+
+ +after this, the module will be a global in your app and can be used like any other component. + +
+// any page or module
+export default state => div([
+  // module without props
+  Mod(),
+  'modules that need props: ',
+  Mod({ state, customProp: true }),
+
+ + diff --git a/example/pages/modules/index.mjs b/example/pages/modules/index.mjs deleted file mode 100644 index 0493d5ce..00000000 --- a/example/pages/modules/index.mjs +++ /dev/null @@ -1,52 +0,0 @@ -export const state = { - title: '@magic-modules', - description: '@magic-modules documentation.', -} - -export const View = state => [ - h1(state.title), - p('magic modules are predefined modules for webapps.'), - - h2({ id: 'definition' }, 'module definition:'), - p('the minimal module is a function that returns some html.'), - Pre(` -// /assets/ModuleName.mjs - -// simplest module -export const View = () => div('hello, world') - -// complete signature -export const View = (props = {}, children = []) => div('hello, world') -`), - - h2({ id: 'usage' }, 'usage'), - p([ - 'if the npm package name starts with @magic-modules/ or magic-module-, it will get imported automagically.', - ' the name of the Module will be set to a PascalCased version of the remainder of the module name.', - ' @magic-modules/git-badges, for example, turns into GitBadges.', - ' the same is true for all uppercased files in your /assets/ directory and subdirectories.', - ' in the rare case where you want to install a npm module that can not be found, you can import it in /assets/index.mjs', - ]), - Pre(` -// /assets/index.mjs -export default { - // ...otherModules - - // load module from node_modules - NpmModule: require('not-standard-named-magic-module-from-npm'), -}`), - p( - 'after this, the module will be a global in your app and can be used like any other component.', - ), - - Pre(` -// any page or module -export default state => div([ - // module without props - Mod(), - 'modules that need props: ', - Mod({ state, customProp: true }), -`), - - ModuleList(), -] diff --git a/example/pages/modules/markdown.md b/example/pages/modules/markdown.md index 5daa16ea..f73e3c64 100644 --- a/example/pages/modules/markdown.md +++ b/example/pages/modules/markdown.md @@ -1,6 +1,5 @@ --- -@state -{ +@state { "title": "markdown file example", "description": "markdown file description" } @@ -18,17 +17,21 @@ any kind of markdown can be used here, but if you use html natively, only tags valid in a html5 body, excluding <script> and <style> tags, are accepted. +### a list: + +- [@magic/core](https://magic.github.io/) +- [@magic-libraries](https://magic-libraries.github.io/) +- [@magic-modules](https://magic-modules.github.io/) +- [@magic-themes](https://magic-themes.github.io/) + +### state + this markdown file also starts with a magic @state declaration. it is used internally to, for example, add the title and meta rel="description" tags to the head of this html file. -* [@magic/core](https://magic.github.io) -* [@magic-libraries](https://magic-libraries.github.io) -* [@magic-modules](https://magic-modules.github.io) -* [@magic-themes](https://magic-themes.github.io) - the state looks like the following, and any valid json is allowed -``` +
 \-\-\-
 @state
 {
@@ -36,4 +39,4 @@ the state looks like the following, and any valid json is allowed
   "description": "markdown file description"
 }
 \-\-\-
-```
+
diff --git a/example/pages/modules/preinstalled.md b/example/pages/modules/preinstalled.md new file mode 100644 index 00000000..476e0359 --- /dev/null +++ b/example/pages/modules/preinstalled.md @@ -0,0 +1,186 @@ +--- +@state { + "title": "@magic-modules", + "description": "@magic-modules documentation." +} +--- + +# ${state.title} + +

preinstalled

+ +magic has some preinstalled modules that will be used in most pages. + +

app

+ +this is the main app module. +it has magically inherited properties and all of it is customizable. + +to add actions/state/style to the app you can just create an /assets/app.mjs file. +the contents of this file get +[deep .merged](https://github.com/magic/deep) +into the app + +
+// /src/app.mjs
+export const state = {
+  merge: 'gets merged into state',
+}
+export const actions = {
+  mergedActions: state => ({ ...state, merge: 'merged action executed' }),
+}
+
+ + + +the Menu module provides... menus. + +
+export const View = state => {
+  const items = [
+    { to: '/', text: 'example page' },
+    { to: 'https://example.com', text: 'example.com' },
+    { to: 'https://example.com', nofollow: true, noreferrer: true, target: 'utopia', text: 'nofollow and noref" },
+  ]
+
+  return Menu({ items, collapse: false })
+}
+
+// output:
+<nav class="Menu">
+  <ul>
+    <li>
+      <a onclick="actions.go" href="/">example page
+    </li>
+    <li>
+      <a href="https://example.com" target="_blank" rel="noopener">example.com
+    </li>
+    <li>
+      <a href="https://example.com" target="utopia" rel="noopener nofollow noreferrer">nofollow and noref
+    </li>
+  </ul>
+</nav>
+
+ + + +the Menu module allows multiple props to be passed when instantiating the Menu + + + +by default, the menu will only show submenu items if their parent link is active. +to force submenu items to show at all times, just pass a collapse: false prop + +
+Menu({
+  // if false, menu will always show all submenu items
+  collapse: false, // (default: true)
+})
+
+ + + +every MenuItem accepts the same props as a link does. +additionally a MenuItem accepts an items prop with sub menu items. + +
+const menuItem = ({
+  to: '/url',
+  text: 'link text',
+  items: [MenuItems],
+  noreferrer: true, // set rel='noreferrer'
+  nofollow: true, // set rel='nofollow'
+})
+
+ + + +to define a submenu, simply define a .items array on the menu item + +
+// assets/app.mjs
+export default {
+  state: {
+    // ...state
+    menuName: [
+      {
+        to: '/example-page',
+        text: 'example page',
+        items: [
+          { to: '/example-page/#sub', text: 'example sub page' },
+      ] },
+    ],
+  },
+  // ... rest of app.mjs
+}
+
+ + + +the link module allows you to link to things. + +
+// in any page or module View
+export default () => [
+  Link({ to: '/', text: 'page' }),
+  // output: <a href="/" onclick="actions.go">page
+  Link({ to: 'https://example.com', text: 'page' }),
+  // output: <a href="https://example.com" target="_blank" rel="noopener">page
+  Link({ to: '/', text: 'page', nofollow: true, noreferrer: true }),
+  // output: <a href="https://example.com" target="_blank" rel="nofollow noreferrer noopener">page
+
+  // you can also use children syntax instead of the text prop:
+  Link({ to: '/' }, 'home'),
+
+  // Link also supports # hash links
+  Link({ to: '/#hash' }, 'home with hash'),
+]
+
+ +

img

+ +the img module adds some sane default values to your images. + +
+// in any page or module View
+export default () => [
+  Img('/image.png'),
+  // output: <img src="/image.png" alt="" role="presentation"/>
+  Img({ src: '/image.png' }),
+  // output: <img src="/image.png" alt="" role="presentation"/>
+  Img({ src: '/image.png', alt: 'image description' }),
+  // output: <img src="/image.png" alt="image description" />
+  Img({ src: '/image.png', title: 'image title', }),
+  // output: <img src="/image.png" title="image title" alt="image title"/>
+  Img({ src: '/image.png', title: 'image title', alt: 'image alt' }),
+  // output: <img src="/image.png" title="image title" alt="image alt"/>
+]
+
+ + + +the footer module contains a small info text and a link to the magic github repository. + +to overwrite this behaviour, just place a Footer.mjs file in your assets +and require it in /assets/index.mjs. + +
+// /assets/Footer.mjs:
+const Footer = () =>
+footer({ class: 'main' }, [
+  div({ class: 'wrapper' }, [
+    'made with a few bits of ',
+    Link({ href: 'https://github.com/magic/core', target: '_blank', rel: 'noopener' }, 'magic'),
+  ]),
+])
+
+Footer.style: {
+  'footer.main': {
+    position: 'relative',
+    textAlign: 'center',
+    padding: '5em 0 .5em',
+  },
+}
+
+export default Footer
+
diff --git a/example/pages/modules/preinstalled.mjs b/example/pages/modules/preinstalled.mjs deleted file mode 100644 index b059c00b..00000000 --- a/example/pages/modules/preinstalled.mjs +++ /dev/null @@ -1,172 +0,0 @@ -export const state = { - title: '@magic-modules', - description: '@magic-modules documentation.', -} - -export const View = state => [ - h1('@magic modules'), - - h2({ id: 'preinstalled' }, 'preinstalled'), - p('magic has some preinstalled modules that will be used in most pages.'), - - h2({ id: 'app' }, 'app'), - p([ - 'this is the main app module.', - ' it has magically inherited properties and all of it is customizable.', - ]), - p([ - 'to add actions/state/style to the app you can just create an /assets/app.mjs file.', - 'the contents of this file get ', - Link({ to: 'https://github.com/magic/deep', text: 'deep .merged' }), - ' into the app', - ]), - Pre(` -// /assets/app.mjs -export const state = { - merge: 'gets merged into state', -} -export const actions = { - mergedActions: state => ({ ...state, merge: 'merged action executed' }), -} -`), - - h2({ id: 'menu' }, 'menu'), - p('the Menu module provides... menus.'), - - Pre(` -export const View = state => { - const items = [ - { to: '/example-page', text: 'example page' }, - { to: 'https://example.com', text: 'example.com' }, - { to: 'https://example.com', nofollow: true, noreferrer: true, target: 'utopia', text: 'nofollow and noref" }, - ] - - return Menu({ items, collapse: false }) -} -// output: - -}`), - - h3({ id: 'menu-props' }, 'Menu props'), - p('the Menu module allows multiple props to be passed when instantiating the Menu'), - - h3({ id: 'menu-props-collapse' }, 'props.collapse'), - p([ - 'by default, the menu will only show submenu items if their parent link is active.', - ' to force submenu items to show at all times, just pass a collapse: false prop', - ]), - - Pre(` -Menu({ - // if false, menu will always show all submenu items - collapse: false, // (default: true) -})`), - - h3({ id: 'menu-item-props' }, 'Menu.Item props'), - p([ - 'every MenuItem accepts props the same props as a link does.', - ' additionally a MenuItem accepts a items prop with sub menu items.', - ]), - - Pre(` -const menuItem = ({ - to: '/url', - text: 'link text', - items: [MenuItems], - noreferrer: true, // set rel='noreferrer' - nofollow: true, // set rel='nofollow' -})`), - - h3({ id: 'menu-sub-menus' }, 'sub menus'), - p('to define a submenu, simply define a .items array on the menu item'), - Pre(` -// assets/app.mjs -export default { - state: { - // ...state - menuName: [ - { - to: '/example-page', - text: 'example page', - items: [ - { to: '/example-page/#sub', text: 'example sub page' }, - ] }, - ], - }, - // ... rest of app.mjs -}`), - - h2({ id: 'link' }, 'link'), - p('the link module allows you to link to things.'), - Pre(` -// in any page or module View -export default () => [ - Link({ to: '/page', text: 'page' }), - // output: page - Link({ to: 'https://example.com', text: 'page' }), - // output: page - Link({ to: '/page', text: 'page', nofollow: true, noreferrer: true }), - // output: page - - // you can also use children syntax instead of the text prop: - Link({ to: '/' }, 'home'), - - // Link also supports # hash links - Link({ to: '/#hash' }, 'home with hash'), -]`), - - h2({ id: 'img' }, 'img'), - p('the img module adds some sane default values to your images.'), - Pre(` -// in any page or module View -export default () => [ - Img('/image.png'), - // output: - Img({ src: '/image.png }), - // output: - Img({ src: '/image.png', alt: 'image description' }), - // output: - Img({ src: '/image.png', title: 'image title', }), - // output: image title - Img({ src: '/image.png', title: 'image title', alt: 'image alt' }), - // output: image alt -]`), - - h2({ id: 'footer' }, 'footer'), - p('the footer module contains a small info text and a link to the magic github repository.'), - p( - 'to overwrite this behaviour, just place a Footer.mjs file in your assets and require it in /assets/index.mjs.', - ), - Pre(` -// /assets/Footer.mjs: -const Footer = () => -footer({ class: 'main' }, [ - div({ class: 'wrapper' }, [ - 'made with a few bits of ', - Link({ href: 'https://github.com/magic/core', target: '_blank', rel: 'noopener' }, 'magic'), - ]), -]) - -Footer.style: { - 'footer.main': { - position: 'relative', - textAlign: 'center', - padding: '5em 0 .5em', - }, -} - -export default Footer - `), -] diff --git a/example/pages/modules/propTypes.mjs b/example/pages/modules/propTypes.md similarity index 53% rename from example/pages/modules/propTypes.mjs rename to example/pages/modules/propTypes.md index 6979604a..a9717d22 100644 --- a/example/pages/modules/propTypes.mjs +++ b/example/pages/modules/propTypes.md @@ -1,16 +1,19 @@ -export const state = { - title: '@magic-modules/propTypes', - description: '@magic-modules/propTypes documentation.', +--- +@state { + "title": "@magic-modules/prop-types", + "description": "@magic-modules/prop-types documentation." } +--- -export const View = state => [ - h1(state.title), +# @magic-modules/prop-types - h2({ id: 'check-props' }, 'CHECK_PROPS'), - p('@magic-modules can export a .propTypes object with an array of prop types.'), +

CHECK_PROPS

- h4({ to: '#example' }, 'example'), - Pre(` +@magic-modules can export a .propTypes object with an array of prop types. + +

example

+ +
 export const View = (prop1, prop2, prop3) => [
   p(prop1),
   p(prop2),
@@ -29,5 +32,4 @@ export const propTypes = [
       { name: 'prop4prop2', type: 'number' },
   ] },
 ]
-`),
-]
+
diff --git a/example/pages/test.mjs b/example/pages/test.mjs new file mode 100644 index 00000000..9b9fc8c7 --- /dev/null +++ b/example/pages/test.mjs @@ -0,0 +1,51 @@ +export const View = state => [ + h2('Test page'), + + p('This page shows various features and tests of magic functionality'), + + h3('Link Tests:'), + + ul([ + li(Link({ to: '/modules/' })), + li(Link({ to: '/modules/#gl-magic-modules' })), + li(Link({ to: 'https://magic.github.io' })), + ]), + + h3('Broken link tests:'), + + p("the following links are broken, and it's intentional for magic to warn us on every rebuild."), + + ul([ + li(Link({ text: 'redirect link', to: 'https://magic.github.io/core#expected-redirect' })), + + li(Link({ text: 'broken link', to: 'https://expect-error' })), + li( + Link({ + text: '404 link', + to: 'https://en.wikipedia.org/hMdYfVaKY4btraQcgD0me6RRBDnugbpJ4FLpgJgeB7', + }), + ), + li(Img({ alt: 'Broken Image Link', src: 'https://broken-image-link' })), + ]), + + h3('Image test'), + + p("while at it, let's test an image, 2 times with a working src:"), + + ul([ + li(Img({ alt: 'Magic Logo', src: '/logo.png' })), + li(Img({ alt: 'Magic Logo', src: '/core/logo.png' })), + ]), + + p('and once with a broken src:'), + + Img({ alt: 'Broken Magic Logo', src: '/logo23-broken.png' }), + + h3('Links in modules'), + p(Link({ to: '/', text: 'Link in a paragraph' })), + + ul([ + li(Link({ to: '/', text: 'Link in a list' })), + li(Link({ to: '/', text: 'Second Link in a list' })), + ]), +] diff --git a/example/pages/themes.md b/example/pages/themes.md new file mode 100644 index 00000000..0b369ceb --- /dev/null +++ b/example/pages/themes.md @@ -0,0 +1,32 @@ +--- +@state { + "title": "@magic-themes", + "description": "@magic-theme docs." +} +--- + +# @magic-themes + +magic themes are themes for magic apps. +you decide which theme to load by specifying the theme name in config.THEME + +
+// /config.mjs
+export default {
+  // ...rest of config,
+  THEME: 'blue',
+}
+
+ +## theme load order + +themes get loaded from multiple places. last in the list overwrites earlier entries. + +
+// ...default module styles get inserted here
+/node_modules/@magic/core/src/themes/THEME/index.mjs
+/node_modules/@magic-themes/THEME
+/assets/themes/THEME/index.mjs
+
+ + diff --git a/example/pages/themes.mjs b/example/pages/themes.mjs deleted file mode 100644 index b7864712..00000000 --- a/example/pages/themes.mjs +++ /dev/null @@ -1,29 +0,0 @@ -export const state = { - title: '@magic-themes', - description: '@magic-theme docs.', -} - -export const View = state => [ - h1(state.title), - p( - 'magic themes are themes for magic apps. you decide which theme to load by specifying the theme name in config.THEME', - ), - Pre(` -// /config.mjs -export default { - // ...rest of config, - THEME: 'blue', -} -`), - - h2('theme load order'), - p('themes get loaded from multiple places. last in the list overwrites earlier entries.'), - Pre(` -// ...default module styles get inserted here -/node_modules/@magic/core/src/themes/THEME/index.mjs -/node_modules/@magic-themes/THEME -/assets/themes/THEME/index.mjs -`), - - ThemeList(), -] diff --git a/config.mjs b/magic.js similarity index 54% rename from config.mjs rename to magic.js index 9107514c..162478e4 100644 --- a/config.mjs +++ b/magic.js @@ -2,7 +2,7 @@ export default { ROOT: 'example', THEME: 'docs', WEB_ROOT: '/core/', - URL: 'magic.github.io/core', + URL: 'magic.github.io/core/', PUBLIC: 'docs', @@ -10,13 +10,19 @@ export default { // if this is not set, no blog is created. BLOG_DIR: 'news', + // should this page utilize code splitting + SPLIT: false, + // this option adds the // 'X-Clacks-Overhead', 'GNU Terry Pratchet' // http header + // when serving pages using grundstein. // see http://www.gnuterrypratchett.com/ FOR_DEATH_CAN_NOT_HAVE_HIM: true, - KEEP_CLIENT: false, + HOIST: ['LightSwitch', 'NoSpy'], - HOIST: ['Gdpr', 'LightSwitch'], + // this is set to be able to test broken link behaviour. + // DO NOT ENABLE IN YOUR APP (unless you need to because reasons...) + NO_CHECK_LINKS_EXIT: true, } diff --git a/package-lock.json b/package-lock.json index 3d04c758..6e49ab3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,2784 +1,3620 @@ { "name": "@magic/core", - "version": "0.0.33", - "lockfileVersion": 1, + "version": "0.0.159", + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", - "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", - "requires": { - "@babel/highlight": "^7.8.3" + "packages": { + "": { + "name": "@magic/core", + "version": "0.0.159", + "license": "AGPL-3.0", + "dependencies": { + "@magic-libraries/prevent-default": "0.0.6", + "@magic/cases": "0.0.13", + "@magic/cli": "0.0.54", + "@magic/css": "0.7.59", + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/fs": "0.0.39", + "@magic/hyperapp": "2.0.4-alpha.0.6", + "@magic/log": "0.1.22", + "@magic/tags": "0.0.15", + "@magic/transmute": "0.0.59", + "@magic/types": "0.1.37", + "@swc/core": "1.15.43" + }, + "bin": { + "magic": "src/bin.mjs" + }, + "devDependencies": { + "@magic-modules/git-badges": "0.0.12", + "@magic-modules/library-list": "0.0.18", + "@magic-modules/light-switch": "0.0.12", + "@magic-modules/module-list": "0.0.22", + "@magic-modules/no-spy": "0.0.9", + "@magic-modules/pre": "0.0.12", + "@magic-modules/theme-list": "0.0.14", + "@magic-themes/docs": "0.0.15", + "@magic/format": "0.0.74", + "@magic/test": "0.3.12" + }, + "engines": { + "node": ">=14.15.4" } }, - "@babel/compat-data": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.8.5.tgz", - "integrity": "sha512-jWYUqQX/ObOhG1UiEkbH5SANsE/8oKXiQWjj7p7xgj9Zmnt//aUvyz4dBkK0HNsS8/cbyC5NmmH87VekW+mXFg==", - "requires": { - "browserslist": "^4.8.5", - "invariant": "^2.2.4", - "semver": "^5.5.0" - } - }, - "@babel/core": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.8.4.tgz", - "integrity": "sha512-0LiLrB2PwrVI+a2/IEskBopDYSd8BCb3rOvH7D5tzoWd696TBEduBvuLVm4Nx6rltrLZqvI3MCalB2K2aVzQjA==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helpers": "^7.8.4", - "@babel/parser": "^7.8.4", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.0", - "lodash": "^4.17.13", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", - "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", - "requires": { - "@babel/types": "^7.8.3", - "jsesc": "^2.5.1", - "lodash": "^4.17.13", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.8.3.tgz", - "integrity": "sha512-6o+mJrZBxOoEX77Ezv9zwW7WV8DdluouRKNY/IR5u/YTMuKHgugHOzYWlYvYLpLA9nPsQCAAASpCIbjI9Mv+Uw==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.8.3.tgz", - "integrity": "sha512-5eFOm2SyFPK4Rh3XMMRDjN7lBH0orh3ss0g3rTYZnBQ+r6YPj7lgDyCvPphynHvUrobJmeMignBr6Acw9mAPlw==", - "requires": { - "@babel/helper-explode-assignable-expression": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-builder-react-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.8.3.tgz", - "integrity": "sha512-JT8mfnpTkKNCboTqZsQTdGo3l3Ik3l7QIt9hh0O9DYiwVel37VoJpILKM4YFbP2euF32nkQSb+F9cUk9b7DDXQ==", - "requires": { - "@babel/types": "^7.8.3", - "esutils": "^2.0.0" - } - }, - "@babel/helper-call-delegate": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.8.3.tgz", - "integrity": "sha512-6Q05px0Eb+N4/GTyKPPvnkig7Lylw+QzihMpws9iiZQv7ZImf84ZsZpQH7QoWN4n4tm81SnSzPgHw2qtO0Zf3A==", - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.4.tgz", - "integrity": "sha512-3k3BsKMvPp5bjxgMdrFyq0UaEO48HciVrOVF0+lon8pp95cyJ2ujAh0TrBHNMnJGT2rr0iKOJPFFbSqjDyf/Pg==", - "requires": { - "@babel/compat-data": "^7.8.4", - "browserslist": "^4.8.5", - "invariant": "^2.2.4", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.8.3.tgz", - "integrity": "sha512-Gcsm1OHCUr9o9TcJln57xhWHtdXbA2pgQ58S0Lxlks0WMGNXuki4+GLfX0p+L2ZkINUGZvfkz8rzoqJQSthI+Q==", - "requires": { - "@babel/helper-regex": "^7.8.3", - "regexpu-core": "^4.6.0" - } - }, - "@babel/helper-define-map": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.8.3.tgz", - "integrity": "sha512-PoeBYtxoZGtct3md6xZOCWPcKuMuk3IHhgxsRRNtnNShebf4C8YonTSblsK4tvDbm+eJAw2HAPOfCr+Q/YRG/g==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.8.3.tgz", - "integrity": "sha512-N+8eW86/Kj147bO9G2uclsg5pwfs/fqqY5rwgIL7eTBklgXjcOJ3btzS5iM6AitJcftnY7pm2lGsrJVYLGjzIw==", - "requires": { - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", - "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", - "requires": { - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", - "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.8.3.tgz", - "integrity": "sha512-ky1JLOjcDUtSc+xkt0xhYff7Z6ILTAHKmZLHPxAhOP0Nd77O+3nCsd6uSVYur6nJnCI029CrNbYlc0LoPfAPQg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", - "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-imports": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", - "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-module-transforms": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.8.3.tgz", - "integrity": "sha512-C7NG6B7vfBa/pwCOshpMbOYUmrYQDfCpVL/JCRu0ek8B5p8kue1+BCXpg2vOYs7w5ACB9GTOBYQ5U6NwrMg+3Q==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", - "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", - "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==" - }, - "@babel/helper-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.8.3.tgz", - "integrity": "sha512-BWt0QtYv/cg/NecOAZMdcn/waj/5P26DR4mVLXfFtDokSR6fyuG0Pj+e2FqtSME+MqED1khnSMulkmGl8qWiUQ==", - "requires": { - "lodash": "^4.17.13" - } - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.8.3.tgz", - "integrity": "sha512-kgwDmw4fCg7AVgS4DukQR/roGp+jP+XluJE5hsRZwxCYGg+Rv9wSGErDWhlI90FODdYfd4xG4AQRiMDjjN0GzA==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-wrap-function": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-replace-supers": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.3.tgz", - "integrity": "sha512-xOUssL6ho41U81etpLoT2RTdvdus4VfHamCuAm4AHxGr+0it5fnwoVdwUJ7GFEqCsQYzJUhcbsN9wB9apcYKFA==", - "requires": { - "@babel/helper-member-expression-to-functions": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-simple-access": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", - "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", - "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", - "requires": { - "@babel/types": "^7.8.3" - } - }, - "@babel/helper-wrap-function": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.8.3.tgz", - "integrity": "sha512-LACJrbUET9cQDzb6kG7EeD7+7doC3JNvUgTEQOx2qaO1fKlzE/Bf05qs9w1oXQMmXlPO65lC3Tq9S6gZpTErEQ==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/helpers": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.8.4.tgz", - "integrity": "sha512-VPbe7wcQ4chu4TDQjimHv/5tj73qz88o12EPkO2ValS2QiQS/1F2SsjyIGNnAD0vF/nZS6Cf9i+vW6HIlnaR8w==", - "requires": { - "@babel/template": "^7.8.3", - "@babel/traverse": "^7.8.4", - "@babel/types": "^7.8.3" - } - }, - "@babel/highlight": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", - "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", - "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==" - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.8.3.tgz", - "integrity": "sha512-NZ9zLv848JsV3hs8ryEh7Uaz/0KsmPLqv0+PdkDJL1cJy0K4kOCFa8zc1E3mp+RHPQcpdfb/6GovEsW4VDrOMw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.8.3.tgz", - "integrity": "sha512-NyaBbyLFXFLT9FP+zk0kYlUlA8XtCUbehs67F0nnEg7KICgMc2mNkIeu9TYhKzyXMkrapZFwAhXLdnt4IYHy1w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-WKK+9jz6TWUTX1uej9/EUVOmM1sK7aHv6bZyxbUV3NJjbiIZRqJITeXGMo7D631J72PEnIORh5VOlFCSlrLicg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.8.3.tgz", - "integrity": "sha512-KGhQNZ3TVCQG/MjRbAUwuH+14y9q0tpxs1nWWs3pbSleRdDro9SAMMDyye8HhY1gqZ7/NqIc8SKhya0wRDgP1Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-TS9MlfzXpXKt6YYomudb/KU7nQI6/xnapG6in1uZxoxDghuSMZsPb6D2fyUwNYSAp4l1iR7QtFOjkqcRYcUsfw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-8qvuPwU/xxUCt78HocNlv0mXXo0wdh9VT1R04WU8HGOfaOob26pF+9P5/lYjN/q7DHOX1bvX60hnhOvuQUJdbA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-0gkX7J7E+AtAw9fcwlVQj8peP61qhdg/89D5swOkjYbkboA2CVckn3kiyum1DE0wskGb7KJJxBdyEBApDLLVdw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.8.3.tgz", - "integrity": "sha512-QIoIR9abkVn+seDE3OjA08jWcs3eZ9+wJCKSRgo3WdEU2csFYgdScb+8qHB3+WXsGJD55u+5hWCISI7ejXS+kg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.8.3.tgz", - "integrity": "sha512-1/1/rEZv2XGweRwwSkLpY+s60za9OZ1hJs4YDqFHCw0kYWYwL5IFljVY1MYBL+weT1l9pokDO2uhSTLVxzoHkQ==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.8.3.tgz", - "integrity": "sha512-WxdW9xyLgBdefoo0Ynn3MRSkhe5tFVxxKNVdnZSh318WrG2e2jH+E9wd/++JsqcLJZPfz87njQJ8j2Upjm0M0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.8.3.tgz", - "integrity": "sha512-kwj1j9lL/6Wd0hROD3b/OZZ7MSrZLqqn9RAZ5+cYYsflQ9HZBIKCUkr3+uL1MEJ1NePiUbf98jjiMQSv0NMR9g==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.8.3.tgz", - "integrity": "sha512-0MRF+KC8EqH4dbuITCWwPSzsyO3HIWWlm30v8BbbpOrS1B++isGxPnnuq/IZvOX5J2D/p7DQalQm+/2PnlKGxg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.8.3.tgz", - "integrity": "sha512-imt9tFLD9ogt56Dd5CI/6XgpukMwd/fLGSrix2httihVe7LOGVPhyhMh1BU5kDM7iHD08i8uUtmV2sWaBFlHVQ==", - "requires": { - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-remap-async-to-generator": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.8.3.tgz", - "integrity": "sha512-vo4F2OewqjbB1+yaJ7k2EJFHlTP3jR634Z9Cj9itpqNjuLXvhlVxgnjsHsdRgASR8xYDrx6onw4vW5H6We0Jmg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.8.3.tgz", - "integrity": "sha512-pGnYfm7RNRgYRi7bids5bHluENHqJhrV4bCZRwc5GamaWIIs07N4rZECcmJL6ZClwjDz1GbdMZFtPs27hTB06w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "lodash": "^4.17.13" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.8.3.tgz", - "integrity": "sha512-SjT0cwFJ+7Rbr1vQsvphAHwUHvSUPmMjMU/0P59G8U2HLFqSa082JO7zkbDNWs9kH/IUqpHI6xWNesGf8haF1w==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-define-map": "^7.8.3", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-optimise-call-expression": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "globals": "^11.1.0" - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.8.3.tgz", - "integrity": "sha512-O5hiIpSyOGdrQZRQ2ccwtTVkgUDBBiCuK//4RJ6UfePllUTCENOzKxfh6ulckXKc0DixTFLCfb2HVkNA7aDpzA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.8.3.tgz", - "integrity": "sha512-H4X646nCkiEcHZUZaRkhE2XVsoz0J/1x3VVujnn96pSoGCtKPA99ZZA+va+gK+92Zycd6OBKCD8tDb/731bhgQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.8.3.tgz", - "integrity": "sha512-kLs1j9Nn4MQoBYdRXH6AeaXMbEJFaFu/v1nQkvib6QzTj8MZI5OQzqmD83/2jEM1z0DLilra5aWO5YpyC0ALIw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.8.3.tgz", - "integrity": "sha512-s8dHiBUbcbSgipS4SMFuWGqCvyge5V2ZeAWzR6INTVC3Ltjig/Vw1G2Gztv0vU/hRG9X8IvKvYdoksnUfgXOEQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.8.3.tgz", - "integrity": "sha512-zwIpuIymb3ACcInbksHaNcR12S++0MDLKkiqXHl3AzpgdKlFNhog+z/K0+TGW+b0w5pgTq4H6IwV/WhxbGYSjQ==", - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.8.4.tgz", - "integrity": "sha512-iAXNlOWvcYUYoV8YIxwS7TxGRJcxyl8eQCfT+A5j8sKUzRFvJdcyjp97jL2IghWSRDaL2PU2O2tX8Cu9dTBq5A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.8.3.tgz", - "integrity": "sha512-rO/OnDS78Eifbjn5Py9v8y0aR+aSYhDhqAwVfsTl0ERuMZyr05L1aFSCJnbv2mmsLkit/4ReeQ9N2BgLnOcPCQ==", - "requires": { - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.8.3.tgz", - "integrity": "sha512-3Tqf8JJ/qB7TeldGl+TT55+uQei9JfYaregDcEAyBZ7akutriFrt6C/wLYIer6OYhleVQvH/ntEhjE/xMmy10A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.8.3.tgz", - "integrity": "sha512-3Wk2EXhnw+rP+IDkK6BdtPKsUE5IeZ6QOGrPYvw52NwBStw9V1ZVzxgK6fSKSxqUvH9eQPR3tm3cOq79HlsKYA==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.8.3.tgz", - "integrity": "sha512-MadJiU3rLKclzT5kBH4yxdry96odTUwuqrZM+GllFI/VhxfPz+k9MshJM+MwhfkCdxxclSbSBbUGciBngR+kEQ==", - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.8.3.tgz", - "integrity": "sha512-JpdMEfA15HZ/1gNuB9XEDlZM1h/gF/YOH7zaZzQu2xCFRfwc01NXBMHHSTT6hRjlXJJs5x/bfODM3LiCk94Sxg==", - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-simple-access": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.8.3.tgz", - "integrity": "sha512-8cESMCJjmArMYqa9AO5YuMEkE4ds28tMpZcGZB/jl3n0ZzlsxOAi3mC+SKypTfT8gjMupCnd3YiXCkMjj2jfOg==", - "requires": { - "@babel/helper-hoist-variables": "^7.8.3", - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "babel-plugin-dynamic-import-node": "^2.3.0" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.8.3.tgz", - "integrity": "sha512-evhTyWhbwbI3/U6dZAnx/ePoV7H6OUG+OjiJFHmhr9FPn0VShjwC2kdxqIuQ/+1P50TMrneGzMeyMTFOjKSnAw==", - "requires": { - "@babel/helper-module-transforms": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.8.3.tgz", - "integrity": "sha512-f+tF/8UVPU86TrCb06JoPWIdDpTNSGGcAtaD9mLP0aYGA0OS0j7j7DHJR0GTFrUZPUU6loZhbsVZgTh0N+Qdnw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.8.3.tgz", - "integrity": "sha512-QuSGysibQpyxexRyui2vca+Cmbljo8bcRckgzYV4kRIsHpVeyeC3JDO63pY+xFZ6bWOBn7pfKZTqV4o/ix9sFw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.8.3.tgz", - "integrity": "sha512-57FXk+gItG/GejofIyLIgBKTas4+pEU47IXKDBWFTxdPd7F80H8zybyAY7UoblVfBhBGs2EKM+bJUu2+iUYPDQ==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-replace-supers": "^7.8.3" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.8.4.tgz", - "integrity": "sha512-IsS3oTxeTsZlE5KqzTbcC2sV0P9pXdec53SU+Yxv7o/6dvGM5AkTotQKhoSffhNgZ/dftsSiOoxy7evCYJXzVA==", - "requires": { - "@babel/helper-call-delegate": "^7.8.3", - "@babel/helper-get-function-arity": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.8.3.tgz", - "integrity": "sha512-uGiiXAZMqEoQhRWMK17VospMZh5sXWg+dlh2soffpkAl96KAm+WZuJfa6lcELotSRmooLqg0MWdH6UUq85nmmg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.8.3.tgz", - "integrity": "sha512-r0h+mUiyL595ikykci+fbwm9YzmuOrUBi0b+FDIKmi3fPQyFokWVEMJnRWHJPPQEjyFJyna9WZC6Viv6UHSv1g==", - "requires": { - "@babel/helper-builder-react-jsx": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.8.3" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.8.3.tgz", - "integrity": "sha512-qt/kcur/FxrQrzFR432FGZznkVAjiyFtCOANjkAKwCbt465L6ZCiUQh2oMYGU3Wo8LRFJxNDFwWn106S5wVUNA==", - "requires": { - "regenerator-transform": "^0.14.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.8.3.tgz", - "integrity": "sha512-mwMxcycN3omKFDjDQUl+8zyMsBfjRFr0Zn/64I41pmjv4NJuqcYlEtezwYtw9TFd9WR1vN5kiM+O0gMZzO6L0A==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.8.3.tgz", - "integrity": "sha512-I9DI6Odg0JJwxCHzbzW08ggMdCezoWcuQRz3ptdudgwaHxTjxw5HgdFJmZIkIMlRymL6YiZcped4TTCB0JcC8w==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.8.3.tgz", - "integrity": "sha512-CkuTU9mbmAoFOI1tklFWYYbzX5qCIZVXPVy0jpXgGwkplCndQAa58s2jr66fTeQnA64bDox0HL4U56CFYoyC7g==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.8.3.tgz", - "integrity": "sha512-9Spq0vGCD5Bb4Z/ZXXSK5wbbLFMG085qd2vhL1JYu1WcQ5bXqZBAYRzU1d+p79GcHs2szYv5pVQCX13QgldaWw==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/helper-regex": "^7.8.3" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.8.3.tgz", - "integrity": "sha512-820QBtykIQOLFT8NZOcTRJ1UNuztIELe4p9DCgvj4NK+PwluSJ49we7s9FB1HIGNIYT7wFUJ0ar2QpCDj0escQ==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.8.4.tgz", - "integrity": "sha512-2QKyfjGdvuNfHsb7qnBBlKclbD4CfshH2KvDabiijLMGXPHJXGxtDzwIF7bQP+T0ysw8fYTtxPafgfs/c1Lrqg==", - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.8.3.tgz", - "integrity": "sha512-+ufgJjYdmWfSQ+6NS9VGUR2ns8cjJjYbrbi11mZBTaWm+Fui/ncTLFF28Ei1okavY+xkojGr1eJxNsWYeA5aZw==", - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/preset-env": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.8.4.tgz", - "integrity": "sha512-HihCgpr45AnSOHRbS5cWNTINs0TwaR8BS8xIIH+QwiW8cKL0llV91njQMpeMReEPVs+1Ao0x3RLEBLtt1hOq4w==", - "requires": { - "@babel/compat-data": "^7.8.4", - "@babel/helper-compilation-targets": "^7.8.4", - "@babel/helper-module-imports": "^7.8.3", - "@babel/helper-plugin-utils": "^7.8.3", - "@babel/plugin-proposal-async-generator-functions": "^7.8.3", - "@babel/plugin-proposal-dynamic-import": "^7.8.3", - "@babel/plugin-proposal-json-strings": "^7.8.3", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.8.3", - "@babel/plugin-proposal-optional-catch-binding": "^7.8.3", - "@babel/plugin-proposal-optional-chaining": "^7.8.3", - "@babel/plugin-proposal-unicode-property-regex": "^7.8.3", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.8.3", - "@babel/plugin-transform-arrow-functions": "^7.8.3", - "@babel/plugin-transform-async-to-generator": "^7.8.3", - "@babel/plugin-transform-block-scoped-functions": "^7.8.3", - "@babel/plugin-transform-block-scoping": "^7.8.3", - "@babel/plugin-transform-classes": "^7.8.3", - "@babel/plugin-transform-computed-properties": "^7.8.3", - "@babel/plugin-transform-destructuring": "^7.8.3", - "@babel/plugin-transform-dotall-regex": "^7.8.3", - "@babel/plugin-transform-duplicate-keys": "^7.8.3", - "@babel/plugin-transform-exponentiation-operator": "^7.8.3", - "@babel/plugin-transform-for-of": "^7.8.4", - "@babel/plugin-transform-function-name": "^7.8.3", - "@babel/plugin-transform-literals": "^7.8.3", - "@babel/plugin-transform-member-expression-literals": "^7.8.3", - "@babel/plugin-transform-modules-amd": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.8.3", - "@babel/plugin-transform-modules-systemjs": "^7.8.3", - "@babel/plugin-transform-modules-umd": "^7.8.3", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.8.3", - "@babel/plugin-transform-new-target": "^7.8.3", - "@babel/plugin-transform-object-super": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.8.4", - "@babel/plugin-transform-property-literals": "^7.8.3", - "@babel/plugin-transform-regenerator": "^7.8.3", - "@babel/plugin-transform-reserved-words": "^7.8.3", - "@babel/plugin-transform-shorthand-properties": "^7.8.3", - "@babel/plugin-transform-spread": "^7.8.3", - "@babel/plugin-transform-sticky-regex": "^7.8.3", - "@babel/plugin-transform-template-literals": "^7.8.3", - "@babel/plugin-transform-typeof-symbol": "^7.8.4", - "@babel/plugin-transform-unicode-regex": "^7.8.3", - "@babel/types": "^7.8.3", - "browserslist": "^4.8.5", - "core-js-compat": "^3.6.2", - "invariant": "^2.2.2", - "levenary": "^1.1.1", - "semver": "^5.5.0" - } - }, - "@babel/template": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", - "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/parser": "^7.8.3", - "@babel/types": "^7.8.3" - } - }, - "@babel/traverse": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", - "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", - "requires": { - "@babel/code-frame": "^7.8.3", - "@babel/generator": "^7.8.4", - "@babel/helper-function-name": "^7.8.3", - "@babel/helper-split-export-declaration": "^7.8.3", - "@babel/parser": "^7.8.4", - "@babel/types": "^7.8.3", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.13" - } - }, - "@babel/types": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", - "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", - "requires": { - "esutils": "^2.0.2", - "lodash": "^4.17.13", - "to-fast-properties": "^2.0.0" + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" } }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", - "dev": true - }, - "@magic-libraries/db": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@magic-libraries/db/-/db-0.0.4.tgz", - "integrity": "sha512-/P0b2jgllWMTnVVUCKprIdbnofNzUE9xZEDjxavyOObNLvjCBFsddtf69ZHyxEZxUaU8s/yHMiFxfzN71dYNwA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "requires": { - "@magic-libraries/json": "0.0.2" + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "@magic-libraries/json": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@magic-libraries/json/-/json-0.0.2.tgz", - "integrity": "sha512-HOYyaJ7/YlEq88XyDF+iQlTOrAKRPn2RIfm/Km+xwkKwz7ZZ1v8NvtVfKNHc2T8z/KxVTnlr7TUQVTqbopWBug==", - "dev": true - }, - "@magic-libraries/prevent-default": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@magic-libraries/prevent-default/-/prevent-default-0.0.3.tgz", - "integrity": "sha512-8w7sivG89PJtBSaWih77zPdtmE3QPWnT80GVPiXLK4uk8G7gLJovVQYLEa1ruxtkk1roq5dgNu2FSxS2jWRqlQ==" - }, - "@magic-modules/gdpr": { - "version": "0.0.14", - "resolved": "https://registry.npmjs.org/@magic-modules/gdpr/-/gdpr-0.0.14.tgz", - "integrity": "sha512-tD+39HPyzjW5lgDJyqNfkG9s/lWGvCuRjRx97he7eZbAXOWXIV+wtZxf7Pu5PjC1ejgCR23BLSolfwHjzGzN+A==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "requires": { - "@magic-libraries/db": "0.0.4" + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "@magic-modules/git-badges": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@magic-modules/git-badges/-/git-badges-0.0.4.tgz", - "integrity": "sha512-nthjZ9BBvBam+wA3xXnegLRBVGjuifgRSitZtckKrs2104qkvkdGXxeXri2MIsuEnKA97KDA5XNgGlTfzLpMMw==", - "dev": true + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } }, - "@magic-modules/git-list": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@magic-modules/git-list/-/git-list-0.0.3.tgz", - "integrity": "sha512-AUGt89WYAgHvNA5RlnKPyZ3QlhVKHOuQ1STEME6RIYdapvczd+5B/AFdGRjKOv7IyJP+7QKL/WVCH6XR5RXpTg==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, - "requires": { - "@magic-modules/git-badges": "0.0.3" - }, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@magic-modules/git-badges": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@magic-modules/git-badges/-/git-badges-0.0.3.tgz", - "integrity": "sha512-AwJFbLDJCxWizaz3lD+mWnFJcBUiOSdxkjm6r8ytsE6aeQst4dEJpjyOlweIJZDwMQXnQiM6KKHODDGIzye5fA==", - "dev": true - } + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "@magic-modules/library-list": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@magic-modules/library-list/-/library-list-0.0.7.tgz", - "integrity": "sha512-slseeo2+RXqOM4u9L0f6ROqo/2Ut3lyucg51gmFpIPzbf05qXlXPVvC9/Ebs+0mPGEoXJ5ZSF49DVs6dABPAYQ==", + "node_modules/@eslint/config-helpers": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", + "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", "dev": true, - "requires": { - "@magic-modules/git-list": "0.0.3" + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "@magic-modules/light-switch": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@magic-modules/light-switch/-/light-switch-0.0.5.tgz", - "integrity": "sha512-Hn6eCrvHtWsWSfscfqAD2hpU9fO+aperw3asqt/HP5VOLvVNKrp/0Jc4CTGMOFYfr5+jBsPL8jfXZdU2hM32Fw==", - "dev": true - }, - "@magic-modules/module-list": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@magic-modules/module-list/-/module-list-0.0.8.tgz", - "integrity": "sha512-O8AmwZsREyXYwbQqif9mDgVrQxTwkzypVAXOk5/6uf51yaLg2Upbrl+y4Y90GCqrJ1N0SZSmP4+7ixrcC+F9oQ==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, - "requires": { - "@magic-modules/git-list": "0.0.3" + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "@magic-modules/pre": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@magic-modules/pre/-/pre-0.0.2.tgz", - "integrity": "sha512-ADvevQj5Y2TuiNmdgw1aESOZhVWSSTsk/0rh/4piiFTI+ccpDYDNuzDE9fG3Lu4FtYspYDbX6o5vpMS3l+zLcg==", - "dev": true - }, - "@magic-modules/theme-list": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@magic-modules/theme-list/-/theme-list-0.0.5.tgz", - "integrity": "sha512-VnYkM2IDk7Z3ac9fFeYP8XR+aR4/+1TzXb45gf4tdg2TuINtc+y0b67bq15+R6VLunGiFTnCEWudoW8950gsPA==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, - "requires": { - "@magic-modules/git-list": "0.0.3" + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "@magic-themes/docs": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/@magic-themes/docs/-/docs-0.0.6.tgz", - "integrity": "sha512-duEW8w2u4Ii1GwUmptuUAASi/G1JY7rD9gGyhHbcCfKHo7LPAY4JCEwE7phfM8lekcb9/7kSNklG07fHvZwJHQ==", - "dev": true - }, - "@magic/cases": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@magic/cases/-/cases-0.0.2.tgz", - "integrity": "sha512-Llq+dV01hkzRi8FfnZg8lBIZN6RDkSMI7nlH+Wh/bjPUKOqyds/s8YyY2R0+9GAKzS7sIkhjWXBplaH2lZAfrQ==" - }, - "@magic/cli": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/@magic/cli/-/cli-0.0.23.tgz", - "integrity": "sha512-/GAZ9M7gVr1c5FX8dnqbY4BXtK7mBRD18pNbsm4uULrW50eSr3fJ5LSCnd9QBcA8N7VdVC+yaOOTc/YxdY5LmQ==", - "requires": { - "@magic/cases": "0.0.2", - "@magic/log": "0.1.3", - "@magic/types": "0.1.11" - }, - "dependencies": { - "@magic/log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.3.tgz", - "integrity": "sha512-bznU3AFIlNLlSk5HL+uOoJhIGgL2cabyH6lRGRydT+qf2ovCDQueJDPjUhAiMa13Mx7gIy13q2tRCspTbCrKJw==", - "requires": { - "@magic/types": "0.1.7" - }, - "dependencies": { - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==" - } - } - } + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "@magic/css": { - "version": "0.7.9", - "resolved": "https://registry.npmjs.org/@magic/css/-/css-0.7.9.tgz", - "integrity": "sha512-2MoHblZYgpvRw886PDpq6t27aQ0y+r9xXS6WK6PgPRY6Rl+L2Y8Y8SqwF+CGUO8clbZ/l9pJgCiwCgvjAwDOBw==", - "requires": { - "@magic/deep": "0.1.2", - "@magic/log": "0.1.3", - "@magic/types": "0.1.7", - "autoprefixer": "9.7.3", - "postcss": "7.0.26" - }, - "dependencies": { - "@magic/log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.3.tgz", - "integrity": "sha512-bznU3AFIlNLlSk5HL+uOoJhIGgL2cabyH6lRGRydT+qf2ovCDQueJDPjUhAiMa13Mx7gIy13q2tRCspTbCrKJw==", - "requires": { - "@magic/types": "0.1.7" - } - }, - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==" - } + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "@magic/deep": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@magic/deep/-/deep-0.1.2.tgz", - "integrity": "sha512-k41LYCC5oTkZztyh/Ewtf2c7Sph5Rl1iv8L+h7Fgog5Ve7FZFF7kExPDk+m/Za6Ev93FqpwlJ4YqrL5y3XUDFg==", - "requires": { - "@magic/types": "0.1.7" - }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==" - } + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "@magic/error": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@magic/error/-/error-0.0.5.tgz", - "integrity": "sha512-YxDkh4CyfiYYxB7TYcHTqacoKq4XSrhLSGNLTH/NAi9Eo/oYNesCZpScvMLGqebHQ6vipRh5RzsqVy1+gVNgMA==", - "requires": { - "@magic/types": "0.1.11" - } - }, - "@magic/format": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/@magic/format/-/format-0.0.7.tgz", - "integrity": "sha512-UEP4Q8BKjVdN3NYRK50+NGlr0mB2QvHScIlN33i9TTLQzLmmG65UyzByWlICTvvR/lalOzjIn1viedNWih6GpA==", - "dev": true, - "requires": { - "@magic/cli": "0.0.16", - "@magic/deep": "0.1.2", - "@magic/fs": "0.0.2", - "@magic/log": "0.1.3", - "@magic/types": "0.1.7", - "prettier": "1.19.1" - }, - "dependencies": { - "@magic/cli": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/@magic/cli/-/cli-0.0.16.tgz", - "integrity": "sha512-W/AbMVSwZy2y1uthxoWxMOtEHpiCMPaCCSKQo1SaoFdzBKq50iM4ocSyXVOifxYUztVHpwCi3k6IapVaQmWwEA==", - "dev": true, - "requires": { - "@magic/cases": "0.0.2", - "@magic/log": "0.1.3", - "@magic/types": "0.1.7" - } - }, - "@magic/fs": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.2.tgz", - "integrity": "sha512-tI4BYyG1qyV3vOpjCDmVe5/DOzefjleTSjbhJWaSA9Qlq4fJ12EGh93pOHMmZpEHJgzgmP3MDEhVF4PD+w28Gg==", - "dev": true, - "requires": { - "@magic/deep": "0.1.2", - "@magic/types": "0.1.7" - } - }, - "@magic/log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.3.tgz", - "integrity": "sha512-bznU3AFIlNLlSk5HL+uOoJhIGgL2cabyH6lRGRydT+qf2ovCDQueJDPjUhAiMa13Mx7gIy13q2tRCspTbCrKJw==", - "dev": true, - "requires": { - "@magic/types": "0.1.7" - } - }, - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==", - "dev": true - } + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" } }, - "@magic/fs": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.4.tgz", - "integrity": "sha512-s8U4Ehl3nOse+RQYj6KCU+rM2sV97TiCgEs02YKCkORt3FCzvlAXu52fgfE+aCxhjbUqpzmW4uZ6aeDpqN/B9Q==", - "requires": { - "@magic/deep": "0.1.2", - "@magic/error": "0.0.5", - "@magic/types": "0.1.11" + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "@magic/hyperapp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@magic/hyperapp/-/hyperapp-2.0.3.tgz", - "integrity": "sha512-esnynEeYD6xtBKdY6ZvasV0vQuhVSfATpzKwXKsMZo+L6hkSr3sZ0rrYDAkeqqR69EO6FyltdTUDswX9AqHz2A==" - }, - "@magic/log": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.4.tgz", - "integrity": "sha512-GUiOKQ/L3hnMEYtuYwQ/bjQpVDFa0QReVOwenJVqSJcPXlEZTF7fHoiXHQYcvQ22KduY4AJSxq5f58JvQZaKmg==", - "requires": { - "@magic/types": "0.1.11" - } - }, - "@magic/tags": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@magic/tags/-/tags-0.0.3.tgz", - "integrity": "sha512-VBlJulSS2XUT8OKMVURXN6SdvS465kQ9klZhdmsb+EWcwg5OJtIbTkhUOMQfZIJCxenh6XCyMYDQxjWheI0gUg==" - }, - "@magic/test": { - "version": "0.1.35", - "resolved": "https://registry.npmjs.org/@magic/test/-/test-0.1.35.tgz", - "integrity": "sha512-AEUkDRDqMJsFlrzn2JHSXwHw5mfKp59w2r8yGbQOY7gOgqRQiaO0CkQ3yYu0tT9qsI7vRWE5I9b9DRzuheTrDQ==", - "dev": true, - "requires": { - "@magic/cli": "0.0.23", - "@magic/css": "0.7.9", - "@magic/deep": "0.1.2", - "@magic/fs": "0.0.4", - "@magic/hyperapp": "2.0.3", - "@magic/log": "0.1.3", - "@magic/types": "0.1.11", - "c8": "7.1.0", - "coveralls": "3.0.9" - }, - "dependencies": { - "@magic/log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.3.tgz", - "integrity": "sha512-bznU3AFIlNLlSk5HL+uOoJhIGgL2cabyH6lRGRydT+qf2ovCDQueJDPjUhAiMa13Mx7gIy13q2tRCspTbCrKJw==", - "dev": true, - "requires": { - "@magic/types": "0.1.7" - }, - "dependencies": { - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==", - "dev": true - } - } - } + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "@magic/transmute": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@magic/transmute/-/transmute-0.0.8.tgz", - "integrity": "sha512-MVGjcirE12rMWvfcmFA2g2px3ml6E1dZk+mP39xnkBfjDGsmSMw6A2EzX2GDJfEmBqN+vwJRVU3dpujms41Wsw==", - "requires": { - "@magic/cases": "0.0.2", - "@magic/cli": "0.0.15", - "@magic/log": "0.1.3", - "marked": "0.8.0", - "parse5": "5.1.1", - "prettier": "1.19.1" - }, - "dependencies": { - "@magic/cli": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/@magic/cli/-/cli-0.0.15.tgz", - "integrity": "sha512-t/eRVbz7c4jpcgzFNtwnrBgF67x5Ufxn0N+Hoa8igobPHG9+DotGXlP65JUdv7QFbweCopmJM2gtSfHhZMqf+g==", - "requires": { - "@magic/cases": "0.0.2", - "@magic/log": "0.1.3", - "@magic/types": "0.1.7" - } - }, - "@magic/log": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.3.tgz", - "integrity": "sha512-bznU3AFIlNLlSk5HL+uOoJhIGgL2cabyH6lRGRydT+qf2ovCDQueJDPjUhAiMa13Mx7gIy13q2tRCspTbCrKJw==", - "requires": { - "@magic/types": "0.1.7" - } - }, - "@magic/types": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.7.tgz", - "integrity": "sha512-FTDPXIad3aCUHocKOQdxfzAOXp1tifhmZWYsAuneE2N/p7pXUxbj+jfrl7RUZQ1wIIB7/qnGeBsEwZtpNCm+Fw==" - } + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "@magic/types": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.11.tgz", - "integrity": "sha512-4eateLebE9Txx4gPjUMpDkCDzrECfxPqAWMgim1kADETPnGAgRTHWbMMCtDZGpp48Jj/5sln0iXqp5GrJZH5EA==" - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/is-windows": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/is-windows/-/is-windows-1.0.0.tgz", - "integrity": "sha512-tJ1rq04tGKuIJoWIH0Gyuwv4RQ3+tIu7wQrC0MV47raQ44kIzXSSFKfrxFUOWVRvesoF7mrTqigXmqoZJsXwTg==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", - "dev": true + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } }, - "@types/node": { - "version": "13.7.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.7.4.tgz", - "integrity": "sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==" + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, - "ajv": { - "version": "6.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", - "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" + "node_modules/@magic-libraries/prevent-default": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@magic-libraries/prevent-default/-/prevent-default-0.0.6.tgz", + "integrity": "sha512-5RhdExB5+oTyy+dN/6pYJnHjvFE5LMW4YZN8b3EFy7FF653aG9bL8Jla4NTTv0i0jWU3+o01RlhYZLAJ0dZBQQ==", + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/@magic-modules/git-badges": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@magic-modules/git-badges/-/git-badges-0.0.12.tgz", + "integrity": "sha512-0EYk39XuCQNxUGxpyIbd40UBLDLeEG8Pmp+0x1ECPrWAZbKvWnPOcttr48IhErI6u/aLxGQCs7PZFzNnAtS2lQ==", "dev": true, - "requires": { - "sprintf-js": "~1.0.2" + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "node_modules/@magic-modules/git-list": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@magic-modules/git-list/-/git-list-0.0.13.tgz", + "integrity": "sha512-K+J+N8H9i+I+cDl4tWUKUGyR/jiigaXw+es7w4FbG8L4ds9rFBxf5BeS9Em9ViBjyV5aeyJ8MiScZ+XuS9IMYg==", "dev": true, - "requires": { - "safer-buffer": "~2.1.0" + "license": "AGPL-3.0", + "dependencies": { + "@magic-modules/git-badges": "0.0.11" + }, + "engines": { + "node": ">=14.15.4" } }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "autoprefixer": { - "version": "9.7.3", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.3.tgz", - "integrity": "sha512-8T5Y1C5Iyj6PgkPSFd0ODvK9DIleuPKUPYniNxybS47g2k2wFgLZ46lGQHlBuGKIAEV8fbCDfKCCRS1tvOgc3Q==", - "requires": { - "browserslist": "^4.8.0", - "caniuse-lite": "^1.0.30001012", - "chalk": "^2.4.2", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^7.0.23", - "postcss-value-parser": "^4.0.2" - } - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", - "dev": true - }, - "babel-helper-evaluate-path": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-helper-evaluate-path/-/babel-helper-evaluate-path-0.5.0.tgz", - "integrity": "sha512-mUh0UhS607bGh5wUMAQfOpt2JX2ThXMtppHRdRU1kL7ZLRWIXxoV2UIV1r2cAeeNeU1M5SB5/RSUgUxrK8yOkA==" - }, - "babel-helper-flip-expressions": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-flip-expressions/-/babel-helper-flip-expressions-0.4.3.tgz", - "integrity": "sha1-NpZzahKKwYvCUlS19AoizrPB0/0=" - }, - "babel-helper-is-nodes-equiv": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/babel-helper-is-nodes-equiv/-/babel-helper-is-nodes-equiv-0.0.1.tgz", - "integrity": "sha1-NOmzALFHnd2Y7HfqC76TQt/jloQ=" + "node_modules/@magic-modules/git-list/node_modules/@magic-modules/git-badges": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@magic-modules/git-badges/-/git-badges-0.0.11.tgz", + "integrity": "sha512-lAlEEw+o20dNzONRJaoHEanWdND3H3RRfEZv40g/j2GCkrZZrsIHcYUfN1thgJgbis2STVybJWFRk9N6OiHstw==", + "dev": true, + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" + } }, - "babel-helper-is-void-0": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-is-void-0/-/babel-helper-is-void-0-0.4.3.tgz", - "integrity": "sha1-fZwBtFYee5Xb2g9u7kj1tg5nMT4=" + "node_modules/@magic-modules/library-list": { + "version": "0.0.18", + "resolved": "https://registry.npmjs.org/@magic-modules/library-list/-/library-list-0.0.18.tgz", + "integrity": "sha512-4vAZXlqXspR+mprYqLpIRuXZZJ3Md1B1m3KWq8BngViVBgS7HxKLTBITpJRT4Ax6eyHI5S3NLun8RXtnLyW/2Q==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic-modules/git-list": "0.0.13" + }, + "engines": { + "node": ">=14.15.4" + } }, - "babel-helper-mark-eval-scopes": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-mark-eval-scopes/-/babel-helper-mark-eval-scopes-0.4.3.tgz", - "integrity": "sha1-0kSjvvmESHJgP/tG4izorN9VFWI=" + "node_modules/@magic-modules/light-switch": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@magic-modules/light-switch/-/light-switch-0.0.12.tgz", + "integrity": "sha512-n+liMgsZS/0wVoj3zHdT+ZApmC0NAt3pB2oh9QIbIXuf5E2oJ5r6zqWbp+1MOxxA0px9HqAMKeaVF2RQP8fISw==", + "dev": true, + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" + } }, - "babel-helper-remove-or-void": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-helper-remove-or-void/-/babel-helper-remove-or-void-0.4.3.tgz", - "integrity": "sha1-pPA7QAd6D/6I5F0HAQ3uJB/1rmA=" + "node_modules/@magic-modules/module-list": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@magic-modules/module-list/-/module-list-0.0.22.tgz", + "integrity": "sha512-iABVVX67xphU/0pHJ36zO1UNkMp1dVkETyv+1mrFpNbtffxRXmZPZYLGHbAni9hya46eeKx9EcKv11ODHiHZAA==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic-modules/git-list": "0.0.13" + } }, - "babel-helper-to-multiple-sequence-expressions": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz", - "integrity": "sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA==" + "node_modules/@magic-modules/no-spy": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/@magic-modules/no-spy/-/no-spy-0.0.9.tgz", + "integrity": "sha512-qyrP0w1JjgCLs+Lv1ZjeTsndLgQ1058edGZk4LuwuzmVKZJSdpjIXxUHvKMkLMjCDE40tthUVZpFhLUz13xxWA==", + "dev": true, + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" + } }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz", - "integrity": "sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==", - "requires": { - "object.assign": "^4.1.0" + "node_modules/@magic-modules/pre": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@magic-modules/pre/-/pre-0.0.12.tgz", + "integrity": "sha512-schdCPbNoyZuygMCAfkjM9lNKJyZyW/W8VTS2by8Mee9HZ6vA3D32zvkKH7UOn0OnzL3wqfRchseYmV5OG6gsw==", + "dev": true, + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "babel-plugin-minify-builtins": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-builtins/-/babel-plugin-minify-builtins-0.5.0.tgz", - "integrity": "sha512-wpqbN7Ov5hsNwGdzuzvFcjgRlzbIeVv1gMIlICbPj0xkexnfoIDe7q+AZHMkQmAE/F9R5jkrB6TLfTegImlXag==" + "node_modules/@magic-modules/theme-list": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@magic-modules/theme-list/-/theme-list-0.0.14.tgz", + "integrity": "sha512-9+DbSMvWB3d/y7Imo9YUoaJA61v9AE15AdivZXAGzhQUgjqbWTE/R3o3+BfmTMPgD2IbBFhi9W53qUVJXLj7EA==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic-modules/git-list": "0.0.13" + }, + "engines": { + "node": ">=14.15.4" + } }, - "babel-plugin-minify-constant-folding": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-constant-folding/-/babel-plugin-minify-constant-folding-0.5.0.tgz", - "integrity": "sha512-Vj97CTn/lE9hR1D+jKUeHfNy+m1baNiJ1wJvoGyOBUx7F7kJqDZxr9nCHjO/Ad+irbR3HzR6jABpSSA29QsrXQ==", - "requires": { - "babel-helper-evaluate-path": "^0.5.0" + "node_modules/@magic-themes/docs": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@magic-themes/docs/-/docs-0.0.15.tgz", + "integrity": "sha512-iTdxW/ckVOsuBL6gbkMmNXUeFUOa3ZVz15hSUTDcCz3+mNlca5q9iG1JMicrH5PaPNyEuM6DtidsMqWDkXwVhg==", + "dev": true, + "license": "AGPL-3.0", + "engines": { + "node": ">=14.2.0" } }, - "babel-plugin-minify-dead-code-elimination": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-dead-code-elimination/-/babel-plugin-minify-dead-code-elimination-0.5.1.tgz", - "integrity": "sha512-x8OJOZIrRmQBcSqxBcLbMIK8uPmTvNWPXH2bh5MDCW1latEqYiRMuUkPImKcfpo59pTUB2FT7HfcgtG8ZlR5Qg==", - "requires": { - "babel-helper-evaluate-path": "^0.5.0", - "babel-helper-mark-eval-scopes": "^0.4.3", - "babel-helper-remove-or-void": "^0.4.3", - "lodash": "^4.17.11" + "node_modules/@magic/cases": { + "version": "0.0.13", + "resolved": "https://registry.npmjs.org/@magic/cases/-/cases-0.0.13.tgz", + "integrity": "sha512-CPsO00wIMr543Jy6DvM/oQPdEs2Rtq6G4wN8vfyx//wfqrQkg+3dSK+WC+dgIjUO5uSvQ5XQFIar8UAwRMN6dg==", + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "babel-plugin-minify-flip-comparisons": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-flip-comparisons/-/babel-plugin-minify-flip-comparisons-0.4.3.tgz", - "integrity": "sha1-AMqHDLjxO0XAOLPB68DyJyk8llo=", - "requires": { - "babel-helper-is-void-0": "^0.4.3" + "node_modules/@magic/cli": { + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/@magic/cli/-/cli-0.0.54.tgz", + "integrity": "sha512-S7HBxtTvKdy8YrR61wvWHbeAoy4C72t/GoancI5t5UWAJA2aZNpbHCfFc9vkub6Fi6/7iWZUaAnCbaKoz/sG4w==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/cases": "0.0.13", + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/log": "0.1.22", + "@magic/types": "0.1.37" + }, + "engines": { + "node": ">=14.15.4" } }, - "babel-plugin-minify-guarded-expressions": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-guarded-expressions/-/babel-plugin-minify-guarded-expressions-0.4.4.tgz", - "integrity": "sha512-RMv0tM72YuPPfLT9QLr3ix9nwUIq+sHT6z8Iu3sLbqldzC1Dls8DPCywzUIzkTx9Zh1hWX4q/m9BPoPed9GOfA==", - "requires": { - "babel-helper-evaluate-path": "^0.5.0", - "babel-helper-flip-expressions": "^0.4.3" + "node_modules/@magic/css": { + "version": "0.7.59", + "resolved": "https://registry.npmjs.org/@magic/css/-/css-0.7.59.tgz", + "integrity": "sha512-S5Nkc0759rVa3lmUPMLPLi9oE7bJuAa1jSyxFjDZWE0uTkvzNgi/uXoCgsC35OWSnjRSzGqUg8Klv22aBWBhFA==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/cases": "0.0.13", + "@magic/deep": "0.1.21", + "@magic/fs": "0.0.38", + "@magic/log": "0.1.22", + "@magic/types": "0.1.37", + "autoprefixer": "10.5.0", + "postcss": "8.5.15" + }, + "bin": { + "magic-css": "src/bin/index.js", + "mcss": "src/bin/index.js" + }, + "engines": { + "node": ">=14.15.4" } }, - "babel-plugin-minify-infinity": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-infinity/-/babel-plugin-minify-infinity-0.4.3.tgz", - "integrity": "sha1-37h2obCKBldjhO8/kuZTumB7Oco=" + "node_modules/@magic/css/node_modules/@magic/fs": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.38.tgz", + "integrity": "sha512-N+2RRFFHx/qL/DWk/IxkdgX4xFBmPH9+4AXlKRMU37dZ2oo3oQ4FCchfrv5rE0oEsDrcqgAn+9oGVKplgTFplw==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/log": "0.1.22", + "@magic/mime-types": "0.0.23", + "@magic/types": "0.1.37" + }, + "engines": { + "node": ">=14.15.4" + } }, - "babel-plugin-minify-mangle-names": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-mangle-names/-/babel-plugin-minify-mangle-names-0.5.0.tgz", - "integrity": "sha512-3jdNv6hCAw6fsX1p2wBGPfWuK69sfOjfd3zjUXkbq8McbohWy23tpXfy5RnToYWggvqzuMOwlId1PhyHOfgnGw==", - "requires": { - "babel-helper-mark-eval-scopes": "^0.4.3" + "node_modules/@magic/deep": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@magic/deep/-/deep-0.1.21.tgz", + "integrity": "sha512-yX051g+LN02NGSCHuaANbJqDYg2M5yVd5vgS84mtl+KWHqsBJE2TB3/DLIR/5pNPqi67WRrouMHzRkCghC6Hmw==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.37" } }, - "babel-plugin-minify-numeric-literals": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-numeric-literals/-/babel-plugin-minify-numeric-literals-0.4.3.tgz", - "integrity": "sha1-jk/VYcefeAEob/YOjF/Z3u6TwLw=" - }, - "babel-plugin-minify-replace": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-replace/-/babel-plugin-minify-replace-0.5.0.tgz", - "integrity": "sha512-aXZiaqWDNUbyNNNpWs/8NyST+oU7QTpK7J9zFEFSA0eOmtUNMU3fczlTTTlnCxHmq/jYNFEmkkSG3DDBtW3Y4Q==" - }, - "babel-plugin-minify-simplify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-simplify/-/babel-plugin-minify-simplify-0.5.1.tgz", - "integrity": "sha512-OSYDSnoCxP2cYDMk9gxNAed6uJDiDz65zgL6h8d3tm8qXIagWGMLWhqysT6DY3Vs7Fgq7YUDcjOomhVUb+xX6A==", - "requires": { - "babel-helper-evaluate-path": "^0.5.0", - "babel-helper-flip-expressions": "^0.4.3", - "babel-helper-is-nodes-equiv": "^0.0.1", - "babel-helper-to-multiple-sequence-expressions": "^0.5.0" - } - }, - "babel-plugin-minify-type-constructors": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-minify-type-constructors/-/babel-plugin-minify-type-constructors-0.4.3.tgz", - "integrity": "sha1-G8bxW4f3qxCF1CszC3F2V6IVZQA=", - "requires": { - "babel-helper-is-void-0": "^0.4.3" + "node_modules/@magic/entities": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/@magic/entities/-/entities-0.0.17.tgz", + "integrity": "sha512-Utm6OwjYLE7GqQsWJf4lmKNenCSkhRH56j4IO8Wbim9lskBG3f4jjOs66KW0UbT7YyOZ2GNfWZIOaypi9pqkMw==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.37" + }, + "engines": { + "node": ">=14.15.4" } }, - "babel-plugin-remove-code": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/babel-plugin-remove-code/-/babel-plugin-remove-code-0.0.6.tgz", - "integrity": "sha1-dCtujmQpEz6tIKvDUR3Uou6+UIU=" + "node_modules/@magic/error": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@magic/error/-/error-0.0.23.tgz", + "integrity": "sha512-yJ8uM0OMGXxc6lcnsyYNPoSJQ9fq6b1rGsvhd3waGVpmxbyliB6jLaa99OgAsIwKKIWAVRhS+yw6N3z1w9BIvw==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.37" + } }, - "babel-plugin-transform-inline-consecutive-adds": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-inline-consecutive-adds/-/babel-plugin-transform-inline-consecutive-adds-0.4.3.tgz", - "integrity": "sha1-Mj1Ho+pjqDp6w8gRro5pQfrysNE=" + "node_modules/@magic/format": { + "version": "0.0.74", + "resolved": "https://registry.npmjs.org/@magic/format/-/format-0.0.74.tgz", + "integrity": "sha512-5/T2PDjpuNRjOAXGX/ZHSUmb4By7bgt+EErVAVIMnKXHTJyX1EQFtFCMIJqiz86FkKMF2pC5qi/iceZX637BmQ==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/cli": "0.0.54", + "@magic/deep": "0.1.21", + "@magic/fs": "0.0.38", + "@magic/log": "0.1.22", + "@magic/types": "0.1.37", + "prettier": "3.8.3" + }, + "bin": { + "f": "src/bin.mjs", + "format": "src/bin.mjs" + }, + "engines": { + "node": ">=14.15.4" + } }, - "babel-plugin-transform-member-expression-literals": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-member-expression-literals/-/babel-plugin-transform-member-expression-literals-6.9.4.tgz", - "integrity": "sha1-NwOcmgwzE6OUlfqsL/OmtbnQOL8=" + "node_modules/@magic/format/node_modules/@magic/fs": { + "version": "0.0.38", + "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.38.tgz", + "integrity": "sha512-N+2RRFFHx/qL/DWk/IxkdgX4xFBmPH9+4AXlKRMU37dZ2oo3oQ4FCchfrv5rE0oEsDrcqgAn+9oGVKplgTFplw==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/log": "0.1.22", + "@magic/mime-types": "0.0.23", + "@magic/types": "0.1.37" + }, + "engines": { + "node": ">=14.15.4" + } }, - "babel-plugin-transform-merge-sibling-variables": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-merge-sibling-variables/-/babel-plugin-transform-merge-sibling-variables-6.9.4.tgz", - "integrity": "sha1-hbQi/DN3tEnJ0c3kQIcgNTJAHa4=" + "node_modules/@magic/fs": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.39.tgz", + "integrity": "sha512-di2r1uxvdFnF0kyB9TraKp6cyYOhPePKybmMp6pMD98eeYOw4eZ/uoNcWPEGRfXQ7EJVEk/npJTH9qROVGOBpw==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/log": "0.1.22", + "@magic/mime-types": "0.0.23", + "@magic/types": "0.1.37" + }, + "engines": { + "node": ">=20.0.0" + } }, - "babel-plugin-transform-minify-booleans": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-minify-booleans/-/babel-plugin-transform-minify-booleans-6.9.4.tgz", - "integrity": "sha1-rLs+VqNVXdI5KOS1gtKFFi3SsZg=" + "node_modules/@magic/hyperapp": { + "version": "2.0.4-alpha.0.6", + "resolved": "https://registry.npmjs.org/@magic/hyperapp/-/hyperapp-2.0.4-alpha.0.6.tgz", + "integrity": "sha512-s9R+C5EYdtP3e8ef0ihDf7ul3EbO97rLzC0Kqw0CpgzIvU9sl+G/LPtJwn0NK4xaP9nhlYzNyVl1AXp47VAmKA==", + "license": "MIT" }, - "babel-plugin-transform-property-literals": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-property-literals/-/babel-plugin-transform-property-literals-6.9.4.tgz", - "integrity": "sha1-mMHSHiVXNlc/k+zlRFn2ziSYXTk=", - "requires": { - "esutils": "^2.0.2" + "node_modules/@magic/log": { + "version": "0.1.22", + "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.22.tgz", + "integrity": "sha512-2k+SPmLgwqeTHs1zPcGywVY63nRBcGf+99H/p467oNn7ha3QDMNjSZei2zZo8Yr/vUh3vRcAdRO+UFyeWJ6y2Q==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.37" } }, - "babel-plugin-transform-regexp-constructors": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regexp-constructors/-/babel-plugin-transform-regexp-constructors-0.4.3.tgz", - "integrity": "sha1-WLd3W2OvzzMyj66aX4j71PsLSWU=" - }, - "babel-plugin-transform-remove-console": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-console/-/babel-plugin-transform-remove-console-6.9.4.tgz", - "integrity": "sha1-uYA2DAZzhOJLNXpYjYB9PINSd4A=" - }, - "babel-plugin-transform-remove-debugger": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-debugger/-/babel-plugin-transform-remove-debugger-6.9.4.tgz", - "integrity": "sha1-QrcnYxyXl44estGZp67IShgznvI=" - }, - "babel-plugin-transform-remove-undefined": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-remove-undefined/-/babel-plugin-transform-remove-undefined-0.5.0.tgz", - "integrity": "sha512-+M7fJYFaEE/M9CXa0/IRkDbiV3wRELzA1kKQFCJ4ifhrzLKn/9VCCgj9OFmYWwBd8IB48YdgPkHYtbYq+4vtHQ==", - "requires": { - "babel-helper-evaluate-path": "^0.5.0" - } - }, - "babel-plugin-transform-simplify-comparison-operators": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-simplify-comparison-operators/-/babel-plugin-transform-simplify-comparison-operators-6.9.4.tgz", - "integrity": "sha1-9ir+CWyrDh9ootdT/fKDiIRxzrk=" - }, - "babel-plugin-transform-undefined-to-void": { - "version": "6.9.4", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz", - "integrity": "sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA=" - }, - "babel-preset-minify": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/babel-preset-minify/-/babel-preset-minify-0.5.1.tgz", - "integrity": "sha512-1IajDumYOAPYImkHbrKeiN5AKKP9iOmRoO2IPbIuVp0j2iuCcj0n7P260z38siKMZZ+85d3mJZdtW8IgOv+Tzg==", - "requires": { - "babel-plugin-minify-builtins": "^0.5.0", - "babel-plugin-minify-constant-folding": "^0.5.0", - "babel-plugin-minify-dead-code-elimination": "^0.5.1", - "babel-plugin-minify-flip-comparisons": "^0.4.3", - "babel-plugin-minify-guarded-expressions": "^0.4.4", - "babel-plugin-minify-infinity": "^0.4.3", - "babel-plugin-minify-mangle-names": "^0.5.0", - "babel-plugin-minify-numeric-literals": "^0.4.3", - "babel-plugin-minify-replace": "^0.5.0", - "babel-plugin-minify-simplify": "^0.5.1", - "babel-plugin-minify-type-constructors": "^0.4.3", - "babel-plugin-transform-inline-consecutive-adds": "^0.4.3", - "babel-plugin-transform-member-expression-literals": "^6.9.4", - "babel-plugin-transform-merge-sibling-variables": "^6.9.4", - "babel-plugin-transform-minify-booleans": "^6.9.4", - "babel-plugin-transform-property-literals": "^6.9.4", - "babel-plugin-transform-regexp-constructors": "^0.4.3", - "babel-plugin-transform-remove-console": "^6.9.4", - "babel-plugin-transform-remove-debugger": "^6.9.4", - "babel-plugin-transform-remove-undefined": "^0.5.0", - "babel-plugin-transform-simplify-comparison-operators": "^6.9.4", - "babel-plugin-transform-undefined-to-void": "^6.9.4", - "lodash": "^4.17.11" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "node_modules/@magic/mime-types": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@magic/mime-types/-/mime-types-0.0.23.tgz", + "integrity": "sha512-BwGztfKpPLj2ECpC4zd9ZPAJimjkL2hUInYwuK6r1Y/eKtVVtPfRIf6kV34I/A+akTjFvAvXk+zpzrO5cjOEHA==", + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" + } + }, + "node_modules/@magic/tags": { + "version": "0.0.15", + "resolved": "https://registry.npmjs.org/@magic/tags/-/tags-0.0.15.tgz", + "integrity": "sha512-tVnz6lna7DEOZF+QxEaa5vgMKzMbbqevhpuHcAmgDihRVmKTVg5xzePgK7gaCj5XDwOtqePB9mhfdmRENWvkgA==", + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" + } + }, + "node_modules/@magic/test": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@magic/test/-/test-0.3.12.tgz", + "integrity": "sha512-VcenQTTh0YsGkEsJ99GyNORXSFXRPaAY6J1BoEdqMg1u04cmo3Sl18JwiK/FnpWOiX1mkX9mvIGbC8fn3rgWTQ==", "dev": true, - "requires": { - "tweetnacl": "^0.14.3" + "license": "AGPL-3.0", + "dependencies": { + "@magic/cases": "0.0.12", + "@magic/cli": "0.0.53", + "@magic/deep": "0.1.20", + "@magic/error": "0.0.22", + "@magic/fs": "0.0.37", + "@magic/log": "0.1.21", + "@magic/types": "0.1.36", + "c8": "11.0.0", + "canvas": "3.2.3", + "happy-dom": "20.10.1", + "magic-string": "0.30.21", + "typescript": "6.0.3", + "typescript-eslint": "8.60.1" + }, + "bin": { + "magic-test": "dist/bin/t.js", + "magic-test-native": "dist/bin/node-test-runner.js", + "t": "dist/bin/t.js" + }, + "engines": { + "node": ">=22.18.0" } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@magic/test/node_modules/@magic/cases": { + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@magic/cases/-/cases-0.0.12.tgz", + "integrity": "sha512-Ojcr4GXVPLxOUCYJMhvc6qR1kvGcLzFoheyPLq6maUlpc0Ww64+VVsBgdfh1H1dkX2wFgT9fEiKdQJMKk1s1OQ==", "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "browserslist": { - "version": "4.8.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.7.tgz", - "integrity": "sha512-gFOnZNYBHrEyUML0xr5NJ6edFaaKbTFX9S9kQHlYfCP0Rit/boRIz4G+Avq6/4haEKJXdGGUnoolx+5MWW2BoA==", - "requires": { - "caniuse-lite": "^1.0.30001027", - "electron-to-chromium": "^1.3.349", - "node-releases": "^1.1.49" + "node_modules/@magic/test/node_modules/@magic/cli": { + "version": "0.0.53", + "resolved": "https://registry.npmjs.org/@magic/cli/-/cli-0.0.53.tgz", + "integrity": "sha512-yeONkBj7f2QaQwcYlix+kHesUhFgQfbbl+8EwG20rUiKAxwmeOHycaRRqEIvFfX/aChHgLohjOEUwn42ZJcPWw==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/cases": "0.0.11", + "@magic/deep": "0.1.20", + "@magic/error": "0.0.21", + "@magic/log": "0.1.21", + "@magic/types": "0.1.31" + }, + "engines": { + "node": ">=14.15.4" } }, - "c8": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-7.1.0.tgz", - "integrity": "sha512-r4ba14DehwROz7Sm/JpwqL+s2FGGMiVmgnhAtYJ/jQcPpR7L+yEO3MUoQXHOFBetqd+mpTY5XiNY79QY6bYpnw==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@istanbuljs/schema": "^0.1.2", - "find-up": "^4.0.0", - "foreground-child": "^2.0.0", - "furi": "^2.0.0", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-reports": "^3.0.0", - "rimraf": "^3.0.0", - "test-exclude": "^6.0.0", - "v8-to-istanbul": "^4.1.2", - "yargs": "^15.0.0", - "yargs-parser": "^16.0.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001028", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001028.tgz", - "integrity": "sha512-Vnrq+XMSHpT7E+LWoIYhs3Sne8h9lx9YJV3acH3THNCwU/9zV93/ta4xVfzTtnqd3rvnuVpVjE3DFqf56tr3aQ==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "node_modules/@magic/test/node_modules/@magic/cli/node_modules/@magic/cases": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@magic/cases/-/cases-0.0.11.tgz", + "integrity": "sha512-5ceOj4gr5m+QSuA4LjzSql++lhnhgAPCnOdcejaq8YaKBHVO47OizyqCohJ+9c3qaeCrFShvT6pxY32/yoTXCA==", "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" + "node_modules/@magic/test/node_modules/@magic/cli/node_modules/@magic/error": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@magic/error/-/error-0.0.21.tgz", + "integrity": "sha512-XH72GkDSvrClDT1/K2PQZxV/s0A8tu3Y6ytSyX3708ZFBSK9lcPN0pvXz8Q7CABrTurXyjaQlaLGunpOxxAYkA==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.30" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@magic/test/node_modules/@magic/cli/node_modules/@magic/error/node_modules/@magic/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.30.tgz", + "integrity": "sha512-f1NpNnfEDOmLx5Jmto3iEE39hnep5frMOic9BLUP7s/V3u/Tnvdl7x6DrZcSOTujJDbTvkIJKjUTyApgf0rxVQ==", "dev": true, - "requires": { - "delayed-stream": "~1.0.0" + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "24.9.2" + }, + "engines": { + "node": ">=18" } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "requires": { - "safe-buffer": "~5.1.1" + "node_modules/@magic/test/node_modules/@magic/cli/node_modules/@magic/types": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.31.tgz", + "integrity": "sha512-PJraSoafsNuuqlKiHgYqxlUlE56fURPiWcUZTW+Ad9mMAMFHhq1fK73FTqsewZz60J6dsVRsRdxG48XZ5JzT+A==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "24.9.2" + }, + "engines": { + "node": ">=18" } }, - "core-js-compat": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.4.tgz", - "integrity": "sha512-zAa3IZPvsJ0slViBQ2z+vgyyTuhd3MFn1rBQjZSKVEgB0UMYhUkCj9jJUVPgGTGqWvsBVmfnruXgTcNyTlEiSA==", - "requires": { - "browserslist": "^4.8.3", - "semver": "7.0.0" - }, + "node_modules/@magic/test/node_modules/@magic/cli/node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "license": "MIT", "dependencies": { - "semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" - } + "undici-types": "~7.16.0" } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "coveralls": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.9.tgz", - "integrity": "sha512-nNBg3B1+4iDox5A5zqHKzUTiwl2ey4k2o0NEcVZYvl+GOSJdKBj4AJGKLv6h3SvWch7tABHePAQOSZWM9E2hMg==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.0", - "request": "^2.88.0" + "node_modules/@magic/test/node_modules/@magic/deep": { + "version": "0.1.20", + "resolved": "https://registry.npmjs.org/@magic/deep/-/deep-0.1.20.tgz", + "integrity": "sha512-0nW36ddsSdxDCqZ1UR1abqQCLnwy1DIhxFc6rFUEO+YA8SL2dJ9UBcqgJc1tCU30B+35d7CTsfJ9n/oRRYl5tw==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.30" } }, - "cross-spawn": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz", - "integrity": "sha512-u7v4o84SwFpD32Z8IIcPZ6z1/ie24O6RU3RbtL5Y316l3KuHVPx9ItBgWQ6VlfAFnRnTtMUrsQ9MUUTuEZjogg==", + "node_modules/@magic/test/node_modules/@magic/deep/node_modules/@magic/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.30.tgz", + "integrity": "sha512-f1NpNnfEDOmLx5Jmto3iEE39hnep5frMOic9BLUP7s/V3u/Tnvdl7x6DrZcSOTujJDbTvkIJKjUTyApgf0rxVQ==", "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "24.9.2" + }, + "engines": { + "node": ">=18" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "node_modules/@magic/test/node_modules/@magic/deep/node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, - "requires": { - "assert-plus": "^1.0.0" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" } }, - "debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "requires": { - "ms": "^2.1.1" + "node_modules/@magic/test/node_modules/@magic/error": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@magic/error/-/error-0.0.22.tgz", + "integrity": "sha512-oU49KN/BuGI/3Ngeg9qMW/omxXT33uSYSWeI0dS0yIfSu58kt4tSbaQB1f01xLdZTue3PDBmFiknksA1wbJMMg==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.36" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "requires": { - "object-keys": "^1.0.12" + "node_modules/@magic/test/node_modules/@magic/fs": { + "version": "0.0.37", + "resolved": "https://registry.npmjs.org/@magic/fs/-/fs-0.0.37.tgz", + "integrity": "sha512-t3XwV+gRhuQATGQaN4IXHfZGrPJEc3Ayi6Mj5/A6Wpx/+hsIliQPupY/PQnjBot8ek/0EzbY9/xGcpayCKoJEg==", + "dev": true, + "license": "AGPL-3.0", + "dependencies": { + "@magic/deep": "0.1.20", + "@magic/error": "0.0.21", + "@magic/log": "0.1.21", + "@magic/mime-types": "0.0.22", + "@magic/types": "0.1.30" + }, + "engines": { + "node": ">=14.15.4" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "node_modules/@magic/test/node_modules/@magic/fs/node_modules/@magic/error": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@magic/error/-/error-0.0.21.tgz", + "integrity": "sha512-XH72GkDSvrClDT1/K2PQZxV/s0A8tu3Y6ytSyX3708ZFBSK9lcPN0pvXz8Q7CABrTurXyjaQlaLGunpOxxAYkA==", "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.30" } }, - "electron-to-chromium": { - "version": "1.3.359", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.359.tgz", - "integrity": "sha512-ewZp4BQftbLclBwmFYoTrlyiLMXQTiYeqh1hn24sWao9bGhUHzQtpytymN8JsenWlQ9SbBWynTPvfghb0Ipn1Q==" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "node_modules/@magic/test/node_modules/@magic/fs/node_modules/@magic/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.30.tgz", + "integrity": "sha512-f1NpNnfEDOmLx5Jmto3iEE39hnep5frMOic9BLUP7s/V3u/Tnvdl7x6DrZcSOTujJDbTvkIJKjUTyApgf0rxVQ==", "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "24.9.2" + }, + "engines": { + "node": ">=18" } }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "node_modules/@magic/test/node_modules/@magic/fs/node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" } }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "node_modules/@magic/test/node_modules/@magic/log": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@magic/log/-/log-0.1.21.tgz", + "integrity": "sha512-GBXWKZXmSAA0UdOONyQRgJbLZigWGR2/h2swRJu9yEWPglvscMAIa4NyRxX1dSrvpCbiOpOb/z+I5vM7OkIwEw==", "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "license": "AGPL-3.0", + "dependencies": { + "@magic/types": "0.1.30" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "furi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/furi/-/furi-2.0.0.tgz", - "integrity": "sha512-uKuNsaU0WVaK/vmvj23wW1bicOFfyqSsAIH71bRZx8kA4Xj+YCHin7CJKJJjkIsmxYaPFLk9ljmjEyB7xF7WvQ==", + "node_modules/@magic/test/node_modules/@magic/log/node_modules/@magic/types": { + "version": "0.1.30", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.30.tgz", + "integrity": "sha512-f1NpNnfEDOmLx5Jmto3iEE39hnep5frMOic9BLUP7s/V3u/Tnvdl7x6DrZcSOTujJDbTvkIJKjUTyApgf0rxVQ==", "dev": true, - "requires": { - "@types/is-windows": "^1.0.0", - "is-windows": "^1.0.2" + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "24.9.2" + }, + "engines": { + "node": ">=18" } }, - "gensync": { - "version": "1.0.0-beta.1", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", - "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==" - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "node_modules/@magic/test/node_modules/@magic/log/node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, - "requires": { - "assert-plus": "^1.0.0" + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/@magic/test/node_modules/@magic/mime-types": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@magic/mime-types/-/mime-types-0.0.22.tgz", + "integrity": "sha512-ACjNJMiTDXl0SqmZGPFZtJkJZg7HzE7atXSwNAhNOXWpXJr7kKvMRnILIEQU5rx76AmyHXhJoQNjDMHT3tcpCA==", "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "license": "AGPL-3.0", + "engines": { + "node": ">=14.15.4" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "node_modules/@magic/test/node_modules/@magic/types": { + "version": "0.1.36", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.36.tgz", + "integrity": "sha512-dR/FkKBdXcosWguhG7twc3kqG8zJg/qancWHZHf5scgToegbHUMCy22rqCn2dUZnb4czQet5XrDYqjKwzS4rEA==", "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "25.9.2" + }, + "engines": { + "node": ">=18" } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" - }, - "html-escaper": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.0.tgz", - "integrity": "sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==", - "dev": true - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "node_modules/@magic/test/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } + "license": "MIT" }, - "hyperapp-render": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hyperapp-render/-/hyperapp-render-3.1.0.tgz", - "integrity": "sha512-4BhBR9oQfZm2wJP8Ks8nLd91In5kLQunIgGwdaWN6Hr7+zGHyma0VEn30eE46LKRcjUlANtbmt0VgUF27gp9uw==", - "requires": { - "@types/node": "*" + "node_modules/@magic/transmute": { + "version": "0.0.59", + "resolved": "https://registry.npmjs.org/@magic/transmute/-/transmute-0.0.59.tgz", + "integrity": "sha512-hExeVRF4j3t50LGgwyAtQ+NFFOIqEE71bmzYr+jYAHjkci90uZObDpYpwzFRbLW6ipxZn67iryoz6lRjhPiPDg==", + "license": "AGPL-3.0", + "dependencies": { + "@magic/cases": "0.0.13", + "@magic/cli": "0.0.54", + "@magic/entities": "0.0.17", + "@magic/log": "0.1.22", + "@magic/types": "0.1.37", + "marked": "18.0.5", + "posthtml-parser": "0.12.1", + "prettier": "3.8.5" + }, + "bin": { + "magic-transmute": "src/bin.js" + }, + "engines": { + "node": ">=14.15.4" } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/@magic/transmute/node_modules/prettier": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.5.tgz", + "integrity": "sha512-zxcTTCedNGJM4R8sj/Cq/F0W/c4iE0afWBcBwMTRtw4WHYP9TWkYjdiH3npPRUYsXQCPR0hTU9yjovOu+E6EQA==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/@magic/types": { + "version": "0.1.37", + "resolved": "https://registry.npmjs.org/@magic/types/-/types-0.1.37.tgz", + "integrity": "sha512-ASSuoenz/NYYB6wgPBG2bVNStHcvGaMYuA62m7gOLxPJu8MhsGVpYTHMQ6q0NrEeE6njhQU/dXCs+ZoKku5tmg==", + "license": "AGPL-3.0", + "dependencies": { + "@types/node": "25.9.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@swc/core": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.43.tgz", + "integrity": "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.27" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.43", + "@swc/core-darwin-x64": "1.15.43", + "@swc/core-linux-arm-gnueabihf": "1.15.43", + "@swc/core-linux-arm64-gnu": "1.15.43", + "@swc/core-linux-arm64-musl": "1.15.43", + "@swc/core-linux-ppc64-gnu": "1.15.43", + "@swc/core-linux-s390x-gnu": "1.15.43", + "@swc/core-linux-x64-gnu": "1.15.43", + "@swc/core-linux-x64-musl": "1.15.43", + "@swc/core-win32-arm64-msvc": "1.15.43", + "@swc/core-win32-ia32-msvc": "1.15.43", + "@swc/core-win32-x64-msvc": "1.15.43" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.43.tgz", + "integrity": "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.43.tgz", + "integrity": "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.43.tgz", + "integrity": "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.43.tgz", + "integrity": "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.43.tgz", + "integrity": "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.43.tgz", + "integrity": "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.43.tgz", + "integrity": "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.43.tgz", + "integrity": "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.43.tgz", + "integrity": "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.43.tgz", + "integrity": "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.43.tgz", + "integrity": "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.43", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.43.tgz", + "integrity": "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.27.tgz", + "integrity": "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "peer": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/node": { + "version": "25.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", + "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" } }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.1.tgz", + "integrity": "sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/type-utils": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.60.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "node_modules/@typescript-eslint/parser": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.1.tgz", + "integrity": "sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "node_modules/@typescript-eslint/project-service": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.1.tgz", + "integrity": "sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.60.1", + "@typescript-eslint/types": "^8.60.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.1.tgz", + "integrity": "sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.1.tgz", + "integrity": "sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } }, - "istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.1.tgz", + "integrity": "sha512-sdwTrpjosW7ANQYJ39ZBF1ZyEMEGVB2UsikrserVM/30a/F1dTLnu9bGxEdosugyu5caigjLrR2qiD11asjI1A==", "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.1.tgz", + "integrity": "sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.1.tgz", + "integrity": "sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.60.1", + "@typescript-eslint/tsconfig-utils": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/visitor-keys": "8.60.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.1.tgz", + "integrity": "sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.60.1", + "@typescript-eslint/types": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.1.tgz", + "integrity": "sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.60.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/c8": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-11.0.0.tgz", + "integrity": "sha512-e/uRViGHSVIJv7zsaDKM7VRn2390TgHXqUSvYwPHBQaU6L7E9L0n9JbdkwdYPvshDT0KymBmmlwSpms3yBaMNg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.1", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^8.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": "20 || >=22" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/canvas": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.3.tgz", + "integrity": "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } + "optional": true } } }, - "istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-2osTcC8zcOSUkImzN2EWQta3Vdi4WjjKw99P2yWx5mLnigAM0Rd5uYFn1cf2i/Ois45GkNjaoTqc5CxgMSX80A==", + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.379", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.379.tgz", + "integrity": "sha512-v/qV5aV5EUA2pGilzUCq5/eyOloZAqDZBu9UMBIzgPpLlprjSR6zswsWBTv0KpqxLGUAZEwhO95ZCt7srymNVA==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.5.0.tgz", + "integrity": "sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==", + "dev": true, + "license": "MIT", + "peer": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.6.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/happy-dom": { + "version": "20.10.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.1.tgz", + "integrity": "sha512-awPoqPjx8CgjapJllyDlgzgVHjBExcitKK5ZJkxwhQJyQpHFkyS2bEcqCm7IeW20cQvuCI0cz2Ifq79CJKqtiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" } }, - "js-tokens": { + "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "license": "MIT" + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4" + } }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } }, - "json-schema-traverse": { + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT", + "peer": true }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true }, - "json5": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", - "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", - "requires": { - "minimist": "^1.2.0" + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" } }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", - "dev": true + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, - "levenary": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/levenary/-/levenary-1.1.1.tgz", - "integrity": "sha512-mkAdOIt79FD6irqjYSs4rdbnlT5vRonMEvBVPVb3XmevfS8kgRXwfes0dhPdEtzTWD/1eNE/Bm/G1iRt6DcnQQ==", - "requires": { - "leven": "^3.1.0" + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "requires": { - "p-locate": "^4.1.0" + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" + "node_modules/marked": { + "version": "18.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.5.tgz", + "integrity": "sha512-S6GcvALHg6K4ohtu4E7x0a1AqhAjp6cV8KhLSyN9qVapnzJkusVBxZRcIU9AeYsbe6P1hKDusSbEOzGyyuce6w==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" } }, - "make-dir": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", - "integrity": "sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w==", + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", "dev": true, - "requires": { - "semver": "^6.0.0" + "license": "MIT", + "engines": { + "node": ">=10" }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "marked": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.8.0.tgz", - "integrity": "sha512-MyUe+T/Pw4TZufHkzAfDj6HarCBWia2y27/bhuYkTaiUnfDYFnCP3KUN+9oM7Wi6JA2rymtVYbQu3spE0GCmxQ==" - }, - "mime-db": { - "version": "1.43.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", - "dev": true - }, - "mime-types": { - "version": "2.1.26", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", - "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "requires": { - "mime-db": "1.43.0" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "requires": { - "brace-expansion": "^1.1.7" + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" }, - "node-releases": { - "version": "1.1.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.50.tgz", - "integrity": "sha512-lgAmPv9eYZ0bGwUYAKlr8MG6K4CvWliWqnkcT2P8mMAgVrH3lqfBPorFlxiG1pHQnqmavJZ9vbMXUTNyMLbrgQ==", - "requires": { - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=" + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "license": "MIT", + "engines": { + "node": ">=18" } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "wrappy": "1" } }, - "p-limit": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", - "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, - "requires": { - "p-try": "^2.0.0" + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, - "requires": { - "p-limit": "^2.2.0" + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "path-exists": { + "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "postcss": { - "version": "7.0.26", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", - "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", - "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" } }, - "postcss-value-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.0.3.tgz", - "integrity": "sha512-N7h4pG+Nnu5BEIzyeaaIYWs0LI5XC40OrRh5L60z0QjFsqGWcHcbkBvpe1WYpcIS9yQ8sOi/vIPt1ejQCrMVrg==" - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==" - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" - }, - "psl": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz", - "integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/posthtml-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.12.1.tgz", + "integrity": "sha512-rYFmsDLfYm+4Ts2Oh4DCDSZPtdC1BLnRXAobypVzX9alj28KGl65dIFtgDY9zB57D0TC4Qxqrawuq/2et1P0GA==", + "license": "MIT", + "dependencies": { + "htmlparser2": "^9.0.0" + }, + "engines": { + "node": ">=16" + } }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } }, - "regenerate": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", - "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==" - }, - "regenerate-unicode-properties": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.1.0.tgz", - "integrity": "sha512-LGZzkgtLY79GeXLm8Dp0BVLdQlWICzBnJz/ipWUgo59qBaZ+BHtq51P2q1uVZlppMuUAT37SDk39qUbjTWB7bA==", - "requires": { - "regenerate": "^1.4.0" - } - }, - "regenerator-transform": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.1.tgz", - "integrity": "sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==", - "requires": { - "private": "^0.1.6" - } - }, - "regexpu-core": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", - "integrity": "sha512-YlVaefl8P5BnFYOITTNzDvan1ulLOiXJzCNZxduTIosN17b87h3bvG9yHMoHaRuo88H4mQ06Aodj5VtYGGGiTg==", - "requires": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.1.0", - "regjsgen": "^0.5.0", - "regjsparser": "^0.6.0", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.1.0" - } - }, - "regjsgen": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", - "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==" - }, - "regjsparser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.3.tgz", - "integrity": "sha512-8uZvYbnfAtEm9Ab8NTb3hdLwL4g/LQzEYP7Xs27T96abJCCE2d6r3cPZPQEsLKy0vRSGVNG+/zVGtLr86HQduA==", - "requires": { - "jsesc": "~0.5.0" - }, - "dependencies": { - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" - } + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" } }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, - "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", - "requires": { - "path-parse": "^1.0.6" + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, - "requires": { - "glob": "^7.1.3" + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "dev": true, - "requires": { - "ansi-regex": "^5.0.0" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/test-exclude": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-8.0.0.tgz", + "integrity": "sha512-ZOffsNrXYggvU1mDGHk54I96r26P8SyMjO5slMKSc7+IWmtB/MQKnEC2fP51imB3/pT6YK5cT5E8f+Dd9KdyOQ==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^13.0.6", + "minimatch": "^10.2.2" + }, + "engines": { + "node": "20 || >=22" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } }, - "tough-cookie": { + "node_modules/ts-api-utils": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "tunnel-agent": { + "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "dev": true, - "requires": { + "license": "Apache-2.0", + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" - }, - "unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "requires": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "unicode-match-property-value-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz", - "integrity": "sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g==" + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "unicode-property-aliases-ecmascript": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz", - "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==" + "node_modules/typescript-eslint": { + "version": "8.60.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.1.tgz", + "integrity": "sha512-6m5hkkRAp8lKvhVpcprAIn5KkehQEh+47oHH2VGnExEh7dhNxXlg6GPAOIu6TxbVQxhebrJDvjl3020ooiWCMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.60.1", + "@typescript-eslint/parser": "8.60.1", + "@typescript-eslint/typescript-estree": "8.60.1", + "@typescript-eslint/utils": "8.60.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { "punycode": "^2.1.0" } }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" }, - "v8-to-istanbul": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.2.tgz", - "integrity": "sha512-G9R+Hpw0ITAmPSr47lSlc5A1uekSYzXxTMlFxso2xoffwo4jQnzbv1p9yXIinO8UMZKfAFewaCHwWvnH4Jb4Ug==", + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" + "convert-source-map": "^2.0.0" }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } + "engines": { + "node": ">=10.12.0" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "license": "MIT", + "engines": { + "node": ">=12" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "license": "ISC", + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "license": "MIT", + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - } + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "yargs": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.1.0.tgz", - "integrity": "sha512-T39FNN1b6hCW4SOIk1XyTOWxtXdcen0t+XYrysQmChzSipvhBO8Bj0nK1ozAasdk24dNWuMZvr4k24nz+8HHLg==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^16.1.0" + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "yargs-parser": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-16.1.0.tgz", - "integrity": "sha512-H/V41UNZQPkUMIT5h5hiwg4QKIY1RPvoBV4XcjUbRM8Bk2oKqqyZ0DIEbTFZB0XjbtSPG8SAa/0DxCQmiRgzKg==", + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } } } diff --git a/package.json b/package.json index 7700a1c6..434660e7 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,25 @@ { "name": "@magic/core", - "version": "0.0.33", + "version": "0.0.159", "author": "Wizards & Witches", - "homepage": "https://magic.github.io/core", + "homepage": "https://magic.github.io/", "license": "AGPL-3.0", "description": "@magic core. generate static pages and serverless lambdas. ~5kb client boilerplate.", - "module": "src/index.mjs", + "main": "src/index.mjs", + "type": "module", "engines": { - "node": ">=13.5.0" + "node": ">=14.15.4" }, - "engineString": true, + "engineStrict": true, "scripts": { "start": "t -p", - "build": "NODE_ENV=production src/bin.mjs build", + "build": "NODE_ENV=production src/bin.mjs build --no-check-links-exit", "devbuild": "src/bin.mjs build", "serve": "src/bin.mjs serve --watch src", "prod": "NODE_ENV=production src/bin.mjs build serve --watch src", "clean": "src/bin.mjs clean", - "dev": "src/bin.mjs dev --watch src", - "format": "f -w --exclude docs", + "dev": "src/bin.mjs dev --watch src --keep-client", + "format": "f -w --exclude docs api", "test": "t", "calls": "calls" }, @@ -36,39 +37,31 @@ "url": "https://github.com/magic/core/issues" }, "dependencies": { - "@babel/core": "7.8.4", - "@babel/plugin-proposal-export-namespace-from": "7.8.3", - "@babel/plugin-proposal-object-rest-spread": "7.8.3", - "@babel/plugin-transform-arrow-functions": "7.8.3", - "@babel/plugin-transform-react-jsx": "7.8.3", - "@babel/preset-env": "7.8.4", - "@magic-libraries/prevent-default": "0.0.3", - "@magic/cases": "0.0.2", - "@magic/cli": "0.0.23", - "@magic/css": "0.7.9", - "@magic/deep": "0.1.2", - "@magic/error": "0.0.5", - "@magic/fs": "0.0.4", - "@magic/hyperapp": "2.0.3", - "@magic/log": "0.1.4", - "@magic/tags": "0.0.3", - "@magic/transmute": "0.0.8", - "@magic/types": "0.1.11", - "babel-plugin-remove-code": "0.0.6", - "babel-preset-minify": "0.5.1", - "hyperapp-render": "3.1.0" + "@magic-libraries/prevent-default": "0.0.6", + "@magic/cases": "0.0.13", + "@magic/cli": "0.0.54", + "@magic/css": "0.7.59", + "@magic/deep": "0.1.21", + "@magic/error": "0.0.23", + "@magic/fs": "0.0.39", + "@magic/hyperapp": "2.0.4-alpha.0.6", + "@magic/log": "0.1.22", + "@magic/tags": "0.0.15", + "@magic/transmute": "0.0.59", + "@magic/types": "0.1.37", + "@swc/core": "1.15.43" }, "devDependencies": { - "@magic-modules/gdpr": "0.0.14", - "@magic-modules/git-badges": "0.0.4", - "@magic-modules/library-list": "0.0.7", - "@magic-modules/light-switch": "0.0.5", - "@magic-modules/module-list": "0.0.8", - "@magic-modules/pre": "0.0.2", - "@magic-modules/theme-list": "0.0.5", - "@magic-themes/docs": "0.0.6", - "@magic/format": "0.0.7", - "@magic/test": "0.1.35" + "@magic-modules/git-badges": "0.0.12", + "@magic-modules/library-list": "0.0.18", + "@magic-modules/light-switch": "0.0.12", + "@magic-modules/module-list": "0.0.22", + "@magic-modules/no-spy": "0.0.9", + "@magic-modules/pre": "0.0.12", + "@magic-modules/theme-list": "0.0.14", + "@magic-themes/docs": "0.0.15", + "@magic/format": "0.0.74", + "@magic/test": "0.3.12" }, "keywords": [ "magic", @@ -84,6 +77,10 @@ "name": "jascha ehrenreich", "github": "jaeh" }, + { + "name": "daniela weiss", + "github": "mslitto" + }, { "name": "philipp tiefenbacher", "github": "wizard23" @@ -100,6 +97,10 @@ "name": "litto", "github": "mslitto" }, + { + "name": "michael parenti", + "github": "exiledsurfer" + }, { "name": "daniel safar", "github": "n1c3" diff --git a/src/bin.mjs b/src/bin.mjs index df457fe8..701575bb 100755 --- a/src/bin.mjs +++ b/src/bin.mjs @@ -2,12 +2,37 @@ import cli from '@magic/cli' -import { runCluster } from './cluster/index.mjs' +import { runCluster } from './runCluster.mjs' const args = { - options: [['--help', '-help', 'help', '--h', '-h'], ['--no-minify'], ['--watch', '-w']], + options: [ + ['--help', '-help', 'help', '--h', '-h'], + '--no-minify', + ['--watch', '-w'], + '--keep-client', + '--no-mangle-names', + '--keep-console', + '--keep-dead-code', + '--keep-debugger', + '--no-check-links', + '--no-check-links-remote', + '--no-check-links-exit', + '--force-publish', + ], env: [[['--production', '--prod', '--p', '-p'], 'NODE_ENV', 'production']], commands: [['serve', 'dev', 'development', 'start'], 'clean', 'connect', 'publish', 'build'], + single: [ + '--no-minify', + '--keep-client', + '--no-mangle-names', + '--keep-console', + '--keep-dead-code', + '--keep-debugger', + '--no-check-links', + '--no-check-links-remote', + '--no-check-links-exit', + '--force-publish', + ], help: { name: 'magic', header: 'static and serverless page generator', @@ -28,6 +53,10 @@ const args = { '--no-minify': 'do not minify js', '--keep-client': 'keeps the unminifed .__browserify_empty.js', '--watch': 'watch additional directories', + '--no-check-links': 'do not check any links for validity', + '--no-check-links-remote': 'do not check external links for validity', + '--no-check-links-exit': 'do not exit production build on broken links', + '--force-publish': 'add -f to the publishing git push call', }, example: ` production: diff --git a/src/cluster/build.mjs b/src/cluster/build.mjs index 685dc1a3..649db81d 100644 --- a/src/cluster/build.mjs +++ b/src/cluster/build.mjs @@ -6,26 +6,22 @@ import runCmd from './runCmd.mjs' import { serve } from '../tasks/index.mjs' export const build = async ({ commands, config }) => { - const App = await runApp(config) - const app = await runCmd('prepare', App, config) - try { - const { bundle, css, pages /*, serviceWorker */ } = await runCmd('transpile', app, config) - app.pages = pages - app.css = css - app.client = bundle.code - // app.sw = serviceWorker.code + const App = await runApp(config) + let app = await runCmd('prepare', App, config) + + app = await runCmd('transpile', app, config) if (commands.build) { await runCmd('write', app, config) } - } catch (e) { - log.error('error during build', e) - } - if (commands.serve) { - serve(app) - } else { - process.send({ evt: 'quit' }) + if (commands.serve) { + serve(app, config) + } else { + process.send({ evt: 'quit' }) + } + } catch (e) { + log.error('E_MAGIC_BUILD', e) } } diff --git a/src/cluster/index.mjs b/src/cluster/index.mjs index 60cc0ebd..0ffe7bfd 100644 --- a/src/cluster/index.mjs +++ b/src/cluster/index.mjs @@ -1,43 +1,3 @@ -import cluster from 'cluster' - -import { build } from './build.mjs' -import { master } from './master.mjs' -import { watch } from './watch.mjs' - -import { runConfig } from '../config.mjs' - -export const runCluster = async options => { - const { args, commands } = options - - // get the config - const config = await runConfig() - - if (!global.CHECK_PROPS) { - const { CHECK_PROPS } = await import('../lib/CHECK_PROPS.mjs') - global.CHECK_PROPS = CHECK_PROPS - } - - if (cluster.isMaster) { - await master({ cluster, commands, config }) - } else if (cluster.isWorker) { - if (commands.serve && cluster.worker.id === 1) { - // watcher, - // watches the directory and tells master to restart the build process when files change - watch({ args, config }) - } else { - // builder - // builds the files and pages needed - build({ commands, config }) - } - - process - .on('unhandledRejection', error => { - process.send({ evt: 'error', error: error.toString(), stack: error.stack }) - }) - .on('uncaughtException', error => { - process.send({ evt: 'error', error: error.toString(), stack: error.stack }) - }) - } -} - -export default runCluster +export { build } from './build.mjs' +export { primary } from './primary.mjs' +export { watch } from './watch.mjs' diff --git a/src/cluster/master.mjs b/src/cluster/primary.mjs similarity index 59% rename from src/cluster/master.mjs rename to src/cluster/primary.mjs index 69ea91a0..f043fc30 100644 --- a/src/cluster/master.mjs +++ b/src/cluster/primary.mjs @@ -2,28 +2,38 @@ import log from '@magic/log' import { runCmd } from './runCmd.mjs' -export const master = async ({ cluster, commands, config }) => { - if (config.URL_WARNING) { - log.warn('Autodetected URL:', `https://${config.URL}`) +export const primary = async ({ + args, + cluster, + commands, + DIR, + GIT, + URL, + URL_WARNING, + WEB_ROOT, +}) => { + if (URL_WARNING) { + log.warn('Autodetected URL:', `https://${URL}`) log.info(` - to hide this warning and make startup ${config.URL_WARNING}ms faster, - add the following to your config.mjs file (and adjust the values if needed) + to hide this warning and make startup ${URL_WARNING}ms faster, + add the following to your magic.js file (and adjust the values if needed) - URL: '${config.URL}', - WEB_ROOT: '${config.WEB_ROOT}', + URL: '${URL}', + WEB_ROOT: '${WEB_ROOT}', `) } if (commands.clean) { - await runCmd('clean', config) + await runCmd('clean', DIR.PUBLIC) } if (commands.connect) { - await runCmd('connect', config) + await runCmd('connect', { DIR, GIT }) } if (commands.publish) { - await runCmd('publish', config) + const FORCE = !!args.forcePublish + await runCmd('publish', { DIR, GIT, FORCE }) } const bail = !commands.build && !commands.serve @@ -36,6 +46,7 @@ export const master = async ({ cluster, commands, config }) => { if (commands.serve) { watchWorker = cluster.fork() } + let buildWorker = cluster.fork() let lastCall = new Date().getTime() @@ -49,9 +60,16 @@ export const master = async ({ cluster, commands, config }) => { const delay = now - lastCall lastCall = now if (delay > 10) { - // files have changed, restart build worker - buildWorker.kill(1) - buildWorker = cluster.fork() + if (process.platform === 'win32') { + log.error( + 'E_WIN_RESTART', + 'windows can not restart the process yet. please manually restart.', + ) + } else { + // files have changed, restart build worker + buildWorker.kill(1) + buildWorker = cluster.fork() + } } } else { log.warn('Unexpected message from watch worker', msg) diff --git a/src/cluster/runCmd.mjs b/src/cluster/runCmd.mjs index 4824e57b..e6f6beb0 100644 --- a/src/cluster/runCmd.mjs +++ b/src/cluster/runCmd.mjs @@ -5,11 +5,14 @@ import * as tasks from '../tasks/index.mjs' export const runCmd = async (cmd, ...args) => { const startTime = log.hrtime() - const result = await tasks[cmd](...args) - - log.timeTaken(startTime, cmd) - - return result + try { + const result = await tasks[cmd](...args) + log.timeTaken(startTime, cmd) + return result + } catch (e) { + log.timeTaken(startTime, cmd) + return e + } } export default runCmd diff --git a/src/cluster/watch.mjs b/src/cluster/watch.mjs index 8205ad6e..f96cd074 100644 --- a/src/cluster/watch.mjs +++ b/src/cluster/watch.mjs @@ -6,15 +6,23 @@ import * as tasks from '../tasks/index.mjs' const cwd = process.cwd() -export const watch = ({ args, config }) => { +export const watch = ({ args, ROOT, CONFIG_FILE_PATH, STATIC }) => { const watchDirs = args.watch - let dirs = [config.ROOT] + let dirs = [ROOT] + + if (!is.array(STATIC)) { + STATIC = [STATIC] + } if (is.array(watchDirs)) { - dirs = [...dirs, ...watchDirs] + dirs = [...dirs, ...STATIC, ...watchDirs] + } else if (is.string(watchDirs)) { + dirs = [...dirs, ...STATIC, watchDirs] } dirs = dirs.map(dir => (dir.startsWith(cwd) ? dir : path.join(cwd, dir))) + dirs.push(CONFIG_FILE_PATH) + tasks.watch(dirs) } diff --git a/src/config.mjs b/src/config.mjs index 34184871..b070cc84 100644 --- a/src/config.mjs +++ b/src/config.mjs @@ -1,26 +1,37 @@ import child_process from 'child_process' import path from 'path' +import cases from '@magic/cases' import deep from '@magic/deep' import error from '@magic/error' import fs from '@magic/fs' -import log from '@magic/log' +import is from '@magic/types' import colors from './themes/colors.mjs' -import { replacePathSepForImport } from './lib/index.mjs' +import { findConfigFile, replaceSlashSlash, saveImport } from './lib/index.mjs' -export const runConfig = async () => { +const magicConfigNames = ['magic.mjs', 'magic.js'] +const oldConfigName = 'config.mjs' + +export const runConfig = async (args = {}) => { let conf = {} - const confPath = replacePathSepForImport(path.join(process.cwd(), 'config.mjs'), path.sep) - try { - const { default: imported } = await import(confPath) - conf = imported - } catch (e) { - log.warn('no local conf file found.') + conf.CONFIG_FILE_PATH = await findConfigFile( + process.cwd(), + magicConfigNames, + oldConfigName, + args.silent, + ) + + if (!conf.CONFIG_FILE_PATH) { + return } + const { default: imported } = await saveImport(conf.CONFIG_FILE_PATH) + + conf = deep.merge(conf, imported) + // make sure conf.ROOT starts with or is equal to process.cwd() if (!conf.ROOT) { conf.ROOT = process.cwd() @@ -28,12 +39,18 @@ export const runConfig = async () => { conf.ROOT = path.resolve(process.cwd(), conf.ROOT) } + conf.NO_CHECK_LINKS = args.noCheckLinks || conf.NO_CHECK_LINKS + conf.NO_CHECK_LINKS_REMOTE = args.noCheckLinksRemote || conf.NO_CHECK_LINKS_REMOTE + conf.NO_CHECK_LINKS_EXIT = args.noCheckLinksExit || conf.NO_CHECK_LINKS_EXIT + // object to collect various directories in. conf.DIR = conf.DIR || {} // change to change the name of magic.js and magic.css conf.CLIENT_LIB_NAME = conf.CLIENT_LIB_NAME || 'magic' + conf.NODE_MODULES = conf.NODE_MODULES || `${process.cwd()}${path.sep}node_modules` + // name of service-worker client file conf.CLIENT_SERVICE_WORKER_NAME = conf.CLIENT_SERVICE_WORKER_NAME || 'service-worker' @@ -63,15 +80,27 @@ export const runConfig = async () => { // assets dir, can include themes, modules, libraries const ASSETS = path.join(conf.ROOT, 'assets') + + const LIB = [path.join(ASSETS, 'lib'), path.join(conf.ROOT, 'lib')] + // module dir, modules get imported from here const MODULES = path.join(ASSETS, 'modules') + // static directory, files in this dir get copied to conf.PUBLIC - const STATIC = path.join(ASSETS, 'static') + let STATIC = path.join(ASSETS, 'static') + + if (conf.ADD_STATIC) { + if (!is.array(conf.ADD_STATIC)) { + conf.ADD_STATIC = [conf.ADD_STATIC] + } + + STATIC = [STATIC, ...conf.ADD_STATIC] + } // themes dir, files in this dir get used as themes const THEMES = path.join(ASSETS, 'themes') // API dir for server side lambdas. - const API = path.join(process.cwd(), conf.DIR.API || 'api') + const API = path.join(process.cwd(), conf.API_DIR || 'api') // global css variables that get used by @magic/css const THEME_VARS = conf.THEME_VARS || {} @@ -93,15 +122,73 @@ export const runConfig = async () => { conf.HASHES = JSON.parse(content) } catch (e) { if (e.code === 'ENOENT') { - conf.HASHES = { - pages: {}, - static: {}, - } + conf.HASHES = {} } else { throw error(e) } } + // array of scripts that should be appended to the body + const mapScript = src => { + if (!src.startsWith(conf.WEB_ROOT)) { + src = replaceSlashSlash(`${conf.WEB_ROOT}/${src}`) + } + + const result = { + src, + integrity: conf.HASHES[src], + } + + if (!src.startsWith(conf.URL) && !src.startsWith('/')) { + result.crossorigin = 'anonymous' + } + + return result + } + + const scriptKeys = ['PREPEND_SCRIPTS', 'APPEND_SCRIPTS'] + + scriptKeys.forEach(key => { + // scripts that get added as script tags before of magic.js + if (is.empty(conf[key])) { + conf[key] = [] + } else if (!is.array(conf.PREPEND_SCRIPTS)) { + conf[key] = [conf[key]] + } + + conf[key] = conf[key].map(mapScript) + }) + + const addKeys = ['PREPEND_JS', 'APPEND_JS', 'PREPEND_CSS', 'APPEND_CSS'] + + addKeys.forEach(key => { + if (!conf[key]) { + conf[key] = [] + } + + if (!is.array(conf[key])) { + conf[key] = conf[key] + } + }) + + // array of html tags that get prepended before the #magic html tag + // structure: { name, props, children } + if (!conf.PREPEND_TAGS) { + conf.PREPEND_TAGS = [] + } else if (!is.array(conf.PREPEND_TAGS)) { + conf.PREPEND_TAGS = [conf.PREPEND_TAGS] + } + + // array of html tags that get appended after the #magic html tag + // structure: { name, props, children } + if (!conf.APPEND_TAGS) { + conf.APPEND_TAGS = [] + } else if (!is.array(conf.APPEND_TAGS)) { + conf.APPEND_TAGS = [conf.APPEND_TAGS] + } + + conf.INCLUDED_HASH_EXTENSIONS = conf.INCLUDED_HASH_EXTENSIONS || ['.txt', '.xml'] + // the following files are zippable const ZIPPABLE = [ 'css', @@ -123,6 +210,8 @@ export const runConfig = async () => { 'ico', ] + conf.THEME = conf.THEME || [] + // those are image formats. const IMAGES = ['jpg', 'jpeg', 'png', 'svg', 'gif'] @@ -135,6 +224,7 @@ export const runConfig = async () => { STATIC, THEMES, API, + LIB, }, FILETYPES: { ZIPPABLE, @@ -161,6 +251,17 @@ export const runConfig = async () => { conf.IGNORED_STATIC = conf.IGNORED_STATIC || [] + if (is.string(conf.IGNORED_STATIC)) { + conf.IGNORED_STATIC = [conf.IGNORED_STATIC] + } + + conf.IGNORED_STATIC = conf.IGNORED_STATIC.map(st => { + if (!st.startsWith('.')) { + st = '.' + st + } + return st + }) + // find WEB_ROOT manually from git. // show warning if this has to be done, needs a few hundred ms if (!conf.WEB_ROOT || !conf.URL) { @@ -221,16 +322,25 @@ export const runConfig = async () => { conf.HOIST = conf.HOIST || [] // set to true to get babel build info - conf.BABEL = conf.BABEL || {} - - conf.BABEL = { - DEBUG: false, - REMOVE_CHECK_PROPS: conf.IS_PROD, - MINIFY: conf.IS_PROD, - USE_PRESETS: true, - KEEP_COMMENTS: conf.IS_DEV, - ...conf.BABEL, + if (conf.BABEL) { + log.warn('W_DEPRECATED', 'config.BABEL is deprecated.') } + // merge commandline arguments into config. + // --keep-client = KEEP_CLIENT + // --no-mangle-names = NO_MANGLE_NAMES + // --keep-console = KEEP_CONSOLE + // --keep-dead-code = KEEP_DEAD_CODE + // --keep-debugger = KEEP_DEBUGGER + // --no-check-links = NO_CHECK_LINKS + // --no-check-links-remote = NO_CHECK_LINKS_REMOTE + // --no-check-links-exit = NO_CHECK_LINKS_EXIT + Object.entries(args).map(([k, v]) => { + const snaked = cases.snakeCaps(k) + conf[snaked] = v || v === '' + }) + + conf.__DEPRECATED__ = [...scriptKeys, ...addKeys].filter(k => conf[k] && conf[k].length) + return conf } diff --git a/src/defaultApp.mjs b/src/defaultApp.mjs new file mode 100644 index 00000000..e85c835d --- /dev/null +++ b/src/defaultApp.mjs @@ -0,0 +1,12 @@ +export const defaultApp = { + state: {}, + actions: {}, + effects: {}, + helpers: {}, + cookies: {}, + subscriptions: [], + lib: {}, + init: [], + server: {}, + links: [], +} diff --git a/src/index.mjs b/src/index.mjs index d142116c..8bec7d06 100644 --- a/src/index.mjs +++ b/src/index.mjs @@ -1 +1,5 @@ -export colors from './themes/colors.mjs' +export { colors } from './themes/colors.mjs' +export { reset } from './themes/reset.css.mjs' +export { replaceSlashSlash } from './lib/index.mjs' + +export { renderToString } from '@magic/hyperapp' diff --git a/src/lib/CHECK_PROPS.mjs b/src/lib/CHECK_PROPS.mjs index 9612c8a2..fe58157d 100644 --- a/src/lib/CHECK_PROPS.mjs +++ b/src/lib/CHECK_PROPS.mjs @@ -1,21 +1,16 @@ -export const CHECK_PROPS = (props, propTypeDecl, name) => { - const currentPage = - app && app.state ? app.state.url.replace(config.WEB_ROOT, '/') : 'Unknown on client.' +export const CHECK_PROPS = (props, propTypeDecl, name, log) => { + const currentPage = app && app.state?.url ? app.state.url : 'Unknown on client.' - if (!name) { - const err = new Error() - console.error( - `CHECK_PROPS: expected Module name as third argument ${err.stack} on page ${currentPage}`, - ) - return + if (log === false) { + log = () => {} + } else if (!log) { + log = e => console.log(e.code, e.message) } - if (!propTypeDecl) { - const err = new Error() - console.error( - `CHECK_PROPS: expected propTypes as second argument ${err.stack} on page ${currentPage}`, - ) - return + const error = (msg, code = 'E_CHECK_PROPS') => { + const err = new Error(msg) + err.code = code + return err } const is = (e, ...types) => @@ -32,13 +27,31 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { is.null = e => e === null is.promise = e => e instanceof Promise + if (!propTypeDecl) { + const err = error('expected propTypes as second argument', `E_CHECK_PROPS_${currentPage}`) + log(err) + + return false + } + + if (!name) { + const err = error('expected Module name as third argument', 'E_CHECK_PROPS') + log(`${err.stack} on page ${currentPage}`) + + return false + } + let propTypes = propTypeDecl[name] if (!is.array(propTypes)) { - console.error( - `CHECK_PROPS: expected propTypes to be an array. received: ${propTypes} on page ${currentPage} in component ${name}`, + const err = error( + `expected propTypes to be an array. received: ${propTypes} on page ${currentPage} in component ${name}`, + 'E_CHECK_PROPS', ) - return + + log.error(err) + + return false } if (!propTypes[0].key) { @@ -46,22 +59,67 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { propTypes = pT if (FALLBACK && typeof props === FALLBACK.type) { - return + return true } } - propTypes.map(propType => { - const { key, required, type } = propType + const errors = [] + + propTypes.forEach(propType => { + const { key, required, type, oneOf, someOf } = propType let value = props[key] - const types = Array.isArray(type) ? type : [type] + const types = is.array(type) ? type : [type] if (!required && !types.includes('undefined')) { types.push('undefined') } + + if (oneOf) { + if (!value && !required) { + value = propType.default + } + + const includes = oneOf.includes(value) + if (!includes && value !== propType.default) { + const err = error( + `${name} needs value to be one of [${oneOf + .filter(a => a) + .join(', ')}]. received ${value}`, + 'E_CHECK_PROPS_ONEOF_MISMATCH', + ) + errors.push(err) + } + } + + if (someOf) { + if (!value && !required) { + value = propType.default + } + + if (!is.array(value)) { + const err = error( + `${name} has someOf, someOf needs value to be an array. + received ${value}, which is a ${typeof value}`, + 'E_CHECK_PROPS_SOMEOF_ARG_NOT_ARRAY', + ) + errors.push(err) + } + + const includes = !value.some(inc => !someOf.includes(inc)) + if (!includes && value !== propType.default) { + const err = error( + `${name} needs value to be one of [${someOf.join(', ')}]. received ${value}`, + 'E_CHECK_PROPS_SOME_OF_ARG_MISMATCH', + ) + + errors.push(err) + } + } + const match = is(value, ...types) - if (Array.isArray(required)) { + if (is.array(required)) { if (!match) { const altExists = required.filter(key => is(props[key], ...types)) if (altExists.length) { @@ -74,9 +132,11 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { const typeString = types.length > 1 ? `["${types.join(', "')}"]` : types[0] if (!is(value, ...types)) { - console.error( + const err = error( `${name} needs props.${key} to be ${typeInfo} ${typeString}. received ${typeof value}`, + 'E_CHECK_PROPS_TYPE_MISMATCH', ) + errors.push(err) } else if (required) { if (typeof value === 'object' && !Object.keys(value).length) { let typeString = '' @@ -89,7 +149,11 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { } typeString += ' object' } - console.error(`${name} needs props.${key} to be a non empty ${typeString}`) + const err = error( + `${name} needs props.${key} to be a non empty ${typeString}`, + 'E_CHECK_PROPS_PROP_EMPTY', + ) + errors.push(err) } } @@ -98,9 +162,17 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { const { max = Number.MAX_SAFE_INTEGER, min = Number.MIN_SAFE_INTEGER } = propType if (max && value > max) { - console.error(`${name}: number expected to be <= ${max}, was ${value}`) + const err = error( + `${name} number expected to be <= ${max}, was ${value}`, + 'E_CHECK_PROPS_NUMBER_TOO_BIG', + ) + errors.push(err) } else if (min > 0 && value < min) { - console.error(`${name}: number expected to be >= ${min}, was ${value}`) + const err = error( + `${name} number expected to be >= ${min}, was ${value}`, + 'E_CHECK_PROPS_NUMBER_TOO_SMALL', + ) + errors.push(err) } } @@ -108,9 +180,17 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { const { max = 500, min = 0 } = propType if (max && value.length > max) { - console.error(`${name}: string length expected to be <= ${max}, length was ${value.length}`) + const err = error( + `${name} string length expected to be <= ${max}, length was ${value.length}`, + 'E_CHECK_PROPS_STRING_TO_LONG', + ) + errors.push(err) } else if (min > 0 && value.length < min) { - console.error(`${name}: string length expected to be >= ${min}, length was ${value.length}`) + const err = error( + `${name} string length expected to be >= ${min}, length was ${value.length}`, + 'E_CHECK_PROPS_STRING_TO_SHORT', + ) + errors.push(err) } } @@ -118,7 +198,11 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { const { item } = propType if (!is(value, 'array')) { - console.error(`${name} needs props.${key} to be an array. received ${typeof value}`) + const err = error( + `${name} needs props.${key} to be an array. received ${typeof value}`, + 'E_CHECK_PROPS_ARRAY_MISMATCH', + ) + errors.push(err) } value.forEach(val => { @@ -129,39 +213,50 @@ export const CHECK_PROPS = (props, propTypeDecl, name) => { : item.type[0] : item.type - if (!is(val, item.type)) { - console.error( - `${name} has item that is expected to be ${typeInfo} ${typeString}, received ${typeof val}`, - ) - } - - if (item.type === 'object') { + if (item.type.includes('object') && is(val, 'object')) { item.keys.forEach(iKey => { const v = val[iKey.key] if (!is(v, iKey.type)) { const typeInfo = is(iKey.type, 'array') && iKey.type.length > 1 ? 'one of' : 'a' - const typeString = Array.isArray(iKey.type) - ? iKey.type.length > 1 - ? `["${iKey.type.join(', "')}"]` - : iKey.type[0] - : iKey.type - console.error( + let typeString = '' + if (Array.isArray(iKey.type)) { + if (iKey.type.length > 1) { + typeString = `["${iKey.type.join(', "')}"]` + } else { + typeString = iKey.type[0] + } + } else { + typeString = iKey.type + } + + const err = error( `${name} expects item.${ iKey.key } to be ${typeInfo} ${typeString}, received ${typeof v}, on page ${currentPage}`, + 'E_CHECK_PROPS_OBJECT_KEY_MISMATCH', ) + + errors.push(err) } }) - } else if (!is(val, item.type)) { - console.error( - `${name} has item that is expected to be ${typeInfo} ${typeString}, received ${typeof val}, on page ${currentPage}`, - ) + } else { + const type = is(item.type, 'array') ? item.type : [item.type] + if (!is(val, ...type)) { + const err = error( + `${name} has item that is expected to be ${typeInfo} ${typeString}, received ${typeof val}, on page ${currentPage}`, + 'E_CHECK_PROPS_ARRAY_ITEM_MISMATCH', + ) + errors.push(err) + } } - // console.log({val, item: propType.item}) }) } }) - return true + if (log.error) { + errors.forEach(log.error) + } + + return !errors.length } diff --git a/src/lib/addEmbeddedCss.mjs b/src/lib/addEmbeddedCss.mjs new file mode 100644 index 00000000..8d672b59 --- /dev/null +++ b/src/lib/addEmbeddedCss.mjs @@ -0,0 +1,12 @@ +import is from '@magic/types' +import error from '@magic/error' + +export const addEmbeddedCss = file => { + const contents = app.static[file] + + if (is.empty(contents) || !is.function(contents.toString)) { + throw error(`Empty css file: ${file}`, 'E_PREPEND_CSS_EMPTY') + } + + return contents.toString() +} diff --git a/src/lib/addJsFiles.mjs b/src/lib/addJsFiles.mjs new file mode 100644 index 00000000..a6b52dda --- /dev/null +++ b/src/lib/addJsFiles.mjs @@ -0,0 +1,31 @@ +import is from '@magic/types' +import error from '@magic/error' + +import { createHash } from './createHash.mjs' +import { replaceSlashSlash } from './replaceSlashSlash.mjs' + +export const addJsFiles = ({ js = [], WEB_ROOT }) => { + if (is.empty(js)) { + return + } + + if (!is.array(js)) { + js = [js] + } + + const hashes = Object.fromEntries( + js.map(({ src }) => { + const staticSrc = src.replace(WEB_ROOT, '') + const fileContent = app.static[replaceSlashSlash(`/${staticSrc}`)] + + if (!fileContent) { + throw error(`script ${staticSrc} could not be loaded`, 'E_EXTERNAL_SCRIPT') + } + + const fileHash = createHash(fileContent) + return [staticSrc, fileHash] + }), + ) + + return hashes +} diff --git a/src/lib/applyWebRoot.mjs b/src/lib/applyWebRoot.mjs deleted file mode 100644 index 8a9ad081..00000000 --- a/src/lib/applyWebRoot.mjs +++ /dev/null @@ -1,18 +0,0 @@ -export const applyWebRoot = ({ ENV, WEB_ROOT }, str) => { - if (!WEB_ROOT || WEB_ROOT === '/') { - return str - } - - if (!WEB_ROOT.startsWith('/')) { - WEB_ROOT = `/${WEB_ROOT}` - } - if (!WEB_ROOT.endsWith('/')) { - WEB_ROOT = `${WEB_ROOT}/` - } - - return str - .replace(/href="\//gm, `href="${WEB_ROOT}`) - .replace(/href='\//gm, `href='${WEB_ROOT}`) - .replace(/src="\//gm, `src="${WEB_ROOT}`) - .replace(/src='\//gm, `src='${WEB_ROOT}`) -} diff --git a/src/lib/babel/dead_code.mjs b/src/lib/babel/dead_code.mjs index dbe8db5d..70454d93 100644 --- a/src/lib/babel/dead_code.mjs +++ b/src/lib/babel/dead_code.mjs @@ -1,4 +1,7 @@ import is from '@magic/types' +import cases from '@magic/cases' + +import { handleLink } from '../handleLink.mjs' const isModuleTag = (name, moduleNames) => moduleNames.includes(name) @@ -10,28 +13,8 @@ const used = { tags: new Set(), } -const handleLink = (path, config) => { - const { node } = path - const href = node.value.value - - // console.log('href', href) - if (!href.startsWith(config.WEB_ROOT)) { - const isLocal = href.startsWith('/') && !href.startsWith('//') - const isHash = href.startsWith('#') || href.startsWith('/#') - const isExtension = href.startsWith('-') - - // if (isExtension) { - // console.log('is extension', href, path) - // } - - if (isLocal || isHash) { - node.value.value = `${config.WEB_ROOT}${href}`.replace(/\/\//g, '/') - } - } -} - // collect all used modules, htmlTags, actions, effects, helpers -const findUsedSpells = (t, app, config) => path => { +const findUsedSpells = (t, app, WEB_ROOT) => path => { // find used modules and html tags in app const moduleNames = Object.keys(app.modules) @@ -48,13 +31,20 @@ const findUsedSpells = (t, app, config) => path => { let name if (t.isMemberExpression(callee)) { name = object.name + + if (object.name === 'lib') { + name = callee.property.name + } } else if (key) { name = key.name } - if (Object.keys(app.lib).includes(name)) { - if (is.string(app.lib[name])) { - used.lib[name] = {} + if (name) { + const kebabName = cases.kebab(name) + if (Object.keys(app.lib).includes(kebabName)) { + if (is.string(app.lib[kebabName])) { + used.lib[name] = {} + } } } } else { @@ -64,18 +54,17 @@ const findUsedSpells = (t, app, config) => path => { if (t.isObjectProperty(path.node)) { if (path.node.key) { - const validKeys = ['src', 'logo'] //, 'href', 'to'] - if (path.node.key.name) { - const { name } = path.node.key + const validKeys = ['src', 'logo', 'href', 'to'] + if (t.isIdentifier(path.node.key)) { if (path.node.value.value) { - if (validKeys.includes(name)) { - handleLink(path, config) + if (validKeys.includes(path.node.key.name)) { + path.node.value.value = handleLink({ href: path.node.value.value, app, WEB_ROOT }) } } - } else if (path.node.key.value) { + } else if (t.isStringLiteral(path.node.key)) { const { value: name } = path.node.key if (validKeys.includes(name)) { - handleLink(path, config) + path.node.value.value = handleLink({ href: path.node.value.value, app, WEB_ROOT }) } } } @@ -89,7 +78,7 @@ const findUsedSpells = (t, app, config) => path => { if (t.isMemberExpression(action.object)) { const { name: objName } = action.object.object const { name: propName } = action.object.property - console.log({ objName, propName }) + // console.log({ objName, propName }) if (type !== objName) { used[type][objName] = used[type][objName] || {} used[type][objName][propName] = {} @@ -100,6 +89,7 @@ const findUsedSpells = (t, app, config) => path => { } else if (t.isIdentifier(action.object)) { const { name: objName } = action.object const { name: propName } = action.property + if (objName !== type) { used[type][objName] = used[type][objName] || {} used[type][objName][propName] = {} @@ -122,8 +112,14 @@ const findUsedSpells = (t, app, config) => path => { } else if (t.isIdentifier(helper.object)) { const { name: objName } = helper.object const { name: propName } = helper.property - used[type][objName] = used[type][objName] || {} - used[type][objName][propName] = {} + + if (objName !== type) { + used[type][objName] = used[type][objName] || {} + used[type][objName][propName] = {} + } else { + used[type] = used[type] || {} + used[type][propName] = {} + } } } } @@ -178,8 +174,15 @@ const removeUnused = (t, app) => path => { } else { if (name === 'lib') { // remove unused lib imports - // console.log(name, path.node, path.node.init.properties) + // if (path.node.init && path.node.init.properties) { + // const { properties } = path.node.init + // properties.forEach(prop => { + // console.log(prop.key.name, Object.keys(used.lib).includes(prop.key.name)) + // }) + // console.log(used.lib); + // } } + if (name === 'actions') { // remove unused actions path.node.init.properties.forEach(prop => { @@ -215,15 +218,16 @@ const removeUnused = (t, app) => path => { } } -export default (app, config) => ({ types: t }) => ({ - visitor: { - Program: { - enter(path) { - path.traverse({ enter: findUsedSpells(t, app, config) }) - }, - exit(path) { - path.traverse({ enter: removeUnused(t, app, config) }) +export default (app, { WEB_ROOT }) => + ({ types: t }) => ({ + visitor: { + Program: { + enter(path) { + path.traverse({ enter: findUsedSpells(t, app, WEB_ROOT) }) + }, + exit(path) { + path.traverse({ enter: removeUnused(t, app) }) + }, }, }, - }, -}) + }) diff --git a/src/lib/babel/index.mjs b/src/lib/babel/index.mjs index b8d32240..4448b390 100644 --- a/src/lib/babel/index.mjs +++ b/src/lib/babel/index.mjs @@ -1,37 +1,27 @@ import magic from './dead_code.mjs' export const getBabelConf = (app, config) => { - const { - CLIENT_LIB_NAME, - BABEL_DEBUG, - REMOVE_CHECK_PROPS, - MINIFY, - USE_PRESETS, - KEEP_COMMENTS, - } = config.BABEL + const { CLIENT_LIB_NAME, BABEL_DEBUG, REMOVE_CHECK_PROPS, MINIFY, USE_PRESETS, KEEP_COMMENTS } = + config.BABEL const presets = [ [ '@babel/preset-env', { targets: { - browsers: 'last 2 versions', + firefox: 75, + chrome: 60, + edge: 80, + safari: 12, }, forceAllTransforms: true, ignoreBrowserslistConfig: true, - // modules: false, debug: BABEL_DEBUG, }, ], ] - const plugins = [ - ['@babel/plugin-transform-react-jsx', { pragma: 'h' }], - magic(app, config), - '@babel/plugin-transform-arrow-functions', - '@babel/plugin-proposal-object-rest-spread', - '@babel/plugin-proposal-export-namespace-from', - ] + const plugins = [['@babel/plugin-transform-react-jsx', { pragma: 'h' }], magic(app, config)] if (REMOVE_CHECK_PROPS) { plugins.push([ @@ -43,28 +33,29 @@ export const getBabelConf = (app, config) => { } if (MINIFY) { - const { argv } = process - const minify = !argv.includes('--no-minify') - if (minify) { - presets.push([ - 'minify', - { - mangle: { - topLevel: true, - }, - removeConsole: !argv.includes('--keep-console'), - removeDebugger: !argv.includes('--keep-debugger'), + presets.push([ + 'minify', + { + // this might sometimes break pages in production. + // "too much recursion". + // 17.02.2021: should be fixed. still left the comment for future reference + mangle: { + topLevel: true, }, - ]) - } + removeConsole: !config.KEEP_CONSOLE, + removeDebugger: !config.KEEP_DEBUGGER, + }, + ]) } + const sourceMaps = config.ENV === 'development' ? 'inline' : false + return { filename: `${CLIENT_LIB_NAME}.js`, minified: MINIFY, comments: KEEP_COMMENTS, configFile: false, - sourceMaps: false, + sourceMaps, presets: USE_PRESETS ? presets : [], plugins, } diff --git a/src/lib/checkLinks.mjs b/src/lib/checkLinks.mjs new file mode 100644 index 00000000..01f8068d --- /dev/null +++ b/src/lib/checkLinks.mjs @@ -0,0 +1,84 @@ +import log from '@magic/log' + +import { httpGet } from './httpGet.mjs' +import { isPageUrl } from './isPageUrl.mjs' +import { isHashedUrl } from './isHashedUrl.mjs' +import { isStaticUrl } from './isStaticUrl.mjs' + +const redirectStatusCodes = [301, 302, 303, 307, 308] + +export const checkLinks = async ({ + staticUrls, + links, + pages, + ignoredLinks = [], + noRemote = false, + root, + dev, +}) => { + const linkResolvers = links.map(async link => { + if (link.startsWith(root)) { + if (isPageUrl(pages, link)) { + return + } else if (isHashedUrl(pages, link)) { + return + } else if (isStaticUrl(staticUrls, link, root)) { + return + } + } else if (link.startsWith('mailto:')) { + return + } else if (link.includes('://') || link.startsWith('//')) { + if (noRemote || dev || ignoredLinks.includes(link)) { + return + } + + const { statusCode, headers, error } = await httpGet(link) + + if (statusCode === 200) { + return + } else if (redirectStatusCodes.includes(statusCode)) { + if (headers.location) { + log.warn( + 'W_CHECKLINKS_REDIRECT', + 'There is a link in your app that is getting redirected.\nto make this warning disappear: change:', + link, + 'to:', + headers.location, + ) + return + } else { + log.warn( + 'W_CHECKLINKS_REDIRECT', + 'Weird http redirect. Please file an issue: https://github.com/magic/core/issues/', + { statusCode, headers }, + ) + return + } + } else if (error) { + if (error.code === 'EAI_AGAIN') { + log.error('E_CHECKLINKS_HOSTNAME_NOT_FOUND', 'could not resolve hostname:', link) + + return + } + } else if (statusCode === 404) { + log.error('E_CHECKLINKS_404', '404 not found for link:', link) + return + } + } + + return link + }) + + const unresolvedLinks = await Promise.all(linkResolvers) + const filteredUnresolvedLinks = unresolvedLinks.filter(a => a) + + if (filteredUnresolvedLinks.length) { + log.error( + 'E__CHECKLINKS_UNRESOLVED', + 'links could not be resolved:', + JSON.stringify(filteredUnresolvedLinks, null, 2), + ) + } + + return filteredUnresolvedLinks +} diff --git a/src/lib/compress.mjs b/src/lib/compress.mjs deleted file mode 100644 index 1efbf6f9..00000000 --- a/src/lib/compress.mjs +++ /dev/null @@ -1,42 +0,0 @@ -import fs from '@magic/fs' -import log from '@magic/log' - -import { getFiles } from './getFiles.mjs' -import { getFileType } from './getFileType.mjs' - -export const compress = async () => { - // both zip and compress get overwritten if node-zopfli-es exists - let zip - let compress = () => - log.warn( - 'node-zopfli-es is not installed.', - ` - Magic will continue happily, but none of your files will be minified. - to fix this, please run - - npm install node-zopfli-es - - this needs xcode on macos, which might be the reason you see this error. - `, - ) - - try { - zip = await import('node-zopfli-es') - } catch (e) {} - - if (zip) { - ;(async (zippable, images) => { - const files = await getFiles(config.DIR.PUBLIC) - await Promise.all( - files - .filter(file => zippable.includes(getFileType(file))) - .filter(file => !images.includes(getFileType(file))) - .map(async file => { - const fileContent = await fs.readFile(file) - const zipped = await zip.gzip(fileContent) - await fs.writeFile(`${file}.gz`, zipped) - }), - ) - })() - } -} diff --git a/src/lib/contentTypes.mjs b/src/lib/contentTypes.mjs deleted file mode 100644 index a2bb2877..00000000 --- a/src/lib/contentTypes.mjs +++ /dev/null @@ -1,29 +0,0 @@ -export const contentTypes = { - css: 'text/css', - js: 'application/javascript', - mjs: 'application/javascript', - html: 'text/html', - json: 'application/json', - xml: 'application/xml', - zip: 'application/zip', - pdf: 'application/pdf', - doc: 'application/msword', - docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - xls: 'application/vnd.ms-excel', - xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - ppt: 'application/vnd.ms-powerpoint', - pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - odt: 'application/vnd.oasis.opendocument.text', - mpeg: 'audio/mpeg', - mpg: 'audio/mpg', - ogg: 'audio/ogg', - csv: 'text/csv', - text: 'text/plain', - txt: 'text/plain', - png: 'image/png', - jpeg: 'image/jpeg', - jpg: 'image/jpg', - gif: 'image/gif', - ico: 'image/x-icon', - webp: 'image/webp', -} diff --git a/src/lib/createFileHash.mjs b/src/lib/createFileHash.mjs deleted file mode 100644 index 2b7ebd94..00000000 --- a/src/lib/createFileHash.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import crypto from 'crypto' - -export const createFileHash = str => { - const hash = crypto.createHash('sha384') - - hash.update(str) - const value = hash.digest('base64') - - return `sha384-${value}` -} diff --git a/src/lib/createHash.mjs b/src/lib/createHash.mjs new file mode 100644 index 00000000..fe5716dd --- /dev/null +++ b/src/lib/createHash.mjs @@ -0,0 +1,22 @@ +import crypto from 'crypto' + +import is from '@magic/types' +import error from '@magic/error' + +export const createHash = content => { + if (!is.buffer(content) && !is.string(content)) { + throw error(`createHash(content) expects a string or buffer, got ${typeof content}`, 'ARG_TYPE') + } + + if (is.empty(content)) { + throw error('createHash(content) expects a non empty string or buffer', 'ARG_EMPTY') + } + + const hash = crypto.createHash('sha384') + + hash.update(content) + + const digested = hash.digest('base64') + + return `sha384-${digested}` +} diff --git a/src/lib/findConfigFile.mjs b/src/lib/findConfigFile.mjs new file mode 100644 index 00000000..5ce828e5 --- /dev/null +++ b/src/lib/findConfigFile.mjs @@ -0,0 +1,75 @@ +import path from 'path' + +import fs from '@magic/fs' +import log from '@magic/log' + +import { replacePathSepForImport } from './replacePathSepForImport.mjs' + +export const findConfigFile = async (ROOT, magicConfigNames, oldConfigName, silent = false) => { + let filePath + + let configFound = false + + await Promise.all( + magicConfigNames.map(async name => { + const fd = path.join(ROOT, name) + const exists = await fs.exists(fd) + + if (exists) { + if (configFound) { + const msg = + 'Found multiple config files. Make sure only one of the following files exist:\n' + const names = magicConfigNames.map(name => path.join(ROOT, name)).join('\n') + + log.warn('W_MULTI_CONFIG', msg, names, '\n') + } + + configFound = true + filePath = replacePathSepForImport(fd) + } + }), + ) + + if (!filePath) { + const oldConfigFd = path.join(ROOT, oldConfigName) + const oldConfigExists = await fs.exists(oldConfigFd) + + if (oldConfigExists) { + filePath = replacePathSepForImport(oldConfigFd) + + log.warn( + 'W_OLD_CONFIG', + 'You are using config.mjs, which is the old config. please rename the file to magic.js', + ) + } + } + + if (!filePath) { + if (!silent) { + log.error( + 'E_NO_CONFIG', + `No config file found. Please create ${ROOT}/magic.js (https://magic.github.io/core/files/#config)`, + ) + log(` +Example Config: + +export default { + ROOT: 'src', ${log.paint('green', '// the directory your pages, modules and themes live in')} + PUBLIC: 'docs', ${log.paint( + 'green', + '// the output directory, docs gets accepted by github and gitlab pages', + )} + THEME: 'example', ${log.paint( + 'green', + '// the @magic-theme you want to use. needs to be installed.', + )} + URL: 'https://example.com', ${log.paint('green', '// the root url of the site')} + WEB_ROOT: '/', ${log.paint('green', '// gets appended to the URL, should start and end with a /')} +}`) + + process.exit() + } + } + + return filePath +} diff --git a/src/lib/findModuleStyles.mjs b/src/lib/findModuleStyles.mjs index c9eb72e6..4fd7d347 100644 --- a/src/lib/findModuleStyles.mjs +++ b/src/lib/findModuleStyles.mjs @@ -9,8 +9,8 @@ export const findModuleStyles = (modules, vars, parent) => { .forEach(([name, mod]) => { if (!is.empty(mod.style)) { let style = mod.style - if (is.function(mod.style)) { - style = mod.style(vars) + if (is.function(style)) { + style = style(vars) } let selector = `.${name}` diff --git a/src/lib/getBlog.mjs b/src/lib/getBlog.mjs index 21e48927..b6d4c2f5 100644 --- a/src/lib/getBlog.mjs +++ b/src/lib/getBlog.mjs @@ -4,14 +4,14 @@ import error from '@magic/error' import fs from '@magic/fs' import log from '@magic/log' -export const getBlog = async () => { +export const getBlog = async ({ BLOG_DIR, ROOT }) => { let files try { - files = await fs.getFiles(config.BLOG_DIR) + files = await fs.getFiles(BLOG_DIR) } catch (e) { - if (config.BLOG_DIR.startsWith(config.ROOT)) { - log.warn('NOEXIST', `${config.BLOG_DIR} does not exist or does not contain blog posts`) + if (BLOG_DIR.startsWith(ROOT)) { + log.warn('NOEXIST', `${BLOG_DIR} does not exist or does not contain blog posts`) } throw error(e) diff --git a/src/lib/getContentType.mjs b/src/lib/getContentType.mjs deleted file mode 100644 index ca07a670..00000000 --- a/src/lib/getContentType.mjs +++ /dev/null @@ -1,13 +0,0 @@ -import { contentTypes } from './contentTypes.mjs' -import { getFileType } from './getFileType.mjs' - -export const getContentType = uri => { - const fileType = getFileType(uri) - let contentType = 'text/plain' - - if (contentTypes[fileType]) { - contentType = contentTypes[fileType] - } - - return contentType -} diff --git a/src/lib/getGitConfig.mjs b/src/lib/getGitConfig.mjs index e41802ac..90c71a38 100644 --- a/src/lib/getGitConfig.mjs +++ b/src/lib/getGitConfig.mjs @@ -2,24 +2,20 @@ import log from '@magic/log' import { xc } from '../lib/xc.mjs' -export const getGitConfig = async (config = {}) => { - if (config.GIT && config.GIT.ORIGIN) { - if (!config.GIT.BRANCH) { - config.GIT.BRANCH = 'gh-pages' +export const getGitConfig = async GIT => { + if (GIT && GIT.ORIGIN) { + if (!GIT.BRANCH) { + GIT.BRANCH = 'gh-pages' } - return config.GIT + return GIT } - log.info('config.GIT missing ORIGIN') - const git = config.GIT || {} + log.info('GIT missing ORIGIN') + const git = GIT || {} const data = await xc('git remote -v') if (data.stdout) { - git.ORIGIN = data.stdout - .split('\n')[1] - .split('\t')[1] - .replace('(push)', '') - .trim() + git.ORIGIN = data.stdout.split('\n')[1].split('\t')[1].replace('(push)', '').trim() git.BRANCH = git.BRANCH || 'gh-pages' return git diff --git a/src/lib/getPages.mjs b/src/lib/getPages.mjs index 792d84c7..99611b14 100644 --- a/src/lib/getPages.mjs +++ b/src/lib/getPages.mjs @@ -1,12 +1,9 @@ -import path from 'path' - -import log from '@magic/log' import error from '@magic/error' import fs from '@magic/fs' -export const getPages = async () => { +export const getPages = async ({ dir, root }) => { try { - const files = await fs.getFiles(config.DIR.PAGES) + const files = await fs.getFiles(dir) if (files.length === 0) { // this actually throws the error below throw new Error('no files') @@ -14,16 +11,13 @@ export const getPages = async () => { return files } catch (e) { - if (config.DIR.PAGES.startsWith(config.ROOT)) { - throw error( - `${config.DIR.PAGES} does not exist or does not contain pages`, - 'E_PAGE_DIR_NOEXIST', - ) + if (dir.startsWith(root)) { + throw error(`${dir} does not exist or does not contain pages`, 'E_PAGE_DIR_NOEXIST') // TODO: install an example and give the user a choice which one // const indexPage = "() => div('hello world')" - // const pagePath = path.join(config.DIR.PAGES, 'index.mjs') - // await fs.mkdirp(config.DIR.PAGES) + // const pagePath = path.join(dir, 'index.mjs') + // await fs.mkdirp(dir) // await fs.writeFile(pagePath, indexPage) //~ return await getPages() } diff --git a/src/lib/handleDependencies.mjs b/src/lib/handleDependencies.mjs deleted file mode 100644 index 82162bea..00000000 --- a/src/lib/handleDependencies.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import is from '@magic/types' - -import { stringifyObject } from './stringifyObject.mjs' - -export const handleDependencies = (name, component) => { - if (is.fn(component) && !is.case.upper(name[0])) { - return `const ${name} = C('${name}')\n` - } - - const views = Object.entries(component) - .filter(([name]) => is.case.upper(name[0])) - .map(([name, view]) => ({ - name, - view: view.toString(), - })) - - let comp = `const ${name} = ` - if (is.fn(component)) { - comp += `${component.toString()}\n` - } else { - comp += ' {\n' - } - - if (views.length) { - views.forEach(view => { - if (is.fn(component)) { - comp += `${name}.${view.name} = ${view.view}\n` - } else { - comp += ` ${view.name}: ${view.view},\n` - } - - if (config.ENV === 'development') { - if (component[view.name].props) { - const propString = stringifyObject(component[view.name].props) - if (is.fn(component)) { - comp += `${name}.${view.name}.props = ${propString}\n` - } else { - comp += `props: ${propString},\n` - } - } - } - }) - if (!is.fn(component)) { - comp += '}' - } - } - - if (config.ENV === 'development') { - if (component.props) { - comp += `${name}.props = ${stringifyObject(component.props)}\n` - } - } - return `${comp}\n` -} diff --git a/src/lib/handleLink.mjs b/src/lib/handleLink.mjs new file mode 100644 index 00000000..69364c17 --- /dev/null +++ b/src/lib/handleLink.mjs @@ -0,0 +1,87 @@ +import log from '@magic/log' + +import { replaceSlashSlash } from './replaceSlashSlash.mjs' + +export const handleLink = ({ app, href, parent = {}, WEB_ROOT }) => { + if (href.startsWith('mailto:')) { + return href + } + + if (href.startsWith(WEB_ROOT)) { + href = replaceSlashSlash(href) + app.links.push(href) + return href + } + + if (href.startsWith('/#')) { + href = href.substr(1) + } + + let local = false + + if (href.startsWith('/') && !href.startsWith('//')) { + local = true + + if (!href.startsWith(WEB_ROOT)) { + href = `${WEB_ROOT}${href.substr(1)}` + } + } else if (href.startsWith('#')) { + local = true + + if (parent.to) { + href = `${parent.to}/${href}` + } else { + href = `${WEB_ROOT}/${href}` + } + } else if (href.startsWith('-')) { + if (parent.to) { + local = true + // no slash! + href = `${parent.to}${href}` + } else { + log.error( + 'E_PREPARE_STATE_LINKS_EXPANDED_LINK', + 'an expanded Link without Parent was found:', + href, + parent, + ) + } + } else if (!href.startsWith('http') && !href.startsWith('//')) { + log.error( + 'E_PREPARE_STATE_LINKS_UNKNOWN_HREF', + '@magic did not handle this href. Please file a bug at https://github.com/magic/core/issues', + { + href, + parent, + }, + ) + } + + if (local) { + href = replaceSlashSlash(href) + } else { + if (href.startsWith('//')) { + href = `//${replaceSlashSlash(href.substr(2))}` + } else { + href = href + .split('://') + .map(a => replaceSlashSlash(a)) + .join('://') + } + } + + app.links.push(href) + + return href +} + +// export const handleLink = (val, app) => { +// if (!val.startsWith(WEB_ROOT)) { +// if (val.startsWith('/') || val.startsWith('#') || val.startsWith('/#')) { +// val = replaceSlashSlash(`${WEB_ROOT}${val}`) +// } +// } + +// app.links.push(val) +// return val +// } diff --git a/src/lib/httpGet.mjs b/src/lib/httpGet.mjs new file mode 100644 index 00000000..0d65b282 --- /dev/null +++ b/src/lib/httpGet.mjs @@ -0,0 +1,22 @@ +import http from 'http' +import https from 'https' + +export const httpGet = url => + new Promise((resolve, reject) => { + const handler = url.startsWith('https') ? https : http + + const req = handler.get(url, res => { + const { statusCode, headers } = res + + const data = [] + res.on('data', d => { + data.push(d) + }) + + res.on('end', () => resolve({ statusCode, headers, data })) + + res.on('error', error => resolve({ error, statusCode })) + }) + + req.on('error', error => resolve({ error })) + }) diff --git a/src/lib/index.mjs b/src/lib/index.mjs index 9d040640..2870ac5a 100644 --- a/src/lib/index.mjs +++ b/src/lib/index.mjs @@ -1,19 +1,28 @@ +export { addEmbeddedCss } from './addEmbeddedCss.mjs' +export { addJsFiles } from './addJsFiles.mjs' export { addTrailingSlash } from './addTrailingSlash.mjs' -export { applyWebRoot } from './applyWebRoot.mjs' export { CHECK_PROPS } from './CHECK_PROPS.mjs' -// export { compress } from './compress.mjs' -export { createFileHash } from './createFileHash.mjs' +export { checkLinks } from './checkLinks.mjs' +export { createHash } from './createHash.mjs' +export { findConfigFile } from './findConfigFile.mjs' export { findModuleStyles } from './findModuleStyles.mjs' export { getBabelConf } from './babel/index.mjs' export { getBlog } from './getBlog.mjs' export { getGitConfig } from './getGitConfig.mjs' export { getPages } from './getPages.mjs' -export { handleDependencies } from './handleDependencies.mjs' +export { transformClient } from './swc/index.mjs' +export { handleLink } from './handleLink.mjs' export { handleStyleFunctions } from './handleStyleFunctions.mjs' +export { httpGet } from './httpGet.mjs' +export { isHashedUrl } from './isHashedUrl.mjs' export { isLocalPath } from './isLocalPath.mjs' -// export { minifyImages } from './minifyImages.mjs' +export { isModuleName } from './isModuleName.mjs' +export { isPageUrl } from './isPageUrl.mjs' +export { isStaticUrl } from './isStaticUrl.mjs' export { replacePathSepForImport } from './replacePathSepForImport.mjs' +export { replaceSlashSlash } from './replaceSlashSlash.mjs' +export { saveImport } from './saveImport.mjs' export { stringifyObject } from './stringifyObject.mjs' +export { uniqueMerge } from './uniqueMerge.mjs' export { writeFile } from './writeFile.mjs' -export { writeServer } from './writeServer.mjs' export { xc } from './xc.mjs' diff --git a/src/lib/isHashedUrl.mjs b/src/lib/isHashedUrl.mjs new file mode 100644 index 00000000..deb3e91a --- /dev/null +++ b/src/lib/isHashedUrl.mjs @@ -0,0 +1,13 @@ +export const isHashedUrl = (list, link) => { + if (!link.includes('#')) { + return false + } + + const [url, hash] = link.split('#') + const page = list.find(page => page.name === url) + if (!page) { + return false + } + + return page.rendered.includes(`id="${hash}"`) +} diff --git a/src/lib/isLocalPath.mjs b/src/lib/isLocalPath.mjs index 0d67201c..af5501cd 100644 --- a/src/lib/isLocalPath.mjs +++ b/src/lib/isLocalPath.mjs @@ -1,4 +1,6 @@ +import path from 'path' + export const isLocalPath = p => { const cwd = process.cwd() - return p.startsWith(cwd) && p !== cwd + return p.startsWith(cwd + path.sep) } diff --git a/src/lib/isModuleName.mjs b/src/lib/isModuleName.mjs new file mode 100644 index 00000000..26adb898 --- /dev/null +++ b/src/lib/isModuleName.mjs @@ -0,0 +1 @@ +export const isModuleName = name => name[0].toUpperCase() === name[0] diff --git a/src/lib/isPageUrl.mjs b/src/lib/isPageUrl.mjs new file mode 100644 index 00000000..0723287a --- /dev/null +++ b/src/lib/isPageUrl.mjs @@ -0,0 +1 @@ +export const isPageUrl = (list, link) => list.some(({ name }) => name === link) diff --git a/src/lib/isStaticUrl.mjs b/src/lib/isStaticUrl.mjs new file mode 100644 index 00000000..09d2ce8e --- /dev/null +++ b/src/lib/isStaticUrl.mjs @@ -0,0 +1,7 @@ +export const isStaticUrl = (list, link, root) => { + if (link.startsWith(root)) { + link = link.replace(root, '/') + } + + return list.includes(link) +} diff --git a/src/lib/mapPageObject.mjs b/src/lib/mapPageObject.mjs index 1981d969..c2fee94f 100644 --- a/src/lib/mapPageObject.mjs +++ b/src/lib/mapPageObject.mjs @@ -1,16 +1,19 @@ import is from '@magic/types' -export const mapPageObject = (o, key, c) => ([k, v]) => { - const isGlobal = c.global && c.global[key] && Object.keys(c.global[key]).includes(k) +export const mapPageObject = + (o, key, c) => + ([k, v]) => { + const isGlobal = c.global && c.global[key] && Object.keys(c.global[key]).includes(k) - if (!isGlobal) { - if (!is.defined(o[key])) { - o[key] = { - [k]: v, + if (!isGlobal) { + if (!is.defined(o[key])) { + o[key] = { + [k]: v, + } + } else if (is.undefined(o[key][k])) { + o[key][k] = v } - } else if (is.undefined(o[key][k])) { - o[key][k] = v } + + return o } - return o -} diff --git a/src/lib/minifyImages.mjs b/src/lib/minifyImages.mjs deleted file mode 100644 index 3598ea36..00000000 --- a/src/lib/minifyImages.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import path from 'path' - -import error from '@magic/error' - -import imagemin from 'imagemin' -import imageminGifsicle from 'imagemin-gifsicle' -import imageminMozjpeg from 'imagemin-mozjpeg' -import imageminPngquant from 'imagemin-pngquant' -import imageminSvgo from 'imagemin-svgo' - -import { getDirectories } from './getDirectories.mjs' - -export const minifyImages = async images => { - const imageGlob = `*.{${images.join(',')}}` - try { - const dirs = await getDirectories(config.DIR.STATIC) - - await Promise.all( - dirs.map(async dir => { - const input = [path.join(dir, imageGlob)] - const output = dir.replace(config.DIR.STATIC, config.DIR.PUBLIC) - - return await imagemin(input, output, { - plugins: [ - imageminMozjpeg(config.IMAGEMIN.JPG), - imageminPngquant(config.IMAGEMIN.PNG), - imageminGifsicle(config.IMAGEMIN.GIF), - imageminSvgo(config.IMAGEMIN.SVGO), - ], - }) - }), - ) - } catch (e) { - // do not throw if error is ENOENT - if (e.code !== 'ENOENT') { - throw error(e) - } - } -} diff --git a/src/lib/replacePathSepForImport.mjs b/src/lib/replacePathSepForImport.mjs index 4e5ba1f0..2b0204f6 100644 --- a/src/lib/replacePathSepForImport.mjs +++ b/src/lib/replacePathSepForImport.mjs @@ -1,4 +1,6 @@ -export const replacePathSepForImport = (p, sep) => { +import path from 'path' + +export const replacePathSepForImport = (p, sep = path.sep) => { if (sep !== '/') { if (sep === '\\') { return p.replace(/\\/gi, '/') diff --git a/src/lib/replaceSlashSlash.mjs b/src/lib/replaceSlashSlash.mjs new file mode 100644 index 00000000..5bace1bc --- /dev/null +++ b/src/lib/replaceSlashSlash.mjs @@ -0,0 +1 @@ +export const replaceSlashSlash = (str, replaceWith = '/') => str.replace(/\/\/+/g, replaceWith) diff --git a/src/lib/saveImport.mjs b/src/lib/saveImport.mjs new file mode 100644 index 00000000..a633db82 --- /dev/null +++ b/src/lib/saveImport.mjs @@ -0,0 +1,14 @@ +import path from 'node:path' +import { pathToFileURL } from 'node:url' + +export const saveImport = async p => { + try { + if (path.isAbsolute(p)) { + p = pathToFileURL(p) + } + + return await import(p) + } catch (e) { + return e + } +} diff --git a/src/lib/stringifyObject.mjs b/src/lib/stringifyObject.mjs index 164f79bb..e6b8f747 100644 --- a/src/lib/stringifyObject.mjs +++ b/src/lib/stringifyObject.mjs @@ -1,16 +1,26 @@ import is from '@magic/types' export const stringifyObject = (obj, indent = '') => { - indent = indent + ' ' + if (indent === false) { + indent = '' + } else { + indent += ' ' + } if (is.string(obj)) { - obj = `'${obj}'` + if (obj.includes("'")) { + obj = `"${obj}"` + } else { + obj = `'${obj}'` + } } else if (is.array(obj)) { obj = `[${obj.map(o => stringifyObject(o, indent)).join(',')}]` } else if (is.fn(obj)) { obj = obj.toString() + } else if (is.regex(obj)) { + obj = obj.toString() } else if (is.obj(obj)) { - let str = Object.entries(obj) + const str = Object.entries(obj) .sort(([a], [b]) => (a > b ? 1 : -1)) .map(([k, o]) => `${indent}'${k}': ${stringifyObject(o, indent)}`) .join(',\n') diff --git a/src/lib/swc/index.mjs b/src/lib/swc/index.mjs new file mode 100644 index 00000000..e3ac67be --- /dev/null +++ b/src/lib/swc/index.mjs @@ -0,0 +1,193 @@ +import swc from '@swc/core' +import is from '@magic/types' + +import { visit, used } from './visit.mjs' + +// Core magic variables that are not modules - cached as Set for O(1) lookup +const CORE_VARS = new Set([ + 'h', + 'app', + 'C', + 'initialState', + 'helpers', + 'actions', + 'effects', + 'pages', + 'lib', + 'subscriptions', + 'req', + 'res', + 'err', + 'val', +]) + +/** + * Collect all module variable names that are actually used in pages/object values + * These variables must be kept because they're referenced + */ +const collectUsedModuleVars = block => { + const usedVars = new Set() + + const traverse = node => { + if (!node || typeof node !== 'object') return + + if (node.type === 'ObjectExpression' && node.properties) { + for (const prop of node.properties) { + if (prop.value?.type === 'Identifier') { + usedVars.add(prop.value.value) + } + traverse(prop.value) + } + } + + for (const key of Object.keys(node)) { + if (key === 'span' || key === 'ctxt') continue + const child = node[key] + if (Array.isArray(child)) { + child.forEach(traverse) + } else if (child && typeof child === 'object') { + traverse(child) + } + } + } + + traverse(block) + return usedVars +} + +/** + * Remove unused module declarations from a BlockStatement + * Handles multi-declarator statements (const a = A, b = B) + */ +const removeUnusedFromBlock = (block, usedModules, usedModuleVars) => { + if (!block || block.type !== 'BlockStatement' || !block.stmts) { + return + } + + block.stmts = block.stmts.filter(stmt => { + // Only process variable declarations + if (stmt.type !== 'VariableDeclaration') { + return true + } + + // Handle each declarator individually + stmt.declarations = stmt.declarations.filter(decl => { + if (decl.id?.type !== 'Identifier') { + return true // Keep non-Identifier patterns (destructuring, etc.) + } + + const varName = decl.id.value + + // Skip core magic variables + if (CORE_VARS.has(varName)) { + return true + } + + // Skip if this variable is used elsewhere (e.g., in pages object) + if (usedModuleVars.has(varName)) { + return true + } + + // Check if it's a lowercase variable - only remove if not in usedModules + if (is.case.lower(varName[0])) { + if (!usedModules.has(varName)) { + return false // Remove this declaration + } + return true + } + + // Check if this is an uppercase module variable + if (is.case.upper(varName[0])) { + if (!usedModules.has(varName)) { + return false // Remove this declaration + } + return true + } + + return true + }) + + // Remove statement entirely if all declarators were removed + return stmt.declarations.length > 0 + }) +} + +/** + * Find the __MAGIC__ arrow function and remove unused modules from its body + */ +const filterMagicFunction = (ast, usedModules) => { + for (const item of ast.body) { + if (item.type === 'VariableDeclaration') { + for (const decl of item.declarations) { + if (decl.id?.value === '__MAGIC__' && decl.init?.type === 'ArrowFunctionExpression') { + const arrowFn = decl.init + // The body is either a BlockStatement directly or an object + if (arrowFn.body?.type === 'BlockStatement') { + // First collect which module variables are actually used + const usedModuleVars = collectUsedModuleVars(arrowFn.body) + removeUnusedFromBlock(arrowFn.body, usedModules, usedModuleVars) + } + } + } + } + } +} + +/** + * Transform the client code using SWC: + * 1. Parse the code to AST + * 2. Visit and transform the AST (tracks used modules) + * 3. Remove unused module declarations from __MAGIC__ function + * 4. Generate code from AST + * 5. Minify the output + */ +export const transformClient = async (code, app, config) => { + const fileName = `${config.CLIENT_LIB_NAME}.js` + + // Reset used modules tracking + used.modules = new Set() + // Cache modules as Set for O(1) lookups + used.moduleNames = new Set(Object.keys(app.modules)) + + // Parse the code to AST + const ast = await swc.parse(code, { + syntax: 'typescript', + jsx: true, + isModule: true, + target: 'es2017', + }) + + // Transform the AST - this tracks used modules and removes CHECK_PROPS calls + ast.body = ast.body.map(item => visit({ parent: item, app, config })) + + // Filter unused modules from app.modules + app.modules = Object.fromEntries( + Object.entries(app.modules).filter(([name]) => used.modules.has(name)), + ) + + // Remove unused module declarations from __MAGIC__ function body + filterMagicFunction(ast, used.modules) + + // Generate code from AST + const { code: generated } = await swc.print(ast, { + filename: fileName, + isModule: true, + }) + + // Minify the output + const { code: minified } = await swc.minify(generated, { + compress: { + dead_code: true, + drop_debugger: true, + drop_console: config.IS_PROD, + passes: 2, + unused: true, + }, + mangle: config.IS_PROD, + format: { + comments: false, + }, + }) + + return minified +} diff --git a/src/lib/swc/mergeSubModules.mjs b/src/lib/swc/mergeSubModules.mjs new file mode 100644 index 00000000..255abf0c --- /dev/null +++ b/src/lib/swc/mergeSubModules.mjs @@ -0,0 +1,13 @@ +import { isModuleName, uniqueMerge } from '../../lib/index.mjs' + +export const mergeSubModules = ({ used = {}, name, dependencies }) => { + if (isModuleName(name)) { + if (dependencies) { + used = uniqueMerge(dependencies, used) + const added = dependencies.modules.forEach(module => mergeSubModules({ name: module, used })) + used = uniqueMerge(added, used) + } + } + + return used +} diff --git a/src/lib/swc/moduleViewToString.mjs b/src/lib/swc/moduleViewToString.mjs new file mode 100644 index 00000000..b01c7da7 --- /dev/null +++ b/src/lib/swc/moduleViewToString.mjs @@ -0,0 +1,9 @@ +import is from '@magic/types' + +export const moduleViewToString = module => { + if (is.function(module)) { + return module.toString() + } else if (is.function(module?.View)) { + return module.View.toString() + } +} diff --git a/src/lib/swc/recursivelyResolveDependencies.mjs b/src/lib/swc/recursivelyResolveDependencies.mjs new file mode 100644 index 00000000..655cad24 --- /dev/null +++ b/src/lib/swc/recursivelyResolveDependencies.mjs @@ -0,0 +1,123 @@ +import swc from '@swc/core' + +import is from '@magic/types' + +import { stringifyObject, isModuleName, uniqueMerge } from '../../lib/index.mjs' +import { resolveDependencies } from '../../lib/swc/resolveDependencies.mjs' + +import { moduleViewToString } from './moduleViewToString.mjs' +import { mergeSubModules } from './mergeSubModules.mjs' + +export const recursivelyResolveDependencies = async ({ app, config }) => { + let totalUsed = { + modules: [], + lib: [], + actions: [], + effects: [], + subscriptions: [], + helpers: [], + pages: {}, + dependencies: {}, + resolved: [], + } + + const moduleDependencies = {} + const modulePages = {} + + { + const rootCode = app.View.toString() + + const hoistedString = `\nconst hoisted = ${app.hoisted}` + + const ast = await swc.parse(rootCode + hoistedString) + const usedInRoot = resolveDependencies({ parent: ast, app }) + totalUsed = uniqueMerge(usedInRoot, totalUsed) + } + + { + const { Page } = app.modules + const pageCode = Page.View.toString() + const pageAst = await swc.parse(pageCode) + const usedInPage = resolveDependencies({ parent: pageAst, app }) + totalUsed = uniqueMerge(usedInPage, totalUsed) + } + + { + const actionString = `const actions = ${stringifyObject(app.actions)}` + const effectString = `const effects = ${stringifyObject(app.effects)}` + const subString = `const subscriptions = [${app.subscriptions}]` + const libString = `const lib = ${stringifyObject(app.lib)}` + + const totalString = [actionString, effectString, subString, libString].join('\n') + const ast = await swc.parse(totalString) + const usedInFunctions = resolveDependencies({ parent: ast, app }) + totalUsed = uniqueMerge(usedInFunctions, totalUsed) + } + + await Promise.all( + Object.entries(app.modules).map(async ([name, module]) => { + const view = moduleViewToString(module) + const subViews = [] + + if (is.objectNative(module)) { + const modSubViews = Object.entries(module) + .filter(([name]) => isModuleName(name)) + .map(([_name, subModule]) => { + if (is.function(subModule)) { + return subModule.toString() + } else if (is.function(subModule.View)) { + return subModule.View.toString() + } + }) + + subViews.push(...modSubViews) + } + + const ast = await swc.parse(view) + let usedInModule = resolveDependencies({ parent: ast, app }) + + await Promise.all( + subViews.map(async view => { + const ast = await swc.parse(view) + const usedInSubModule = resolveDependencies({ parent: ast, app }) + usedInModule = uniqueMerge(usedInSubModule, usedInModule) + }), + ) + + moduleDependencies[name] = usedInModule + totalUsed = uniqueMerge(usedInModule, totalUsed) + }), + ) + + await Promise.all( + app.pages.map(async page => { + const pageView = page.View.toString() + const ast = await swc.parse(pageView) + let usedInPage = resolveDependencies({ parent: ast, app }) + + usedInPage.modules.forEach(moduleName => + mergeSubModules({ + used: usedInPage, + name: moduleName, + dependencies: moduleDependencies[moduleName], + }), + ) + + totalUsed.pages[page.name] = usedInPage + totalUsed = uniqueMerge(usedInPage, totalUsed) + + usedInPage.modules.forEach(moduleName => { + modulePages[moduleName] = modulePages[moduleName] || [] + modulePages[moduleName].push(page.name) + }) + }), + ) + + totalUsed.modulesByPage = modulePages + + totalUsed.singlePageModules = Object.entries(modulePages).filter( + ([name, pages]) => pages.length === 1, + ) + + return totalUsed +} diff --git a/src/lib/swc/resolveDependencies.mjs b/src/lib/swc/resolveDependencies.mjs new file mode 100644 index 00000000..5bb208aa --- /dev/null +++ b/src/lib/swc/resolveDependencies.mjs @@ -0,0 +1,200 @@ +import log from '@magic/log' + +const noopTypes = [ + 'StringLiteral', + 'BooleanLiteral', + 'NullLiteral', + 'NumericLiteral', + 'RegExpLiteral', + 'ThisExpression', + 'EmptyStatement', + 'Identifier', +] + +/* + * which types of member accesses we want to track in the used object + */ +const validTypes = ['lib', 'actions', 'effects', 'subscriptions', 'helpers'] + +const visit = ({ parent, app, used }) => { + if (!parent) { + return parent + } + + if (Array.isArray(parent)) { + return parent.map(n => visit({ parent: n, app, used })) + } + + if (parent.type === 'VariableDeclaration') { + parent.declarations = parent.declarations.map(decl => visit({ parent: decl, app, used })) + } else if (parent.type === 'ExpressionStatement') { + parent.expression = visit({ parent: parent.expression, app, used }) + } else if (parent.type === 'AssignmentExpression') { + parent.right = visit({ parent: parent.right, app, used }) + parent.left = visit({ parent: parent.left, app, used }) + } else if (parent.type === 'ArrowFunctionExpression') { + if (parent.params) { + parent.params = visit({ parent: parent.params, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + } else if (parent.body.callee) { + parent.body.callee = visit({ parent: parent.body.callee, app, used }) + parent.params = visit({ parent: parent.params, app, used }) + } else if (parent.body.stmts) { + parent.body.stmts = parent.body.stmts.map(stmt => visit({ parent: stmt, app, used })) + } else { + log.warn('unhandled ArrowFunctionExpression', parent) + } + } else if (parent.type === 'IfStatement') { + parent.test = visit({ parent: parent.test, app, used }) + parent.consequent = visit({ parent: parent.consequent, app, used }) + if (parent.alternate) { + parent.alternate = visit({ parent: parent.alternate, app, used }) + } + } else if (parent.type === 'CallExpression') { + parent.arguments = parent.arguments.map(arg => { + arg.expression = visit({ parent: arg.expression, app, used }) + return arg + }) + + parent.callee = visit({ parent: parent.callee, app, used }) + + if (parent.callee.type === 'Identifier') { + if (Object.keys(app.modules).includes(parent.callee.value)) { + if (!used.modules.includes(parent.callee.value)) { + used.modules.push(parent.callee.value) + } + } + } + } else if (parent.type === 'VariableDeclarator') { + parent.id = visit({ parent: parent.id, app, used }) + parent.init = visit({ parent: parent.init, app, used }) + } else if (parent.type === 'ObjectExpression') { + parent.properties = parent.properties.map(prop => visit({ parent: prop, app, used })) + } else if (parent.type === 'KeyValueProperty') { + parent.key = visit({ parent: parent.key, app, used }) + parent.value = visit({ parent: parent.value, app, used }) + } else if (parent.type === 'MemberExpression') { + parent.property = visit({ parent: parent.property, app, used }) + parent.object = visit({ parent: parent.object, app, used }) + + const { value: type } = parent.object + const { value: key } = parent.property + + if (used.hasOwnProperty(type) && !used[type].includes(key)) { + used[type].push(key) + } + + if (Object.keys(app.modules).includes(type) && type[0].toUpperCase() === type[0]) { + used.modules.push(`${type}.${key}`) + } + } else if (parent.type === 'ArrayExpression') { + parent.elements = parent.elements.map(ele => { + ele.expression = visit({ parent: ele.expression, undefined, app, used }) + return ele + }) + } else if (parent.type === 'ArrayPattern') { + parent.elements = parent.elements.map(ele => visit({ parent: ele, app, used })) + } else if (parent.type === 'TemplateLiteral') { + parent.quasis = parent.quasis.map(quasi => { + quasi.raw = visit({ parent: quasi.raw, app, used }) + quasi.cooked = visit({ parent: quasi.cooked, app, used }) + return quasi + }) + } else if (parent.type === 'SpreadElement') { + parent.arguments = visit({ parent: parent.arguments, app, used }) + } else if (parent.type === 'BlockStatement') { + parent.stmts = parent.stmts.map(stmt => visit({ parent: stmt, app, used })) + } else if (parent.type === 'ReturnStatement') { + parent.argument = visit({ parent: parent.argument, app, used }) + } else if (parent.type === 'ObjectPattern') { + parent.properties.map(prop => visit({ parent: prop, app, used })) + } else if (parent.type === 'AssignmentPattern') { + parent.left = visit({ parent: parent.left, app, used }) + parent.right = visit({ parent: parent.right, app, used }) + } else if (parent.type === 'AssignmentPatternProperty') { + parent.key = visit({ parent: parent.key, app, used }) + } else if (parent.type === 'ConditionalExpression') { + parent.test = visit({ parent: parent.test, app, used }) + parent.consequent = visit({ parent: parent.consequent, app, used }) + parent.alternate = visit({ parent: parent.alternate, app, used }) + } else if (parent.type === 'BinaryExpression') { + parent.left = visit({ parent: parent.left, app, used }) + parent.right = visit({ parent: parent.right, app, used }) + } else if (parent.type === 'UnaryExpression') { + parent.argument = visit({ parent: parent.argument, app, used }) + } else if (parent.type === 'NewExpression') { + parent.callee = visit({ parent: parent.callee, app, used }) + } else if (parent.type === 'ParenthesisExpression') { + parent.expression = visit({ parent: parent.expression, app, used }) + } else if (parent.type === 'RestElement') { + parent.argument = visit({ parent: parent.argument, undefined, app, used }) + } else if (parent.type === 'KeyValuePatternProperty') { + parent.key = visit({ parent: parent.key, app, used }) + parent.value = visit({ parent: parent.value, app, used }) + } else if (parent.type === 'TryStatement') { + parent.block = visit({ parent: parent.block, app, used }) + parent.handler = visit({ parent: parent.handler, app, used }) + } else if (parent.type === 'CatchClause') { + parent.param = visit({ parent: parent.param, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + } else if (parent.type === 'Computed') { + parent.expression = visit({ parent: parent.expression, app, used }) + } else if (parent.type === 'OptionalChainingExpression') { + parent.expr = visit({ parent: parent.expr, app, used }) + } else if (parent.type === 'ForStatement') { + parent.init = visit({ parent: parent.init, app, used }) + parent.test = visit({ parent: parent.test, app, used }) + parent.update = visit({ parent: parent.update, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + } else if (parent.type === 'FunctionExpression') { + parent.params = visit({ parent: parent.params, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + parent.decorators = visit({ parent: parent.decorators, app, used }) + } else if (parent.type === 'Parameter') { + parent.pat = visit({ parent: parent.pat, app, used }) + } else if (parent.type === 'ForInStatement') { + parent.left = visit({ parent: parent.left, app, used }) + parent.right = visit({ parent: parent.right, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + } else if (parent.type === 'UpdateExpression') { + parent.argument = visit({ parent: parent.argument, app, used }) + } else if (parent.type === 'WhileStatement') { + parent.test = visit({ parent: parent.test, app, used }) + parent.body = visit({ parent: parent.body, app, used }) + } else if (parent.type === 'BreakStatement') { + parent.label = visit({ parent: parent.label, app, used }) + } else if (parent.type === 'SequenceExpression') { + parent.expressions = visit({ parent: parent.expressions, app, used }) + } else if (parent.type === 'ContinueStatement') { + parent.label = visit({ parent: parent.label, app, used }) + } else if (parent.type === 'ImportDeclaration') { + parent.specifiers = visit({ parent: parent.specifiers, app, used }) + } else if (parent.type === 'ImportSpecifier') { + parent.local = visit({ parent: parent.local, app, used }) + } else if (parent.type === 'ExportNamedDeclaration') { + parent.specifiers = visit({ parent: parent.specifiers, app, used }) + } else if (parent.type === 'ExportSpecifier') { + parent.orig = visit({ parent: parent.orig, app, used }) + } else if (noopTypes.includes(parent.type)) { + // noop + } else if (parent.type) { + log.warn('unexpected parent type', parent.type, parent) + } + + return parent +} + +export const resolveDependencies = ({ parent: m, app }) => { + const used = { + modules: [], + lib: [], + actions: [], + effects: [], + subscriptions: [], + helpers: [], + } + + m.body.map(item => visit({ parent: item, app, used })) + + return used +} diff --git a/src/lib/swc/visit.mjs b/src/lib/swc/visit.mjs new file mode 100644 index 00000000..5bbca61d --- /dev/null +++ b/src/lib/swc/visit.mjs @@ -0,0 +1,335 @@ +import is from '@magic/types' +import log from '@magic/log' + +import { handleLink } from '../handleLink.mjs' +// import { replaceSlashSlash } from '../replaceSlashSlash.mjs' + +const validKeys = ['src', 'srcset', 'logo', 'href', 'to'] + +const noopTypes = new Set([ + 'BooleanLiteral', + 'NullLiteral', + 'NumericLiteral', + 'RegExpLiteral', + 'ThisExpression', + 'EmptyStatement', + 'Identifier', +]) + +export const used = { + modules: new Set(), +} + +export const visit = ({ app, config, parent, par }) => { + if (!parent) { + return parent + } + + if (Array.isArray(parent)) { + return parent.map(n => visit({ par: parent, parent: n, app, config })) + } + + if (parent.type === 'VariableDeclaration') { + parent.declarations = parent.declarations.map(decl => + visit({ par: parent, parent: decl, app, config }), + ) + } else if (parent.type === 'ExpressionStatement') { + const result = visit({ par: parent, parent: parent.expression, app, config }) + // If result is undefined (e.g., CHECK_PROPS was removed), replace with EmptyStatement + if (result === undefined) { + return { + type: 'EmptyStatement', + span: parent.span, + } + } + parent.expression = result + } else if (parent.type === 'AssignmentExpression') { + parent.right = visit({ par: parent, parent: parent.right, app, config }) + parent.left = visit({ par: parent, parent: parent.left, app, config }) + } else if (parent.type === 'ArrowFunctionExpression') { + if (parent.params) { + parent.params = visit({ par: parent, parent: parent.params, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + } else if (parent.body.callee) { + parent.body.callee = visit({ par: parent, parent: parent.body.callee, app, config }) + parent.params = visit({ par: parent, parent: parent.params, app, config }) + } else if (parent.body.stmts) { + parent.body.stmts = parent.body.stmts.map(stmt => + visit({ par: parent, parent: stmt, app, config }), + ) + } else { + log.warn('unhandled ArrowFunctionExpression', parent) + } + } else if (parent.type === 'IfStatement') { + parent.test = visit({ par: parent, parent: parent.test, app, config }) + parent.consequent = visit({ par: parent, parent: parent.consequent, app, config }) + if (parent.alternate) { + parent.alternate = visit({ par: parent, parent: parent.alternate, app, config }) + } + } else if (parent.type === 'CallExpression') { + const name = parent.callee.value + const isModule = used.moduleNames.has(name) + const lib = Object.keys(app.lib).find(v => v === name) + const isLib = lib && is.function(lib) + + if (isModule) { + used.modules.add(name) + } + + if (isLib) { + // console.log('is lib', name) + } + + /* remove CHECK_PROPS function calls */ + if (parent.callee.value === 'CHECK_PROPS') { + // Return undefined to signal that this expression should be removed + // The parent ExpressionStatement handler will replace it with an EmptyStatement + return undefined + } else { + if (parent.callee.value === 'source' || parent.callee.value === 'img') { + const ignoredProps = ['Identifier', 'TemplateLiteral', 'BinaryExpression'] + + parent.arguments.forEach(arg => { + arg.expression?.properties?.forEach(prop => { + if (prop.type === 'KeyValueProperty') { + if (validKeys.includes(prop.key?.value)) { + let url + if (prop.value.type === 'StringLiteral') { + url = prop.value.value + } else if (prop.value.type === 'TemplateLiteral') { + /* + * We return on TemplateLiterals + * there are variable in play here, + * we do not want to risk mutating them + */ + return + } else if (ignoredProps.includes(prop.value.type)) { + /* + * we return on Identifier, TemplateLiteral and BinaryExpression + * they mean this is a variable or function being passed, + * that we do not want to mutate + */ + return + } else { + log.warn('W_UNKNOWN_URL_TYPE', 'could not find valid ast url type for', prop) + url = config.WEB_ROOT + } + + const isInternal = !url.includes('://') + const isRooted = url.startsWith(config.WEB_ROOT) + + if (isInternal && !isRooted) { + if (!url) { + url = config.WEB_ROOT + } else { + url = handleLink({ + app, + href: url.substr(0, url.length - 1), + WEB_ROOT: config.WEB_ROOT, + }) + } + + if (prop.value.type === 'StringLiteral') { + prop.value.value = url + prop.value.raw = `'${url}'` + } else if (prop.value?.quasis) { + prop.value.quasis[0].cooked = url + prop.value.quasis[0].raw = `'${url}'` + } + } + } + } + }) + }) + } + + parent.arguments = parent.arguments.map(arg => { + arg.expression = visit({ par: parent, parent: arg.expression, app, config }) + + if (arg.expression.type === 'StringLiteral') { + if (used.moduleNames.has(parent.callee.value)) { + const expr = { + type: 'ExpressionStatement', + span: { + start: 0, + end: 22, + ctxt: 0, + }, + expression: { + type: 'CallExpression', + span: { + start: 0, + end: 22, + ctxt: 0, + }, + callee: { + type: 'Identifier', + span: { + start: 0, + end: 4, + ctxt: 0, + }, + value: 'text', + optional: false, + }, + arguments: [ + { + spread: null, + expression: { + type: 'StringLiteral', + span: { + start: 5, + end: 21, + ctxt: 0, + }, + value: arg.expression.value, + raw: arg.expression.raw, + }, + }, + ], + typeArguments: null, + }, + } + + // arg = expr + } + } + + return arg + }) + } + } else if (parent.type === 'VariableDeclarator') { + parent.id = visit({ par: parent, parent: parent.id, app, config }) + parent.init = visit({ par: parent, parent: parent.init, app, config }) + } else if (parent.type === 'ObjectExpression') { + parent.properties = parent.properties.map(prop => + visit({ par: parent, parent: prop, app, config }), + ) + } else if (parent.type === 'KeyValueProperty') { + if (parent.value.type === 'StringLiteral') { + if (validKeys.includes(parent.key.value)) { + parent.value.value = handleLink({ + app, + href: parent.value.value, + WEB_ROOT: config.WEB_ROOT, + }) + } + } + + // Track module references used as values in objects (e.g., pages object: { '/a/': A }) + if (parent.value?.type === 'Identifier') { + const valueName = parent.value.value + if (used.moduleNames.has(valueName)) { + used.modules.add(valueName) + } + } + + parent.key = visit({ par: parent, parent: parent.key, app, config }) + parent.value = visit({ par: parent, parent: parent.value, app, config }) + } else if (parent.type === 'MemberExpression') { + parent.property = visit({ par: parent, parent: parent.property, app, config }) + parent.object = visit({ par: parent, parent: parent.object, app, config }) + } else if (parent.type === 'ArrayExpression') { + parent.elements = parent.elements.map(ele => { + ele.expression = visit({ par: parent, parent: ele.expression, undefined, app, config }) + return ele + }) + } else if (parent.type === 'ArrayPattern') { + parent.elements = parent.elements.map(ele => visit({ par: parent, parent: ele, app, config })) + } else if (parent.type === 'TemplateLiteral') { + parent.quasis = parent.quasis.map(quasi => { + quasi.raw = visit({ par: parent, parent: quasi.raw, app, config }) + quasi.cooked = visit({ par: parent, parent: quasi.cooked, app, config }) + return quasi + }) + } else if (parent.type === 'SpreadElement') { + parent.arguments = visit({ par: parent, parent: parent.arguments, app, config }) + } else if (parent.type === 'BlockStatement') { + parent.stmts = parent.stmts.map(stmt => visit({ par: parent, parent: stmt, app, config })) + } else if (parent.type === 'ReturnStatement') { + parent.argument = visit({ par: parent, parent: parent.argument, app, config }) + } else if (parent.type === 'ObjectPattern') { + parent.properties.map(prop => visit({ par: parent, parent: prop, app, config })) + } else if (parent.type === 'AssignmentPattern') { + parent.left = visit({ par: parent, parent: parent.left, app, config }) + parent.right = visit({ par: parent, parent: parent.right, app, config }) + } else if (parent.type === 'AssignmentPatternProperty') { + parent.key = visit({ par: parent, parent: parent.key, app, config }) + } else if (parent.type === 'ConditionalExpression') { + parent.test = visit({ par: parent, parent: parent.test, app, config }) + parent.consequent = visit({ par: parent, parent: parent.consequent, app, config }) + parent.alternate = visit({ par: parent, parent: parent.alternate, app, config }) + } else if (parent.type === 'BinaryExpression') { + parent.left = visit({ par: parent, parent: parent.left, app, config }) + parent.right = visit({ par: parent, parent: parent.right, app, config }) + } else if (parent.type === 'UnaryExpression') { + parent.argument = visit({ par: parent, parent: parent.argument, app, config }) + } else if (parent.type === 'NewExpression') { + parent.callee = visit({ par: parent, parent: parent.callee, app, config }) + } else if (parent.type === 'ParenthesisExpression') { + parent.expression = visit({ par: parent, parent: parent.expression, app, config }) + } else if (parent.type === 'RestElement') { + parent.argument = visit({ par: parent, parent: parent.argument, undefined, app, config }) + } else if (parent.type === 'KeyValuePatternProperty') { + parent.key = visit({ par: parent, parent: parent.key, app, config }) + parent.value = visit({ par: parent, parent: parent.value, app, config }) + } else if (parent.type === 'TryStatement') { + parent.block = visit({ par: parent, parent: parent.block, app, config }) + parent.handler = visit({ par: parent, parent: parent.handler, app, config }) + } else if (parent.type === 'CatchClause') { + parent.param = visit({ par: parent, parent: parent.param, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + } else if (parent.type === 'Computed') { + parent.expression = visit({ par: parent, parent: parent.expression, app, config }) + } else if (parent.type === 'OptionalChainingExpression') { + parent.expr = visit({ par: parent, parent: parent.expr, app, config }) + } else if (parent.type === 'ForStatement') { + parent.init = visit({ par: parent, parent: parent.init, app, config }) + parent.test = visit({ par: parent, parent: parent.test, app, config }) + parent.update = visit({ par: parent, parent: parent.update, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + } else if (parent.type === 'FunctionExpression') { + parent.params = visit({ par: parent, parent: parent.params, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + parent.decorators = visit({ par: parent, parent: parent.decorators, app, config }) + } else if (parent.type === 'Parameter') { + parent.pat = visit({ par: parent, parent: parent.pat, app, config }) + } else if (parent.type === 'ForInStatement') { + parent.left = visit({ par: parent, parent: parent.left, app, config }) + parent.right = visit({ par: parent, parent: parent.right, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + } else if (parent.type === 'UpdateExpression') { + parent.argument = visit({ par: parent, parent: parent.argument, app, config }) + } else if (parent.type === 'WhileStatement') { + parent.test = visit({ par: parent, parent: parent.test, app, config }) + parent.body = visit({ par: parent, parent: parent.body, app, config }) + } else if (parent.type === 'BreakStatement') { + parent.label = visit({ par: parent, parent: parent.label, app, config }) + } else if (parent.type === 'SequenceExpression') { + parent.expressions = visit({ par: parent, parent: parent.expressions, app, config }) + } else if (parent.type === 'ContinueStatement') { + parent.label = visit({ par: parent, parent: parent.label, app, config }) + } else if (parent.type === 'ImportDeclaration') { + parent.specifiers = visit({ par: parent, parent: parent.specifiers, app, config }) + } else if (parent.type === 'ImportSpecifier') { + parent.local = visit({ par: parent, parent: parent.local, app, config }) + } else if (parent.type === 'ExportNamedDeclaration') { + parent.specifiers = visit({ par: parent, parent: parent.specifiers, app, config }) + } else if (parent.type === 'ExportSpecifier') { + parent.orig = visit({ par: parent, parent: parent.orig, app, config }) + } else if (parent.type === 'StringLiteral') { + // console.log(parent, par) + } else if (parent.type === 'AwaitExpression') { + parent.argument = visit({ par: parent, parent: parent.argument, app, config }) + // noop + } else if (parent.type === 'ForOfStatement') { + parent.body = visit({ par: parent, parent: parent.body, app, config }) + parent.left = visit({ par: parent, parent: parent.left, app, config }) + parent.right = visit({ par: parent, parent: parent.right, app, config }) + } else if (noopTypes.has(parent.type)) { + } else if (parent.type) { + log.warn('unexpected parent type', parent.type, parent) + } + + return parent +} diff --git a/src/lib/uniqueMerge.mjs b/src/lib/uniqueMerge.mjs new file mode 100644 index 00000000..195cfca5 --- /dev/null +++ b/src/lib/uniqueMerge.mjs @@ -0,0 +1,30 @@ +export const uniqueMerge = (a, b) => { + if (!a) { + return b + } else if (!b) { + return a + } + + /* make sure we loop over the smaller object */ + if (Object.keys(a).length > Object.keys(b).length) { + const aTmp = a + a = b + b = aTmp + } + + Object.keys(a) + .filter(a => a) + .forEach(key => { + a[key].forEach(name => { + if (b.hasOwnProperty(key) && !b[key].includes(name)) { + b[key].push(name) + } + }) + + if (b[key]) { + b[key] = b[key].sort() + } + }) + + return b +} diff --git a/src/lib/writeFile.mjs b/src/lib/writeFile.mjs index f9259953..640ce709 100644 --- a/src/lib/writeFile.mjs +++ b/src/lib/writeFile.mjs @@ -2,5 +2,5 @@ import path from 'path' import fs from '@magic/fs' -export const writeFile = async ([name, content]) => +export const writeFile = async ([name, content], config) => await fs.writeFile(path.join(config.DIR.PUBLIC, name), content) diff --git a/src/lib/writeServer.mjs b/src/lib/writeServer.mjs deleted file mode 100644 index 0b487264..00000000 --- a/src/lib/writeServer.mjs +++ /dev/null @@ -1,24 +0,0 @@ -import path from 'path' - -import fs from '@magic/fs' - -export const writeLambda = async ([name, fn]) => { - const content = `const lambda = ${fn.toString()} - - req.body = '' - req.on('data', chunk => req.body += chunk) - req.on('end', (...args) => lambda(req, res, ...args)) -}` - - await fs.writeFile(name + '.mjs', content) -} - -export const writeServer = async app => { - const lambdas = Object.entries(app.lambdas) - if (lambdas.length) { - const apiDir = config.DIR.API || path.join(process.cwd(), 'api') - await fs.mkdirp(apiDir) - - await Promise.all(lambdas.map(([name, val]) => [path.join(apiDir, name), val]).map(writeLambda)) - } -} diff --git a/src/modules/Credits.mjs b/src/modules/Credits.mjs new file mode 100644 index 00000000..d465e40c --- /dev/null +++ b/src/modules/Credits.mjs @@ -0,0 +1,20 @@ +export const View = () => + div({ class: 'Credits' }, [ + 'made with a few bits of ', + Link({ to: 'https://magic.github.io/', target: '_blank', rel: 'noopener' }, 'magic'), + ]) + +export const style = vars => ({ + clear: 'both', + display: 'block', + margin: '2em 0 1em', + + a: { + color: (vars.text && vars.text.dark) || '#dedede', + textDecoration: 'underline', + + '&:hover': { + color: vars.neutral || '#c4c4c4', + }, + }, +}) diff --git a/src/modules/Footer.mjs b/src/modules/Footer.mjs index 3a2cfa7d..cd0324e4 100644 --- a/src/modules/Footer.mjs +++ b/src/modules/Footer.mjs @@ -1,13 +1,5 @@ export const View = (state, children = []) => - footer({ class: 'Footer' }, [ - div({ class: 'Container' }, [ - children, - div({ class: 'Credits' }, [ - 'made with a few bits of ', - Link({ to: 'https://github.com/magic/core', target: '_blank', rel: 'noopener' }, 'magic'), - ]), - ]), - ]) + footer({ class: 'Footer' }, [div({ class: 'Container' }, [Credits(), children])]) export const style = { position: 'relative', @@ -16,9 +8,4 @@ export const style = { '.Container': { textAlign: 'center', }, - - '.Credits': { - display: 'block', - clear: 'both', - }, } diff --git a/src/modules/Header.mjs b/src/modules/Header.mjs index 4614c50b..da302520 100644 --- a/src/modules/Header.mjs +++ b/src/modules/Header.mjs @@ -1,14 +1,15 @@ export const View = (props = {}, children = []) => { CHECK_PROPS(props, propTypes, 'Header') - const { logo, menu, logotext, ...state } = props + const { logo, menu, logotext, root, theme, hash, url } = props + if (!logo && !menu && !logotext) { return } return header({ class: 'Header' }, [ - (logo || logotext) && Logo({ img: logo, text: logotext }), - menu && Menu({ state, items: menu }), + (logo || logotext) && Logo({ root, theme, logo, logotext }), + menu && Menu({ url, hash, menu }), children, ]) } diff --git a/src/modules/Img.mjs b/src/modules/Img.mjs index 5c84d76d..497750b7 100644 --- a/src/modules/Img.mjs +++ b/src/modules/Img.mjs @@ -7,19 +7,22 @@ export const View = props => { CHECK_PROPS(props, propTypes, 'Img') + const { loading = 'lazy' } = props + if (!props.src) { return } - if (!props.alt) { + if (!props.hasOwnProperty('alt')) { if (props.title) { props.alt = props.title } else { - props.role = 'presentation' props.alt = '' } } + props.loading = loading + return img(props) } diff --git a/src/modules/Logo.mjs b/src/modules/Logo.mjs index 27ccbe14..550f1cfb 100644 --- a/src/modules/Logo.mjs +++ b/src/modules/Logo.mjs @@ -1,2 +1,11 @@ -export const View = ({ img, text, ...state }) => - Link({ to: state.root, class: 'Logo' }, [img && Img(img), text && span(text)]) +export const View = ({ logo, logotext, root }) => + CHECK_PROPS({ logo, logotext, root }, propTypes, 'Logo') && + Link({ to: root, class: 'Logo' }, [logo && Img(logo), logotext && span(logotext)]) + +export const propTypes = { + Logo: [ + { key: 'logo', type: 'string' }, + { key: 'logotext', type: 'string' }, + { key: 'root', type: 'string', reqired: true }, + ], +} diff --git a/src/modules/Menu.mjs b/src/modules/Menu.mjs index 4882bcf2..40b10f91 100644 --- a/src/modules/Menu.mjs +++ b/src/modules/Menu.mjs @@ -1,14 +1,18 @@ export const View = (props = {}) => { CHECK_PROPS(props, propTypes, 'Menu') - const { class: className = 'Menu', collapse = true, items, state } = props - let { url, hash, root } = state + const { collapse = true, menu, hash } = props + let { class: className = '', url } = props + + if (!className.includes('Menu')) { + className = `Menu ${className}`.trim() + } if (hash && !url.endsWith(hash)) { url += `#${hash}` } - return nav({ className }, ul(items.map(item => MenuItem({ ...item, url, root, collapse })))) + return nav({ className }, ul(menu.map(item => MenuItem({ ...item, url, collapse })))) } export const style = { @@ -40,7 +44,7 @@ export const style = { export const propTypes = { Menu: [ - { key: 'items', type: 'array', required: true }, + { key: 'menu', type: 'array', required: true }, { key: 'hash', type: 'string' }, { key: 'url', type: 'string' }, { key: 'collapse', type: 'boolean' }, diff --git a/src/modules/MenuItem.mjs b/src/modules/MenuItem.mjs index 156049ea..24d8135f 100644 --- a/src/modules/MenuItem.mjs +++ b/src/modules/MenuItem.mjs @@ -1,43 +1,26 @@ export const View = props => { CHECK_PROPS(props, propTypes, 'MenuItem', true) - const { text, items = [], url, root, parentTo = undefined, collapse, ...item } = props + + const { collapse, items = [], text, url, ...item } = props const p = { class: {}, } - let to = item.to - - if (root && to.startsWith('/')) { - to = to.substr(1) - } - - const first = item.to[0] - const isLocal = first === '/' || first === '-' || first === '#' - - if (parentTo && (first === '-' || first === '#')) { - to = parentTo + to - } - - const isRooted = to.startsWith(root) - if (root && isLocal && !isRooted) { - to = root + to - } - - item.to = to.replace(/\/\//g, '/') + let { to } = item - if (item.to === url) { + if (to === url) { p.class.active = true } let children = [] - const active = url.startsWith(item.to) || !collapse + const active = !collapse || url.includes(to) if (active && items.length) { - children = ul(items.map(i => MenuItem({ parentTo: item.to, url, root, collapse, ...i }))) + children = ul(items.map(i => MenuItem({ url, collapse, ...i }))) } - return li(p, [item.to ? MenuLink(item, text) : span(item, text), children]) + return li(p, [to ? Link(item, text) : span(item, text), children]) } export const propTypes = { @@ -55,7 +38,6 @@ export const propTypes = { ], }, }, - { key: 'parentTo', type: 'string' }, { key: 'collapse', type: 'boolean' }, { key: 'to', type: 'string' }, ], diff --git a/src/modules/MenuLink.mjs b/src/modules/MenuLink.mjs deleted file mode 100644 index b93b8d35..00000000 --- a/src/modules/MenuLink.mjs +++ /dev/null @@ -1 +0,0 @@ -export const View = (item, text) => Link(item, text) diff --git a/src/modules/Route.mjs b/src/modules/Route.mjs deleted file mode 100644 index d65e6bbc..00000000 --- a/src/modules/Route.mjs +++ /dev/null @@ -1,5 +0,0 @@ -export const View = ({ page, state }) => [ - Header(state), - div({ class: 'Page', id: 'page' }, page), - Footer(state), -] diff --git a/src/modules/Router.mjs b/src/modules/Router.mjs index 5656e593..d82888f5 100644 --- a/src/modules/Router.mjs +++ b/src/modules/Router.mjs @@ -1,6 +1,5 @@ +// Page Module export const View = ({ page, state }, children) => { - page = page ? page(state) : '404 - not found' - const magicProps = { id: 'Magic', class: state.pageClass, @@ -12,14 +11,32 @@ export const View = ({ page, state }, children) => { }, } - return main(magicProps, div(wrapperProps, [Route({ state, page }), children])) + return main( + magicProps, + div(wrapperProps, [ + Header(state), + div({ class: 'Page', id: 'page' }, page(state)), + Footer(state), + children, + ]), + ) } -export const Link = ({ to, action = actions.go, ...p }, children) => { - const { href, text, nofollow, noreferrer, ...props } = p +export const Link = ({ to, action = actions.go, text, ...p }, children) => { + const { href, nofollow, noreferrer, ...props } = p to = to || href || '' props.href = to + if (text && children) { + text = [text, children] + } else if (!text) { + if (children) { + text = children + } else { + text = to + } + } + const isLocal = to[0] === '/' || to[0] === '#' if (isLocal) { @@ -35,7 +52,7 @@ export const Link = ({ to, action = actions.go, ...p }, children) => { } } - return a(props, [text, children]) + return a(props, text) } export const state = { @@ -58,7 +75,7 @@ export const actions = { if (hash) { window.location.hash = hash } else { - window.scroll({ top, behavior: 'smooth' }) + window.scroll({ top }) } return { @@ -69,6 +86,11 @@ export const actions = { }, go: (state, e) => { + // if called without an event, do nothing + if (!e || !e.currentTarget) { + return state + } + // make sure our to never includes the origin // this makes sure we can distinguish between local and external links below let to = e.currentTarget.href.replace(window.location.origin, '') @@ -77,40 +99,28 @@ export const actions = { // do nothing if url would not change if (url === state.url && hash === state.hash) { - window.location.hash = hash + if (hash) { + window.location.hash = hash + } return state } - const { scrollY } = window - - const title = state.titles[url] + const title = state.pages && state.pages[url] && state.pages[url].title if (title) { document.title = state.title = title } - window.history.pushState({ url, hash, scrollY }, state.title, to) - if (url !== state.url) { if (!hash) { - const [html] = document.getElementsByTagName('html') - // firefox can not access this value via javascript (07.02.2020) - // which means that it will be an empty string. - // the hack below works by miracle, not logic. - const scrollBehavior = html.style.scrollBehavior - - // this allows firefox to scroll to top in some edge-case scenarios - // example: page with was scrolled manually. - html.style.scrollBehavior = 'auto' - - window.scrollTo({ top: 0, behavior: 'smooth' }) - - // set scrollBehavior back to default. - html.style.scrollBehavior = scrollBehavior + window.scrollTo({ top: 0 }) } } else { window.location.hash = hash } + const { scrollY } = window + window.history.pushState({ url, hash, scrollY }, state.title, to) + return { ...state, url, @@ -120,7 +130,7 @@ export const actions = { }, } -export const helpers = { +export const effects = { listenPopState: (dispatch, action) => { const listener = e => dispatch(action, e) @@ -130,13 +140,16 @@ export const helpers = { }, } -export const subscriptions = [['helpers.listenPopState', 'actions.pop']] +export const subscriptions = [['effects.listenPopState', 'actions.pop']] export const global = { actions: { go: true, pop: true, }, + effects: { + listenPopState: true, + }, state: { pageClass: true, }, diff --git a/src/modules/Seo.mjs b/src/modules/Seo.mjs index b5210444..072e1df3 100644 --- a/src/modules/Seo.mjs +++ b/src/modules/Seo.mjs @@ -1,21 +1,31 @@ +import { replaceSlashSlash } from '../lib/index.mjs' + export const View = state => { const { seo = {} } = state - const { author, type = 'website', context = 'http://schema.org', name = state.title } = seo + const { + author, + type = 'website', + context = 'http://schema.org', + name = state.title, + custom = [], + } = seo + const description = Array.isArray(state.description) ? state.description.join(' ') : state.description + const keywords = Array.isArray(state.keywords) ? state.keywords.join(' ') : state.keywords - const favicon = state.favicon || `${config.WEB_ROOT}favicon.ico` + const favicon = state.favicon || `${state.root}favicon.ico` const head = [link({ rel: 'icon', href: favicon })] - const pageTitle = state.title || seo.name + const pageTitle = seo.name || state.title if (pageTitle) { head.push(title(pageTitle)) } - const pageImage = state.image || seo.image + const pageImage = replaceSlashSlash(seo.image || state.image || state.logo || '') if (pageImage) { head.push(meta({ name: 'twitter:image', property: 'og:image', content: pageImage })) @@ -57,12 +67,18 @@ export const View = state => { head.push(meta({ name: 'author', content: authorName })) } - const openGraph = JSON.stringify({ + const seoGraph = { '@context': context, '@type': type, name, ...seo, - }) + } + + if (pageImage) { + seoGraph.image = pageImage + } + + const openGraph = JSON.stringify(seoGraph) const props = { type: 'application/ld+json', @@ -71,5 +87,9 @@ export const View = state => { head.push(script(props)) + custom.map(cus => { + head.push(script(cus)) + }) + return head } diff --git a/src/modules/app.mjs b/src/modules/app.mjs index 48505f7b..7d38bfa4 100644 --- a/src/modules/app.mjs +++ b/src/modules/app.mjs @@ -3,53 +3,43 @@ import path from 'path' import deep from '@magic/deep' import { h } from '@magic/hyperapp' import is from '@magic/types' +import log from '@magic/log' + +import { saveImport } from '../lib/index.mjs' const url = new URL(import.meta.url) const dirName = path.dirname(url.pathname) const App = async config => { - const { WEB_ROOT = '/', LANG = 'en' } = config - - let localApp = {} - let seo = {} - - try { - const maybeAppFile = path.join(config.ROOT, 'app.mjs') - const { default: def, ...maybeApp } = await import(maybeAppFile) - - if (def) { - let state = def.state - if (is.fn(def.state)) { - state = def.state(config) - } - - const { seo: _, ...s } = state - seo = state.seo - localApp = { ...def, state: s } - } else { - let state = maybeApp.state - if (is.fn(maybeApp.state)) { - state = maybeApp.state(config) - } - - const { seo: _, ...s } = state - seo = state.seo - localApp = { ...maybeApp, state: s } - } - } catch (e) { - // happy without maybeApp - if (e.code !== 'ERR_MODULE_NOT_FOUND') { - throw e - } + const { + PREPEND_TAGS, + APPEND_TAGS, + PREPEND_SCRIPTS, + APPEND_SCRIPTS, + CLIENT_LIB_NAME, + DIR, + LANG = 'en', + NODE_MODULES, + ROOT, + WEB_ROOT, + } = config + + let { THEME = [] } = config + + let localApp = { + state: {}, + actions: {}, + effects: {}, + subscriptions: [], } - let { THEME } = config + let seo = {} if (!is.array(THEME)) { THEME = [THEME] } - const results = await Promise.all( + const themeData = await Promise.all( THEME.map(async theme_name => { // order is meaningful. const themeLocations = [ @@ -61,29 +51,32 @@ const App = async config => { // see if this is a @magic-themes theme `@magic-themes/${theme_name}`, // see if it is installed locally. - path.join(config.DIR.THEMES, theme_name, 'index.mjs'), + path.join(DIR.THEMES, theme_name, 'index.mjs'), + // npm i -g magic: load magic-themes from node_modules explicitly. + path.join(NODE_MODULES, '@magic-themes', theme_name, 'src', 'index.mjs'), ] - await Promise.all( + return await Promise.all( themeLocations.map(async location => { try { - const { state, actions, effects, subscriptions } = await import(location) + const { state, actions, effects, subscriptions } = await saveImport(location) - if (state) { - localApp.state = { ...localApp.state, ...state } - } - if (actions) { - localApp.actions = { ...localApp.actions, ...actions } - } - if (effects) { - localApp.effects = { ...localApp.effects, ...effects } - } - if (subscriptions) { - localApp.subscriptions = { ...localApp.subscriptions, ...subscriptions } + return { + state, + actions, + effects, + subscriptions, } } catch (e) { if (!e.code || !e.code.includes('MODULE_NOT_FOUND')) { - throw error(e) + if (e.name === 'SyntaxError') { + log.error( + 'SYNTAX_ERROR', + '@magic/core: SyntaxError in your source files. Please run `npm run format` to get more information.', + ) + process.exit(1) + } + throw e } } }), @@ -91,35 +84,118 @@ const App = async config => { }), ) + themeData + .filter(a => a) + .forEach(theme => { + theme + .filter(a => a) + .forEach((props = {}) => { + const { state, actions, effects, subscriptions } = props + + if (state) { + if (is.fn(localApp.state)) { + localApp.state = localApp.state(config) + } + localApp.state = { ...localApp.state, ...state } + } + if (actions) { + localApp.actions = { ...localApp.actions, ...actions } + } + if (effects) { + localApp.effects = { ...localApp.effects, ...effects } + } + if (subscriptions) { + localApp.subscriptions = { ...localApp.subscriptions, ...subscriptions } + } + }) + }) + + // will be used in the catch clause to make sure it's this file + // that causes a MODULE_NOT_FOUND error + const maybeAppFile = path.join(ROOT, 'app.mjs') + + try { + const { default: def, ...maybeApp } = await saveImport(maybeAppFile) + + if (def) { + let { state = {} } = def + if (is.fn(def.state)) { + state = def.state(config) + } + + const { seo: _, ...s } = state + seo = state.seo + + localApp = deep.merge(localApp, def) + if (is.fn(localApp.state)) { + localApp.state = localApp.state(config) + } + localApp.state = { ...localApp.state, ...s } + } else { + let { state = {} } = maybeApp + if (is.fn(maybeApp.state)) { + state = maybeApp.state(config) + } + + const { seo: _, ...s } = state + seo = state.seo + + if (is.fn(maybeApp.state)) { + maybeApp.state = maybeApp.state(config) + } + if (is.fn(localApp.state)) { + localApp.state = localApp.state(config) + } + + localApp = deep.merge(localApp, maybeApp) + + localApp.state = { ...localApp.state, ...s } + } + } catch (e) { + // happy without maybeApp + if (e.code !== 'ERR_MODULE_NOT_FOUND' && !e.message.includes(maybeAppFile)) { + throw e + } + } + // default app state. gets merged with /assets/app.js if it exists. // /assets/app.js overwrites the values defined here. - let app = deep.merge(localApp, { + const defaultApp = { state: { url: WEB_ROOT, root: WEB_ROOT, }, - // this View gets server rendered. + // this View gets server rendered, + // the inner function that accepts state gets used to render this app on the client. View: (page, hashes) => state => { + if (is.fn(localApp.state)) { + localApp.state = localApp.state(config) + } + if (is.fn(state)) { + state = state(config) + } + state = { ...localApp.state, ...state, + ...page.state, + url: page.name, } - state.url = page.name - - const shortJsHash = hashes.js.split('-')[1].substr(0, 10) + const shortJsHash = hashes['/magic.js'].split('-')[1].substr(0, 10) const magicJs = { - src: `${config.WEB_ROOT}${config.CLIENT_LIB_NAME}.js?${shortJsHash}`, - integrity: hashes.js, + src: `${WEB_ROOT}${CLIENT_LIB_NAME}.js?${shortJsHash}`, + integrity: hashes['/magic.js'], crossorigin: 'anonymous', + type: 'module', } - const shortCssHash = hashes.css.split('-')[1].substr(0, 10) + const shortCssHash = hashes['/magic.css'].split('-')[1].substr(0, 10) const magicCss = { rel: 'stylesheet', - href: `${config.WEB_ROOT}${config.CLIENT_LIB_NAME}.css?${shortCssHash}`, - integrity: hashes.css, + href: `${WEB_ROOT}${CLIENT_LIB_NAME}.css?${shortCssHash}`, + integrity: hashes['/magic.css'], crossorigin: 'anonymous', } @@ -137,20 +213,29 @@ const App = async config => { meta({ charset: 'utf-8' }), meta({ 'http-equiv': 'X-UA-Compatible', content: 'IE=edge' }), meta({ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }), + meta({ 'http-equiv': 'Permissions-Policy', content: 'interest-cohort=()' }), Seo({ ...state, seo }), link(magicCss), page.Head && page.Head(state), ]), + body([ SkipLink(), + PREPEND_TAGS.map(tag => h(tag.name, tag.props, tag.children)), Page({ page: page.View, state }), + APPEND_TAGS.map(tag => h(tag.name, tag.props, tag.children)), + + PREPEND_SCRIPTS.map(scr => script(scr)), script(magicJs), + APPEND_SCRIPTS.map(scr => script(scr)), // script(serviceWorker), ]), ]), ] }, - }) + } + + const app = deep.merge(defaultApp, localApp) // admin // if (config.ENV === 'development') { diff --git a/src/modules/component.mjs b/src/modules/component.mjs index 11cbe960..763229a5 100644 --- a/src/modules/component.mjs +++ b/src/modules/component.mjs @@ -1,19 +1,24 @@ import { h } from '@magic/hyperapp' -export const component = name => (props = {}, children = false) => { - const is = (ele, ...types) => types.some(type => type === typeof ele) +export const component = + name => + (props = {}, children) => { + const is = (ele, ...types) => types.some(type => type === typeof ele) - if (!children) { - if (is(props, 'string', 'number', 'function') || Array.isArray(props)) { - children = props - props = {} - } else if (is(props.View, 'function')) { - children = props.View - props = {} - } else if (props.props || props.children) { - return h(name, {}, props) + if (is(children, 'undefined')) { + // are there children that have been processed by h already? + if (props.props) { + return h(name, {}, [props]) + } + + if (is(props, 'string', 'number', 'function') || Array.isArray(props)) { + children = props + props = {} + } else if (is(props.View, 'function')) { + children = props.View + props = {} + } } - } - return h(name, props, children) -} + return h(name, props, children) + } diff --git a/src/modules/index.mjs b/src/modules/index.mjs index f1430d86..f7ae6b49 100644 --- a/src/modules/index.mjs +++ b/src/modules/index.mjs @@ -4,11 +4,10 @@ import * as Img from './Img.mjs' import * as Logo from './Logo.mjs' import * as Menu from './Menu.mjs' import * as MenuItem from './MenuItem.mjs' -import * as MenuLink from './MenuLink.mjs' -import * as Route from './Route.mjs' import * as Router from './Router.mjs' import * as Seo from './Seo.mjs' import * as SkipLink from './SkipLink.mjs' +import * as Credits from './Credits.mjs' import * as BlogArchive from './BlogArchive.mjs' import * as BlogMonth from './BlogMonth.mjs' @@ -27,6 +26,7 @@ export const builtins = { BlogPost, BlogTeaser, BlogYear, + Credits, Footer, Header, Img, @@ -34,9 +34,7 @@ export const builtins = { Logo, Menu, MenuItem, - MenuLink, Page, - Route, Seo, SkipLink, } diff --git a/src/modules/tags.mjs b/src/modules/tags.mjs index c95bb6eb..f73f3c59 100644 --- a/src/modules/tags.mjs +++ b/src/modules/tags.mjs @@ -2,11 +2,11 @@ import { bodyTags, headTags, svgTags } from '@magic/tags' import { component } from './component.mjs' -const exp = {} +export const tags = {} const prepareTag = name => { const prepared = component(name) - exp[name] = prepared + tags[name] = prepared } bodyTags.forEach(prepareTag) @@ -15,10 +15,5 @@ svgTags.forEach(prepareTag) headTags.forEach(prepareTag) -const description = content => { - exp.meta({ name: 'description', property: 'og:description', content }) -} - -exp.description = description - -export const tags = exp +tags.description = content => + tags.meta({ name: 'description', property: 'og:description', content }) diff --git a/src/runCluster.mjs b/src/runCluster.mjs new file mode 100644 index 00000000..ad4ad2f1 --- /dev/null +++ b/src/runCluster.mjs @@ -0,0 +1,47 @@ +import cluster from 'cluster' + +import log from '@magic/log' + +import { build, primary, watch } from './cluster/index.mjs' +import { runConfig } from './config.mjs' + +export const runCluster = async options => { + const { args, commands } = options + + // get the config + const config = await runConfig(args) + + if (!global.CHECK_PROPS) { + const { CHECK_PROPS } = await import('./lib/CHECK_PROPS.mjs') + global.CHECK_PROPS = CHECK_PROPS + } + + const { CONFIG_FILE_PATH, DIR, GIT, ROOT, URL, URL_WARNING, WEB_ROOT } = config + + if (cluster.isPrimary || cluster.isMaster) { + const { __DEPRECATED__ = [] } = config + __DEPRECATED__.forEach(key => { + log.error(`E_${key.toUpperCase()}_USED`, `conf.${key} is deprecated.`) + }) + + await primary({ cluster, commands, args, DIR, GIT, URL, URL_WARNING, WEB_ROOT }) + } else if (cluster.isWorker) { + if (commands.serve && cluster.worker.id === 1) { + // watcher, + // watches the directory and tells primary to restart the build process when files change + watch({ args, ROOT, CONFIG_FILE_PATH, STATIC: DIR.STATIC }) + } else { + // builder + // builds the files and pages needed + build({ commands, config }) + } + + process + .on('unhandledRejection', error => { + process.send({ evt: 'error', error: error.toString(), stack: error.stack }) + }) + .on('uncaughtException', error => { + process.send({ evt: 'error', error: error.toString(), stack: error.stack }) + }) + } +} diff --git a/src/tasks/clean/index.mjs b/src/tasks/clean/index.mjs index 8ba1affe..924bc539 100644 --- a/src/tasks/clean/index.mjs +++ b/src/tasks/clean/index.mjs @@ -7,15 +7,13 @@ import log from '@magic/log' import { isLocalPath } from '../../lib/index.mjs' -export const clean = async config => { - const dir = config.DIR.PUBLIC - +export const clean = async dir => { if (is.string(dir) && !is.empty(dir)) { if (!isLocalPath(path.resolve(dir))) { // do not delete above/outside the cwd const msg = `TRIED DELETING OUTSIDE OF CWD! directory: ${dir} is not within ${process.cwd()} - This is not good, please file an issue on https://github.com/magic/core or send me a mail: bug@jaeh.at` + This is not good, please file an issue on https://github.com/magic/core/issues/ or send me a mail: bug@jaeh.at` throw error(msg, 'E_DEL_OUTSIDE_CWD') } diff --git a/src/tasks/connect/index.mjs b/src/tasks/connect/index.mjs index 016132d9..f4476f4d 100644 --- a/src/tasks/connect/index.mjs +++ b/src/tasks/connect/index.mjs @@ -2,12 +2,12 @@ import log from '@magic/log' import { getGitConfig, xc } from '../../lib/index.mjs' -export const connect = async config => { +export const connect = async ({ DIR, GIT }) => { const startTime = log.hrtime() - const git = await getGitConfig(config) + const git = await getGitConfig(GIT) - const dir = config.DIR.PUBLIC.replace(`${process.cwd()}/`, '') + const dir = DIR.PUBLIC.replace(`${process.cwd()}/`, '') const cmdPrefix = `--prefix=${dir}` const cmdOnto = `${git.ORIGIN} ${git.BRANCH}` const cmdArgv = `${cmdPrefix} ${cmdOnto}` diff --git a/src/tasks/prepare/api.mjs b/src/tasks/prepare/api.mjs new file mode 100644 index 00000000..837e50d6 --- /dev/null +++ b/src/tasks/prepare/api.mjs @@ -0,0 +1,49 @@ +import path from 'path' + +import fs from '@magic/fs' + +import { saveImport } from '../../lib/index.mjs' + +export const prepareApi = async (app, config) => { + const { API_DIR: apiDir, DIR } = config + const apiDirResolved = DIR.API + + const moduleLambdas = Object.entries(app.modules) + .filter(([_, dep]) => dep.server) + .map(([name, dep]) => [name.toLowerCase(), dep.server]) + + const pageLambdas = app.pages + // filter out pages that do not have a server property. + .filter(page => page.server) + // pages can only have one lambda attached. + .map(page => [page.name.toLowerCase(), page.server]) + + let apiLambdas = [] + + if (apiDir) { + const apiLambdaFiles = await fs.getFiles(apiDirResolved) + + apiLambdas = await Promise.all( + apiLambdaFiles.map(async file => { + if (file.endsWith('.mjs')) { + const { default: lambda } = await saveImport(file) + const name = path.basename(file, '.mjs') + return [name, lambda] + } + }), + ) + } + + const api = Object.fromEntries([ + // first, load lambdas from the api dir + ...apiLambdas, + // then, get lambdas from modules + ...moduleLambdas, + // overwrite those with the lambdas in pages + ...pageLambdas, + // and, finally, get the lambdas in app.mjs. + ...Object.entries(app.server), + ]) + + return api +} diff --git a/src/tasks/prepare/blog.mjs b/src/tasks/prepare/blog.mjs index 765116f9..1e19f109 100644 --- a/src/tasks/prepare/blog.mjs +++ b/src/tasks/prepare/blog.mjs @@ -1,28 +1,30 @@ import path from 'path' -import fs from '@magic/fs' - import { prepareBlogPost } from './blogPost.mjs' -export const prepareBlog = async app => { - const relativeBlogDir = config.BLOG_DIR.replace(`${config.ROOT}/`, '') +import { replaceSlashSlash } from '../../lib/index.mjs' - const extensions = ['.markdown', '.js', '.htm', '.mjs', '.html', '.md'] +export const prepareBlog = async (app, config) => { + const relativeBlogDir = config.BLOG_DIR.replace(`${config.ROOT}${path.sep}`, '') - let WEB_ROOT = config.WEB_ROOT - if (WEB_ROOT && WEB_ROOT.endsWith('/')) { - WEB_ROOT = WEB_ROOT.slice(0, -1) - } + const extensions = ['.markdown', '.js', '.htm', '.mjs', '.html', '.md'] - WEB_ROOT = `${WEB_ROOT}/${relativeBlogDir}` + const root = replaceSlashSlash(`${config.WEB_ROOT}/${relativeBlogDir}`) const index = {} const posts = await Promise.all( app.blog.map(async file => { - const post = await prepareBlogPost({ WEB_ROOT, PAGES: config.BLOG_DIR })(file) + const preparePost = prepareBlogPost({ + root, + pageDir: config.BLOG_DIR, + state: app.state, + config, + }) + + const post = await preparePost(file) const rootedBlogPath = file.replace(config.BLOG_DIR, '') - let [year, month, day, ...postPath] = rootedBlogPath.split('/').filter(a => a) + let [year, month, day, ...postPath] = rootedBlogPath.split(path.sep).filter(a => a) if (year.startsWith('index')) { return post @@ -54,9 +56,11 @@ export const prepareBlog = async app => { index[year][month] = {} } - if (day && !index[year][month][day]) { - index[year][month][day] = [post] - } else { + if (day) { + if (!index[year][month][day]) { + index[year][month][day] = [] + } + index[year][month][day].push(post) } @@ -68,8 +72,8 @@ export const prepareBlog = async app => { if (!file) { const post = { View: state => BlogArchive(state), - name: `${WEB_ROOT}/`, - path: `${WEB_ROOT}/index.html`, + name: `${root}/`.replace(/\\/gim, '/'), + path: `${root}/index.html`.replace(/\\/gim, '/'), } posts.push(post) @@ -83,8 +87,8 @@ export const prepareBlog = async app => { if (!file) { const post = { View: state => BlogYear(state), - name: `${WEB_ROOT}/${year}/`, - path: `${WEB_ROOT}/${year}/index.html`, + name: `${root}/${year}/`.replace(/\\/gim, '/'), + path: `${root}/${year}/index.html`.replace(/\\/gim, '/'), state: { year, }, @@ -100,8 +104,8 @@ export const prepareBlog = async app => { if (!file) { const post = { View: state => BlogMonth(state), - name: `${WEB_ROOT}/${year}/${month}/`, - path: `${WEB_ROOT}/${year}/${month}/index.html`, + name: `${root}/${year}/${month}/`.replace(/\\/gim, '/'), + path: `${root}/${year}/${month}/index.html`.replace(/\\/gim, '/'), state: { year, month, diff --git a/src/tasks/prepare/blogPost.mjs b/src/tasks/prepare/blogPost.mjs index f33e86f3..0f6f878a 100644 --- a/src/tasks/prepare/blogPost.mjs +++ b/src/tasks/prepare/blogPost.mjs @@ -3,111 +3,113 @@ import path from 'path' import error from '@magic/error' import fs from '@magic/fs' import is from '@magic/types' -import log from '@magic/log' import transmute from '@magic/transmute' -export const prepareBlogPost = ({ WEB_ROOT, PAGES }) => async file => { - const ext = path.extname(file) +import { replaceSlashSlash, saveImport } from '../../lib/index.mjs' - const markdownExtensions = ['.md', '.markdown'] - const htmlExtensions = ['.html', '.htm'] +export const prepareBlogPost = + ({ pageDir, state = {}, config }) => + async file => { + const { TMP_DIR } = config - let transmuted = {} + const ext = path.extname(file) - if (markdownExtensions.includes(ext)) { - const content = await fs.readFile(file, 'utf8') - transmuted = transmute.markdown(content) - file = file.replace('.md', '.mjs').replace('.markdown', '.mjs') - } else if (htmlExtensions.includes(ext)) { - const content = await fs.readFile(file, 'utf8') - transmuted = transmute.html(content) - file = file.replace('.html', '.mjs').replace('.htm', '.mjs') - } - - let pageTmp - if (!is.empty(transmuted)) { - const viewString = `export const View = state => BlogPost(state, [${transmuted.rendered}])` + const markdownExtensions = ['.md', '.markdown'] + const htmlExtensions = ['.html', '.htm'] - const fileTmpPath = path.join(config.TMP_DIR, path.basename(file)) - await fs.mkdirp(config.TMP_DIR) - await fs.writeFile(fileTmpPath, viewString) - pageTmp = await import(fileTmpPath) - } else { - pageTmp = await import(file) + let transmuted = {} - let children = [] - if (is.fn(pageTmp)) { - children = page.toString().split('=>')[1] - } else if (is.fn(pageTmp.View)) { - children = page.View.toString().split('=>')[1] + if (markdownExtensions.includes(ext)) { + const content = await fs.readFile(file, 'utf8') + transmuted = transmute.markdown(content, state) + file = file.replace('.md', '.mjs').replace('.markdown', '.mjs') + } else if (htmlExtensions.includes(ext)) { + const content = await fs.readFile(file, 'utf8') + transmuted = transmute.html(content, state) + file = file.replace('.html', '.mjs').replace('.htm', '.mjs') } - viewString = `export const View = state => BlogPost(state, ${children})` + let pageTmp + if (!is.empty(transmuted)) { + const viewString = `export const View = state => BlogPost(state, [${transmuted.rendered}])` - const fileTmpPath = path.join(config.TMP_DIR, path.basename(file)) - await fs.mkdirp(config.TMP_DIR) - await fs.writeFile(fileTmpPath, viewString) - pageTmp = await import(fileTmpPath) - } + const fileTmpPath = path.join(TMP_DIR, path.basename(file)) + await fs.mkdirp(TMP_DIR) + await fs.writeFile(fileTmpPath, viewString) + pageTmp = await saveImport(fileTmpPath) + } else { + pageTmp = await saveImport(file) - let page - if (is.fn(pageTmp)) { - page = { - ...transmuted, - View: pageTmp, - } - } else if (transmuted) { - const { rendered, ...trans } = transmuted - page = { ...trans, ...pageTmp } - } else { - page = { ...pageTmp } - } + let children = [] + if (is.fn(pageTmp)) { + children = pageTmp.toString().split('=>')[1] + } else if (is.fn(pageTmp.View)) { + children = pageTmp.View.toString().split('=>')[1] + } - page.file = file + viewString = `export const View = state => BlogPost(state, ${children})` - if (!is.empty(transmuted.state)) { - page.state = transmuted.state - } + const fileTmpPath = path.join(TMP_DIR, path.basename(file)) + await fs.mkdirp(TMP_DIR) + await fs.writeFile(fileTmpPath, viewString) + pageTmp = await saveImport(fileTmpPath) + } + + let page + if (is.fn(pageTmp)) { + page = { + ...transmuted, + View: pageTmp, + } + } else if (transmuted) { + const { rendered, ...trans } = transmuted + page = { ...trans, ...pageTmp } + } else { + page = { ...pageTmp } + } - const pageName = file - .replace(PAGES, '') - .replace(/index.[m]?js/gm, '') - .replace(/.[m]?js/gm, '/') + page.file = file.replace(/\\/gim, '/') - if (WEB_ROOT !== '/') { - page.name = `${WEB_ROOT}${pageName}` - } else { - page.name = pageName - } + // has to be initialized! + page.state = {} - page.path = page.name + if (!is.empty(transmuted.originalState)) { + if (is.fn(transmuted.originalState)) { + transmuted.originalState = transmuted.originalState(config) + } - if (page.path.endsWith('/')) { - if (page.name === '/404/') { - // page path for 404 is /404.html, not /404/index.html - page.path = `${page.path.slice(0, -1)}.html` - } else { - page.path = path.join(page.path, 'index.html') + page.state = transmuted.originalState } - } - if (!page.View || !is.function(page.View.toString)) { - const pageDir = PAGES.replace(process.cwd(), '') - // remove slashes - const pageName = page.name.replace(/\//g, '') - const page = `${pageDir}/${pageName}.mjs` + const pageName = file + .replace(pageDir, '') + .replace(/index.[m]?js/gm, '') + .replace(/.[m]?js/gm, '/') + .replace(/\\/gim, '/') + + page.name = replaceSlashSlash( + `${config.BLOG_DIR.replace(config.ROOT, config.WEB_ROOT)}/${pageName}`.replace(/\\/gim, '/'), + ) + + page.path = `${page.name}index.html` + + if (!page.View || !is.function(page.View.toString)) { + const pageDir = pageDir.replace(process.cwd(), '') + // remove slashes + const pageName = page.name.replace(/\//g, '') + const page = `${pageDir}/${pageName}.mjs` - throw error( - ` + throw error( + ` ${page} needs to either -export default () => [] +export default state => [] or -export const View = () => [] +export const View = state => [] `, - 'E_INVALID_BLOGPOST', - ) - } + 'E_INVALID_BLOGPOST', + ) + } - return page -} + return page + } diff --git a/src/tasks/prepare/checkProps.mjs b/src/tasks/prepare/checkProps.mjs new file mode 100644 index 00000000..8ef0b1b2 --- /dev/null +++ b/src/tasks/prepare/checkProps.mjs @@ -0,0 +1,85 @@ +import is from '@magic/types' + +export const prepareCheckProps = (magic, config) => { + const { IS_DEV } = config + + let checkProps = '' + let propTypeString = '' + + if (IS_DEV) { + // add proptype checking + checkProps = `const CHECK_PROPS = ${global.CHECK_PROPS.toString()}` + + propTypeString = 'const propTypes = {\n' + propTypeString += Object.entries(magic.modules) + .sort(([a], [b]) => (a > b ? 1 : -1)) + .map(([name, mod]) => { + // get propTypes of sub modules embedded in and exported from this module (Module.SubModule) + const subPropTypes = Object.entries(mod) + .sort(([a], [b]) => (a > b ? 1 : -1)) + .filter(([k, { propTypes }]) => is.case.upper(k[0]) && k !== 'View' && propTypes) + .map(([sn, sm]) => { + /* + * if the propTypes of the submodule are not correctly nested, + * eg Mod { propTypes: { prop1 } } instead of Mod { propTypes: { Mod: { prop1 } } }, + * we add the repeated Mod: { prop1 } declaration + */ + const { propTypes } = sm + if (is.array(propTypes) || !propTypes[sn]) { + sm.propTypes = { + [sn]: propTypes, + } + } + + return ( + Object.entries(sm.propTypes) + // sort alphabetically + .sort(([a], [b]) => (a > b ? 1 : -1)) + // stringify the props + // JSON.stringify makes sure no code is hiding + .map(([k, t]) => `${k}: ${JSON.stringify(t, null, 2)}`) + .join(',\n') + ) + }) + .filter(a => a) + .join(',\n') + + let propString = '' + if (mod.propTypes) { + /* + * if the propTypes of the module are not correctly nested, + * eg Mod { propTypes: { prop1 }} instead of Mod { propTypes: { Mod: { prop1 }}}, + * we add the repeated Mod: { prop1 } declaration + */ + const { propTypes } = mod + if (!propTypes[name]) { + mod.propTypes = { + [name]: propTypes, + } + } + + propString = Object.entries(mod.propTypes) + // sort the props by key, alphabetically + .sort(([a], [b]) => (a > b ? 1 : -1)) + // stringify the props + // JSON.stringify makes sure no code is hiding + .map(([key, type]) => `${key}: ${JSON.stringify(type, null, 2)}`) + .join(',\n') + } + + if (subPropTypes) { + propString += `\n${subPropTypes}` + } + return propString + }) + .filter(a => a) + .join(',\n') + + propTypeString += '\n}' + } + + return { + checkProps, + propTypeString, + } +} diff --git a/src/tasks/prepare/dependencies.mjs b/src/tasks/prepare/dependencies.mjs new file mode 100644 index 00000000..012512d6 --- /dev/null +++ b/src/tasks/prepare/dependencies.mjs @@ -0,0 +1,8 @@ +import { recursivelyResolveDependencies } from '../../lib/swc/recursivelyResolveDependencies.mjs' + +export const prepareDependencies = async (app, config) => { + const dependencies = await recursivelyResolveDependencies({ app, config }) + // console.log(dependencies.modulesByPage) + + return dependencies +} diff --git a/src/tasks/prepare/globals.mjs b/src/tasks/prepare/globals.mjs deleted file mode 100644 index bc793ce2..00000000 --- a/src/tasks/prepare/globals.mjs +++ /dev/null @@ -1,394 +0,0 @@ -import path from 'path' - -import cases from '@magic/cases' -import deep from '@magic/deep' -import error from '@magic/error' -import fs from '@magic/fs' -import is from '@magic/types' -import log from '@magic/log' - -import { builtins, component, tags } from '../../modules/index.mjs' -import { replacePathSepForImport } from '../../lib/index.mjs' - -const url = new URL(import.meta.url) -const dirName = path.dirname(url.pathname) - -const localLibIndexPath = path.join('src', 'lib', 'index.mjs') -const localLibMjsPath = path.join('src', 'lib.mjs') -const nodeModuleDir = path.join(process.cwd(), 'node_modules') -const recursiveSearch = false - -export const findNodeModules = async () => { - let modules = {} - - const dirs = await fs.getDirectories(nodeModuleDir, recursiveSearch) - - const dirPromises = dirs - .filter(dir => dir.includes('magic-module-') || dir.includes('magic-modules-')) - .map(async nodeModule => { - const name = cases.pascal(nodeModule.split(/magic-module(s)?/)[1]) - const importDir = nodeModule.replace(nodeModuleDir + path.sep, '') - const loadPath = replacePathSepForImport(importDir, path.sep) - - // find module itself - try { - const mod = await import(loadPath) - // copy the imported module into a new object to be able to extend it below - modules[name] = { - ...mod, - } - } catch (e) { - log.error('Error', `requiring node_module: ${nodeModule}, error: ${e.message}`) - } - - // find lib file of module if it exists - try { - const libPath = path.join(nodeModule, localLibIndexPath) - await fs.stat(libPath) - modules[name].lib = path.join(loadPath, localLibIndexPath) - } catch (e) { - if (e.code !== 'ENOENT') { - throw error(e) - } - } - - try { - const libMjsPath = path.join(nodeModule, localLibMjsPath) - await fs.stat(libMjsPath) - modules[name].lib = path.join(loadPath, localLibMjsPath) - } catch (e) { - if (e.code !== 'ENOENT') { - throw error(e) - } - } - }) - - const magicModuleDir = path.join(nodeModuleDir, '@magic-modules') - const nodeModules = await fs.getDirectories(magicModuleDir, recursiveSearch) - - const modulePromises = nodeModules - .filter(n => nodeModuleDir !== n) - .map(async nodeModule => { - if (magicModuleDir !== nodeModule) { - const name = cases.pascal(path.basename(nodeModule)) - - const importPath = nodeModule.replace(nodeModuleDir + path.sep, '') - const loadPath = replacePathSepForImport(importPath, path.sep) - - try { - const mod = await import(loadPath) - // copy the imported module into a new object to be able to extend it below - modules[name] = { - ...mod, - } - } catch (e) { - log.error('E_REQUIRE', `requiring node_module: ${nodeModule}, error: ${e.message}`) - process.exit(1) - } - - try { - const libPath = path.join(nodeModule, localLibIndexPath) - await fs.stat(libPath) - const importPath = path.join(loadPath, localLibIndexPath) - const resolvedLibPath = replacePathSepForImport(importPath, path.sep) - modules[name].lib = resolvedLibPath - } catch (e) { - if (e.code !== 'ENOENT') { - throw error(e) - } - } - - try { - const libMjsPath = path.join(nodeModule, localLibMjsPath) - await fs.stat(libMjsPath) - modules[name].lib = path.join(loadPath, localLibMjsPath) - } catch (e) { - if (e.code !== 'ENOENT') { - throw error(e) - } - } - } - }) - - await Promise.all([...dirPromises, ...modulePromises]) - - return modules -} - -export const findLocalModules = async () => { - let modules = {} - - const assetModules = await fs.getFiles(config.DIR.ASSETS) - const assetPromises = assetModules - .filter(m => is.case.upper(path.basename(m)[0])) - .filter(m => ['.mjs'].some(ext => m.endsWith(ext))) - .map(async m => { - try { - const name = path.basename(m).replace(path.extname(m), '') - const mod = await import(m) - if (mod.default) { - modules[name] = mod.default - } else if (is.fn(mod)) { - modules[name] = mod - } else { - modules[name] = { ...mod } - } - } catch (e) { - log.error('Error', `requiring local magic-module: ${m}, error: ${e.message}`) - } - }) - - await Promise.all(assetPromises) - return modules -} - -export const findAssetFile = async () => { - try { - const maybeAssetFile = path.join(config.DIR.ASSETS, 'index.mjs') - const assets = await import(maybeAssetFile) - let modules = {} - Object.entries(assets).forEach(([name, mod]) => { - if (is.fn(mod)) { - modules[name] = mod - } else { - modules[name] = { ...mod } - } - }) - return modules - } catch (e) { - // we are happy without assetfile - } - return {} -} - -export const findBuiltins = () => { - // we do not have to look for libs here, - // our builtins do not export any. - let modules = {} - Object.entries(builtins).forEach(([name, mod]) => { - modules[name] = mod - }) - Object.entries(tags).forEach(([name, mod]) => { - modules[name] = mod - }) - - modules.component = component - return modules -} - -export const findDefinedLibraries = async (app, modules) => { - const libraries = {} - - if (app.lib) { - Object.entries(app.lib).forEach(([name, val]) => { - libraries[name] = path.resolve(val) - }) - } - - const libNodeModuleDir = path.join(nodeModuleDir, '@magic-libraries') - let libOfficialNodeModuleFiles = await fs.getDirectories(libNodeModuleDir, recursiveSearch) - libOfficialNodeModuleFiles = libOfficialNodeModuleFiles.filter(n => n !== libNodeModuleDir) - - const nodeModules = await fs.getDirectories(nodeModuleDir, recursiveSearch) - const libInofficialNodeModuleFiles = nodeModules.filter( - n => n.includes('magic-library-') || n.includes('magic-libraries-'), - ) - - const libDirs = [...libOfficialNodeModuleFiles, ...libInofficialNodeModuleFiles] - - const libPromises = libDirs.map(async libDir => { - let libName = '' - if (libDir.includes('@magic-libraries')) { - libName = libDir.split('@magic-libraries' + path.sep)[1] - libDir = `@magic-libraries/${libName}` - } else if (libDir.includes('magic-libraries-')) { - libName = libDir.split('magic-libraries-')[1] - libDir = `magic-libraries-${libName}` - } else if (libDir.includes('magic-library')) { - libName = libDir.split('magic-library-')[1] - libDir = `magic-library-${libName}` - } - - libraries[libName] = libDir - }) - - await Promise.all(libPromises) - - Object.entries(modules).forEach(([key, val]) => { - key = `${key[0].toLowerCase()}${key.substring(1)}` - if (val.lib) { - libraries[key] = val.lib - } - - Object.entries(val).forEach(([viewKey, view]) => { - if (view.lib) { - viewKey = `${viewKey[0].toLowerCase()}${key.substring(1)}` - libraries[viewKey] = view.lib - } - }) - }) - - global.lib = global.lib || {} - - const libFnPromises = Object.entries(libraries).map(async ([key, val]) => { - let lib = await import(val) - if (lib.default) { - lib = lib.default - } - - global.lib[key] = lib - - return { - key, - path: val, - lib, - } - }) - - const libs = await Promise.all(libFnPromises) - - return libs -} - -const findThemeModules = async (modules = {}) => { - let { THEME } = config - - if (THEME) { - if (is.string(THEME)) { - THEME = [THEME] - } - - const results = await Promise.all( - THEME.map(async theme_name => { - // order is meaningful. - const themeLocations = [ - // first look if we have this theme preinstalled in @magic, if so, merge it into the styles - path.join(dirName, '..', '..', 'themes', theme_name, 'index.mjs'), - // see if the theme is a full name of a js module in node_modules, - // eg: @org/theme-name or theme-name - theme_name, - // see if this is a @magic-themes theme - `@magic-themes/${theme_name}`, - // see if it is installed locally. - path.join(config.DIR.THEMES, theme_name, 'index.mjs'), - ] - - const modules = {} - - await Promise.all( - themeLocations.map(async location => { - try { - const { default: theme, ...maybeModules } = await import(location) - - const libs = {} - Object.entries(maybeModules).map(([name, fn]) => { - if (is.fn(fn)) { - if (!modules[name]) { - modules[name] = fn - } else if (modules[name].View) { - modules[name] = { - ...modules[name], - View: fn, - } - } else { - modules[name] = fn - } - } else if (is.object(fn)) { - if (!modules[name]) { - modules[name] = { ...fn } - } else if (is.fn(modules[name])) { - modules[name] = { View: modules[name], ...fn } - } else { - modules[name] = { - ...modules[name], - ...fn, - } - } - } - }) - } catch (e) { - if (!e.code || !e.code.includes('MODULE_NOT_FOUND')) { - throw error(e) - } - } - }), - ) - - return modules - }), - ) - - // by writing the results after awaiting them above, - // we force correct order of merges. - // Promise.all does not wait internally for sequential execution, - // but await Promise.all returns ordered results. - results.map(result => { - modules = deep.merge(modules, result) - }) - } - - return modules -} - -export const prepareGlobals = async (app, config) => { - global.keys = global.keys || new Set() - global.config = config - global.app = app - - let modules = {} - - // load core builtin modules - const builtinFiles = await findBuiltins(modules) - if (builtinFiles) { - modules = deep.merge(modules, builtinFiles) - } - - // load modules from themes - modules = await findThemeModules(modules) - - // load plugins from node_modules - const nodeModuleFiles = await findNodeModules(modules) - if (nodeModuleFiles) { - modules = deep.merge(modules, nodeModuleFiles) - } - - // look for /assets/index.mjs - const assetFile = await findAssetFile(modules) - if (assetFile) { - modules = deep.merge(modules, assetFile) - } - - // look for /assets/Uppercased.mjs and /assets/modules/Uppercased.mjs - const localModuleFiles = await findLocalModules(modules) - if (localModuleFiles) { - modules = deep.merge(modules, { ...localModuleFiles }) - } - - Object.entries(modules).forEach(([name, mod]) => { - if (is.fn(mod)) { - global[name] = mod - } else if (is.fn(mod.View)) { - global[name] = mod.View - } else if (is.fn(mod[name])) { - global[name] = mod[name] - } - - const views = Object.entries(mod).filter( - ([k]) => k !== name && is.case.upper(k[0]) && k !== 'View', - ) - views.forEach(([k, v]) => { - if (is.function(v)) { - global[name][k] = v - } else { - global[name][k] = v.View - } - }) - }) - - const libs = await findDefinedLibraries(app, modules) - - return { - modules, - libs, - } -} diff --git a/src/tasks/prepare/globals/findAssetFile.mjs b/src/tasks/prepare/globals/findAssetFile.mjs new file mode 100644 index 00000000..42dc2ae3 --- /dev/null +++ b/src/tasks/prepare/globals/findAssetFile.mjs @@ -0,0 +1,24 @@ +import path from 'path' + +import is from '@magic/types' + +import { saveImport } from '../../../lib/index.mjs' + +export const findAssetFile = async dir => { + try { + const maybeAssetFile = path.join(dir, 'index.mjs') + const assets = await saveImport(maybeAssetFile) + let modules = {} + Object.entries(assets).forEach(([name, mod]) => { + if (is.fn(mod)) { + modules[name] = mod + } else { + modules[name] = { ...mod } + } + }) + return modules + } catch (e) { + // we are happy without assetfile + } + return {} +} diff --git a/src/tasks/prepare/globals/findBuiltins.mjs b/src/tasks/prepare/globals/findBuiltins.mjs new file mode 100644 index 00000000..de9e099f --- /dev/null +++ b/src/tasks/prepare/globals/findBuiltins.mjs @@ -0,0 +1,18 @@ +import { builtins, component, tags } from '../../../modules/index.mjs' + +export const findBuiltins = () => { + // we do not have to look for libs here, + // our builtins do not export any. + let modules = {} + Object.entries(builtins).forEach(([name, mod]) => { + modules[name] = mod + }) + + Object.entries(tags).forEach(([name, mod]) => { + modules[name] = mod + }) + + modules.component = component + + return modules +} diff --git a/src/tasks/prepare/globals/findLibraries.mjs b/src/tasks/prepare/globals/findLibraries.mjs new file mode 100644 index 00000000..611a8651 --- /dev/null +++ b/src/tasks/prepare/globals/findLibraries.mjs @@ -0,0 +1,139 @@ +import path from 'path' + +import cases from '@magic/cases' +import fs from '@magic/fs' +import is from '@magic/types' + +import { saveImport } from '../../../lib/index.mjs' + +const nodeModuleDir = path.join(process.cwd(), 'node_modules') + +export const findLibraries = async (app, modules, { DIR }) => { + const libraries = {} + + if (app.lib) { + Object.entries(app.lib).forEach(([name, val]) => { + if (is.string(val)) { + libraries[name] = path.resolve(val) + } else { + libraries[name] = val + } + }) + } + + const libNodeModuleDir = path.join(nodeModuleDir, '@magic-libraries') + let libOfficialNodeModuleFiles = await fs.getDirectories(libNodeModuleDir, { depth: false }) + libOfficialNodeModuleFiles = libOfficialNodeModuleFiles.filter(n => n !== libNodeModuleDir) + + const nodeModules = await fs.getDirectories(nodeModuleDir, { depth: false }) + const libInofficialNodeModuleFiles = nodeModules.filter( + n => n.includes('magic-library-') || n.includes('magic-libraries-'), + ) + + await Promise.all( + DIR.LIB.map(async dir => { + const files = await fs.getFiles(dir, { depth: 1 }) + + return files.map(file => { + const libName = file + .replace(`${dir}${path.sep}`, '') + .replace('.mjs', '') + .replace('.js', '') + .replace(/\\/gim, '/') + libraries[libName] = file + }) + }), + ) + + const libDirs = [...libOfficialNodeModuleFiles, ...libInofficialNodeModuleFiles] + libDirs.forEach(async libDir => { + let libName = '' + const cleanLibDir = libDir.split(path.sep).slice(0, -1).join(path.sep) // Remove last segment (/src) + const lastSegment = libDir.split(path.sep).pop() + + if (libDir.includes('@magic-libraries')) { + libName = cleanLibDir.split('@magic-libraries' + path.sep)[1] + } else if (libDir.includes('magic-libraries-')) { + libName = cleanLibDir.split('magic-libraries-')[1] + } else if (libDir.includes('magic-library-')) { + libName = cleanLibDir.split('magic-library-')[1] + } + + libraries[libName] = cleanLibDir + }) + + const handleModuleLibs = ([key, val]) => { + key = `${key[0].toLowerCase()}${key.substring(1)}` + if (val.lib) { + libraries[key] = val.lib + } + + Object.entries(val).forEach(([viewKey, view]) => { + if (view.lib) { + viewKey = `${viewKey[0].toLowerCase()}${key.substring(1)}` + libraries[viewKey] = view.lib + } + }) + } + + // Object.entries(app.pages).forEach(([ k, v ]) => { + // if (v.lib) { + // Object.entries(v.lib).forEach(([ key, lib ]) => { + // handleModuleLibs([ key, { lib } ]) + // }) + // } + // }) + + Object.entries(modules).forEach(handleModuleLibs) + + global.lib = global.lib || {} + + const libFnPromises = Object.entries(libraries).map(async ([key, val]) => { + const camelKey = cases.camel(key) + + let lib + + if (!is.string(val)) { + if (val[key] && Object.keys(val).length === 1) { + lib = val[key] + } else { + lib = val + } + } else { + if (!val.endsWith('.mjs') && !val.endsWith('.js')) { + const rootPath = path.join(val, 'index.mjs') + const rootExists = await fs.exists(rootPath) + if (rootExists) { + val = rootPath + } else { + const srcPath = path.join(val, 'src', 'index.mjs') + const srcExists = await fs.exists(srcPath) + if (srcExists) { + val = srcPath + } + } + } + + const imported = await saveImport(val) + + if (imported.default) { + lib = imported.default + } else if (imported[key]) { + lib = imported[key] + } else { + lib = imported + } + } + + global.lib[camelKey] = lib + + return { + key, + lib, + } + }) + + const libs = await Promise.all(libFnPromises) + + return libs +} diff --git a/src/tasks/prepare/globals/findLocalModules.mjs b/src/tasks/prepare/globals/findLocalModules.mjs new file mode 100644 index 00000000..08201465 --- /dev/null +++ b/src/tasks/prepare/globals/findLocalModules.mjs @@ -0,0 +1,37 @@ +import path from 'path' + +import fs from '@magic/fs' +import is from '@magic/types' +import log from '@magic/log' + +import { saveImport } from '../../../lib/index.mjs' + +export const findLocalModules = async dir => { + let modules = {} + + const assetModules = await fs.getFiles(dir) + const assetPromises = assetModules + .filter(m => is.case.upper(path.basename(m)[0])) + .filter(m => ['.mjs', '.js'].some(ext => m.endsWith(ext))) + .map(async m => { + try { + const name = path.basename(m).replace(path.extname(m), '') + + const mod = await saveImport(m) + if (mod.default) { + modules[name] = mod.default + } else if (is.fn(mod)) { + modules[name] = mod + } else { + modules[name] = { ...mod } + } + } catch (e) { + const stack = e.stack.split('at') + log.error('Error', `requiring local magic-module: ${m}, error:`, stack[0], stack[1]) + } + }) + + await Promise.all(assetPromises) + + return modules +} diff --git a/src/tasks/prepare/globals/findNodeModules.mjs b/src/tasks/prepare/globals/findNodeModules.mjs new file mode 100644 index 00000000..9ba63c0f --- /dev/null +++ b/src/tasks/prepare/globals/findNodeModules.mjs @@ -0,0 +1,125 @@ +import path from 'path' + +import cases from '@magic/cases' + +import error from '@magic/error' +import fs from '@magic/fs' +import log from '@magic/log' + +import { replacePathSepForImport, saveImport } from '../../../lib/index.mjs' + +const localLibIndexPath = path.join('src', 'lib', 'index') +const localLibMjsPath = path.join('src', 'lib') +const nodeModuleDir = path.join(process.cwd(), 'node_modules') + +export const findNodeModules = async () => { + let modules = {} + + const dirs = await fs.getDirectories(nodeModuleDir, { depth: false }) + + const dirPromises = dirs + .filter(dir => dir.includes('magic-module-') || dir.includes('magic-modules-')) + .map(async nodeModule => { + const name = cases.pascal(nodeModule.split(/magic-module(s)?-/)[1]) + const importDir = nodeModule.replace(nodeModuleDir + path.sep, '') + const loadPath = replacePathSepForImport(importDir, path.sep) + + // find module itself + try { + const mod = await saveImport(loadPath) + // saveImport returns the error object on failure - check for it + if (mod.code && mod.url) { + log.warn('E_MODULE_NOT_FOUND', `skipping node_module: ${nodeModule}`) + return + } + // copy the imported module into a new object to be able to extend it below + modules[name] = { + ...mod, + } + } catch (e) { + log.error('Error', `requiring node_module: ${nodeModule}, error: ${e.message}`) + } + + // find lib file of module if it exists + const extensions = ['.mjs', '.js'] + await Promise.all( + extensions.map(async ext => { + try { + const libPath = path.join(nodeModule, localLibIndexPath + ext) + await fs.stat(libPath) + modules[name].lib = path.join(loadPath, localLibIndexPath + ext) + } catch (e) { + if (e.code !== 'ENOENT') { + throw error(e) + } + } + }), + ) + + try { + const libMjsPath = path.join(nodeModule, localLibMjsPath) + await fs.stat(libMjsPath) + modules[name].lib = path.join(loadPath, localLibMjsPath) + } catch (e) { + if (e.code !== 'ENOENT') { + throw error(e) + } + } + }) + + const magicModuleDir = path.join(nodeModuleDir, '@magic-modules') + const nodeModules = await fs.getDirectories(magicModuleDir, { depth: false }) + + const modulePromises = nodeModules + .filter(n => nodeModuleDir !== n) + .map(async nodeModule => { + if (magicModuleDir !== nodeModule) { + const name = cases.pascal(path.basename(nodeModule)) + + const loadPath = replacePathSepForImport(nodeModule, path.sep) + + try { + const importPath = path.join(nodeModule, 'src', 'index.mjs') + const mod = await saveImport(importPath) + // saveImport returns the error object on failure - check for it + if (mod.code && mod.url) { + log.warn('E_MODULE_NOT_FOUND', `skipping node_module: ${nodeModule}`) + return + } + // copy the imported module into a new object to be able to extend it below + modules[name] = { + ...mod, + } + } catch (e) { + log.error(e.code, `requiring node_module: ${nodeModule}, error: ${e.message}`) + process.exit(1) + } + + try { + const libPath = path.join(nodeModule, localLibIndexPath) + await fs.stat(libPath) + const importPath = path.join(loadPath, localLibIndexPath) + const resolvedLibPath = replacePathSepForImport(importPath, path.sep) + modules[name].lib = resolvedLibPath + } catch (e) { + if (e.code !== 'ENOENT') { + throw error(e) + } + } + + try { + const libMjsPath = path.join(nodeModule, localLibMjsPath) + await fs.stat(libMjsPath) + modules[name].lib = path.join(loadPath, localLibMjsPath) + } catch (e) { + if (e.code !== 'ENOENT') { + throw error(e) + } + } + } + }) + + await Promise.all([...dirPromises, ...modulePromises]) + + return modules +} diff --git a/src/tasks/prepare/globals/findThemes.mjs b/src/tasks/prepare/globals/findThemes.mjs new file mode 100644 index 00000000..38107736 --- /dev/null +++ b/src/tasks/prepare/globals/findThemes.mjs @@ -0,0 +1,105 @@ +import path from 'path' + +import deep from '@magic/deep' +import error from '@magic/error' +import is from '@magic/types' + +import { saveImport } from '../../../lib/index.mjs' + +export const findThemes = async (modules, { DIR, NODE_MODULES, THEME }) => { + if (THEME) { + if (is.string(THEME)) { + THEME = [THEME] + } + + const url = new URL(import.meta.url) + const dirName = path.dirname(url.pathname) + + const results = await Promise.all( + THEME.map(async theme_name => { + // order is meaningful. + const themeLocations = [ + // first look if we have this theme preinstalled in @magic, + // if so, merge it into the styles + path.join(dirName, '..', '..', 'themes', theme_name, 'index.mjs'), + // see if the theme is a full name of a js module in node_modules, + // eg: @org/theme-name or theme-name + theme_name, + // see if this is a @magic-themes theme + `@magic-themes/${theme_name}`, + // see if it is installed locally, + // either in a subdirectory + path.join(DIR.THEMES, theme_name, 'index.mjs'), + // or as a single mjs file + path.join(DIR.THEMES, theme_name + '.mjs'), + // or from a globally installed magic + path.join(NODE_MODULES, '@magic-themes', theme_name, 'src', 'index.mjs'), + ] + + const mods = await Promise.all( + themeLocations.map(async location => { + try { + const { default: theme, ...maybeModules } = await saveImport(location) + + const results = Object.entries(maybeModules).map(([name, fn]) => { + if (is.case.upper(name[0])) { + if (is.fn(fn)) { + if (!modules[name]) { + return [name, fn] + } else if (modules[name].View) { + return [ + name, + { + ...modules[name], + View: fn, + }, + ] + } else { + return [name, fn] + } + } else if (is.object(fn)) { + if (!modules[name]) { + return [name, fn] + } else if (is.fn(modules[name])) { + return [name, { View: modules[name], ...fn }] + } else { + return [ + name, + { + ...modules[name], + ...fn, + }, + ] + } + } + } + }) + + return Object.fromEntries(results.filter(a => a)) + } catch (e) { + if (!e.code || !e.code.includes('MODULE_NOT_FOUND')) { + throw error(e) + } + } + }), + ) + + return mods.filter(a => a) + }), + ) + + let mods = {} + + // by writing the results after awaiting them above, + // we force correct order of merges. + // Promise.all does not wait internally for sequential execution, + // but await Promise.all returns ordered results. + deep.flatten(results).map(result => { + if (!is.empty(result)) { + mods = deep.merge(mods, result) + } + }) + + return mods + } +} diff --git a/src/tasks/prepare/globals/index.mjs b/src/tasks/prepare/globals/index.mjs new file mode 100644 index 00000000..3e5f33d1 --- /dev/null +++ b/src/tasks/prepare/globals/index.mjs @@ -0,0 +1,61 @@ +import deep from '@magic/deep' + +import { findNodeModules } from './findNodeModules.mjs' +import { findLocalModules } from './findLocalModules.mjs' +import { findAssetFile } from './findAssetFile.mjs' +import { findBuiltins } from './findBuiltins.mjs' +import { findLibraries } from './findLibraries.mjs' +import { findThemes } from './findThemes.mjs' +import { registerModule } from './registerModule.mjs' + +export const prepareGlobals = async (app, config) => { + global.app = app + + const { DIR, NODE_MODULES, ROOT, THEME } = config + + let modules = {} + + // load core builtin modules + const builtinFiles = await findBuiltins(modules) + if (builtinFiles) { + modules = deep.merge(modules, builtinFiles) + Object.entries(builtinFiles).map(registerModule) + } + + // load modules from themes + const themeModulefiles = await findThemes(modules, { DIR, NODE_MODULES, THEME }) + if (themeModulefiles) { + modules = deep.merge(modules, themeModulefiles) + Object.entries(themeModulefiles).map(registerModule) + } + + // load plugins from node_modules + const nodeModuleFiles = await findNodeModules(modules) + if (nodeModuleFiles) { + modules = deep.merge(modules, nodeModuleFiles) + Object.entries(nodeModuleFiles).map(registerModule) + } + + // look for /assets/index.mjs + const assetFile = await findAssetFile(DIR.ASSETS) + if (assetFile) { + modules = deep.merge(modules, assetFile) + Object.entries(assetFile).map(registerModule) + } + + // look for /assets/Uppercased.mjs and /assets/modules/Uppercased.mjs + const localModuleFiles = await findLocalModules(DIR.ASSETS) + if (localModuleFiles) { + modules = deep.merge(modules, localModuleFiles) + Object.entries(localModuleFiles).map(registerModule) + } + + // Object.entries(modules).forEach(registerModule) + + const libs = await findLibraries(app, modules, { DIR, ROOT }) + + return { + modules, + libs, + } +} diff --git a/src/tasks/prepare/globals/registerModule.mjs b/src/tasks/prepare/globals/registerModule.mjs new file mode 100644 index 00000000..12b8333b --- /dev/null +++ b/src/tasks/prepare/globals/registerModule.mjs @@ -0,0 +1,23 @@ +import is from '@magic/types' + +export const registerModule = ([name, mod]) => { + if (is.fn(mod)) { + global[name] = mod + } else if (is.fn(mod.View)) { + global[name] = mod.View + } else if (is.fn(mod[name])) { + global[name] = mod[name] + } + + const views = Object.entries(mod).filter( + ([k]) => k !== name && is.case.upper(k[0]) && k !== 'View', + ) + + views.forEach(([k, v]) => { + if (is.function(v)) { + global[name][k] = v + } else if (v.View) { + global[name][k] = v.View + } + }) +} diff --git a/src/tasks/prepare/index.mjs b/src/tasks/prepare/index.mjs index 82b64846..c1fe1a6f 100644 --- a/src/tasks/prepare/index.mjs +++ b/src/tasks/prepare/index.mjs @@ -1,45 +1,53 @@ import error from '@magic/error' import fs from '@magic/fs' import is from '@magic/types' +import deep from '@magic/deep' +import log from '@magic/log' -import { getBlog, getPages } from '../../lib/index.mjs' +import { getBlog, getPages, replaceSlashSlash } from '../../lib/index.mjs' import { prepareBlog } from './blog.mjs' import { prepareThemes } from './themes.mjs' -import { prepareGlobals } from './globals.mjs' +import { prepareGlobals } from './globals/index.mjs' import { prepareJs } from './js.mjs' import { prepareMetaFiles } from './meta.mjs' import { prepareModules } from './modules.mjs' import { preparePages } from './pages.mjs' -import { prepareServiceWorker } from './service-worker.mjs' +// import { prepareServiceWorker } from './service-worker.mjs' +import { prepareApi } from './api.mjs' +import { prepareStateLinks } from './stateLinks.mjs' +import { prepareDependencies } from './dependencies.mjs' + +import { defaultApp } from '../../defaultApp.mjs' export const prepare = async (app, config) => { - const defaultApp = { - state: {}, - actions: {}, - effects: {}, - helpers: {}, - cookies: {}, - subscriptions: [], - lib: {}, - init: [], + app = { ...defaultApp, ...app } + + if (is.fn(app.build)) { + app = await app.build({ app, config }) } - app = { ...defaultApp, ...app } + app.files = await getPages({ dir: config.DIR.PAGES, root: config.ROOT }) - let { modules, libs } = await prepareGlobals(app, config) + // collect the pages, create their states + app.pages = await preparePages(app, config) - app.files = await getPages() + const { modules, libs } = await prepareGlobals(app, config) - if (config.BLOG_DIR) { - app.blog = await getBlog(app.files) - } + libs.forEach(({ key, lib }) => { + app.lib[key] = lib + }) - // collect the pages, create their states - app.pages = await preparePages(app) + app.modules = modules + + // make all links WEB_ROOTED + // mutates app.state + app.state = prepareStateLinks(app, config) if (config.BLOG_DIR) { - const { posts, index } = await prepareBlog(app) + app.blog = await getBlog(config) + + const { posts, index } = await prepareBlog(app, config) app.state.blog = index @@ -47,10 +55,14 @@ export const prepare = async (app, config) => { } // collect all page states, actions, effects, helpers and subscriptions - app.pages.map(page => { + app.pages.forEach(page => { if (!is.empty(page.state)) { app.state.pages = app.state.pages || {} - app.state.pages[page.name] = page.state + + // filter page.state that is equal to already existing global state + const tmpPageState = Object.entries(page.state).filter(([k, v]) => app.state[k] !== v) + + app.state.pages[page.name] = Object.fromEntries(tmpPageState) } const actionTypes = ['actions', 'effects'] @@ -69,6 +81,11 @@ export const prepare = async (app, config) => { } if (!is.empty(page.helpers)) { + log.warn( + 'E_HELPERS_DEPRECATED', + `page: ${page.name}`, + 'using app.helpers is deprecated, use actions, effects or lib', + ) app.helpers = deep.merge(app.helpers, page.helpers) } @@ -79,18 +96,34 @@ export const prepare = async (app, config) => { if (!is.empty(page.init)) { app.init = deep.merge(app.init, page.init) } - - return page }) // collect all static files, // write their buffers into app.static - app.static = await prepareMetaFiles(app) - let staticExists = false + app.static = await prepareMetaFiles(app, config) + try { - await fs.stat(config.DIR.STATIC) - staticExists = true + let staticDirs = config.DIR.STATIC + if (!is.array(staticDirs)) { + staticDirs = [staticDirs] + } + + await Promise.all( + staticDirs.map(async dir => { + const staticFiles = await fs.getFiles(dir) + + if (!is.empty(staticFiles)) { + const staticPromises = staticFiles.map(async f => { + const name = replaceSlashSlash(`/${f.replace(dir, '')}`) + // TODO: use streams here + app.static[name] = await fs.readFile(f) + }) + + await Promise.all(staticPromises) + } + }), + ) } catch (e) { // it's fine if the static dir does not exist, // but all other errors will throw. @@ -99,45 +132,28 @@ export const prepare = async (app, config) => { } } - if (staticExists) { - const staticFiles = await fs.getFiles(config.DIR.STATIC) - if (!is.empty(staticFiles)) { - const staticPromises = staticFiles.map(async f => { - const name = f.replace(config.DIR.STATIC, '') - // TODO: use streams here - app.static[name] = await fs.readFile(f) - }) - - await Promise.all(staticPromises) - } - } - - libs.forEach(lib => { - app.lib[lib.key] = lib.path - }) - - app.style = await prepareThemes({ app, modules }) + app.style = await prepareThemes(app, config) - prepareModules(app, modules) - - app.modules = modules + // mutates app + // init, cookies, actions, effects, state, helpers, subscriptions + prepareModules(app, config) // create client magic.js file - app.client = await prepareJs(app) + app.client = await prepareJs(app, config) + + if (config.SPLIT) { + app.dependencies = await prepareDependencies(app, config) + } // extract lambdas and prepare them - app.lambdas = {} - Object.entries(modules) - .filter(([_, dep]) => dep.server) - .forEach(([name, dep]) => { - app.lambdas[name.toLowerCase()] = dep.server - }) + app.lambdas = await prepareApi(app, config) // app.serviceWorker = await prepareServiceWorker(app, config) const tmpExists = await fs.exists(config.TMP_DIR) - if (tmpExists) { + if (tmpExists && !config.KEEP_CLIENT) { await fs.rmrf(config.TMP_DIR) + await fs.mkdirp(config.TMP_DIR) } return app diff --git a/src/tasks/prepare/js.mjs b/src/tasks/prepare/js.mjs index 601ac475..1e7fa73c 100644 --- a/src/tasks/prepare/js.mjs +++ b/src/tasks/prepare/js.mjs @@ -3,27 +3,26 @@ import path from 'path' import cases from '@magic/cases' import fs from '@magic/fs' import is from '@magic/types' +import log from '@magic/log' +import error from '@magic/error' -import { stringifyObject } from '../../lib/index.mjs' +import { stringifyObject, replaceSlashSlash } from '../../lib/index.mjs' +import { prepareCheckProps } from './checkProps.mjs' + +export const prepareJs = async (magic, config) => { + const { HOIST, PREPEND_JS, APPEND_JS /*, WEB_ROOT*/ } = config -export const prepareJs = async magic => { const hyperappPath = path.join( process.cwd(), 'node_modules', '@magic', 'hyperapp', 'src', - 'index.mjs', + 'hyperapp.mjs', ) const hyperappContent = await fs.readFile(hyperappPath, 'utf8') - // only load Lazy if requested by user - let imports = 'h, app' - if (magic.modules.Lazy) { - imports += ', Lazy' - } - - delete magic.modules.Lazy + const imports = 'h, app' // replace hyperapp exports, wrap it in a closure // return needed exports only to allow dead code elimination @@ -40,60 +39,73 @@ return { ${imports} } .replace(/name/gm, 'n') .replace(/children/gm, 'c') - let checkProps = '' - let propTypeString = '' - if (config.IS_DEV) { - // add proptype checking - checkProps = `const CHECK_PROPS = ${global.CHECK_PROPS.toString()}` - - propTypeString = 'const propTypes = {\n' - propTypeString += Object.entries(magic.modules) - .sort(([a], [b]) => (a > b ? 1 : -1)) - .map(([_, mod]) => { - const subPropTypes = Object.entries(mod) - .sort(([a], [b]) => (a > b ? 1 : -1)) - .filter(([k, { propTypes }]) => is.case.upper(k[0]) && k !== 'View' && propTypes) - .map(([_, sm]) => - Object.entries(sm.propTypes) - .sort(([a], [b]) => (a > b ? 1 : -1)) - .map(([k, t]) => `${k}: ${JSON.stringify(t, null, 2)}`) - .join(',\n'), - ) - .filter(a => a) - .join(',\n') - - let propString = '' - if (mod.propTypes) { - propString = Object.entries(mod.propTypes) - .sort(([a], [b]) => (a > b ? 1 : -1)) - .map(([key, type]) => `${key}: ${JSON.stringify(type, null, 2)}`) - .join(',\n') - } - if (subPropTypes) { - propString += `\n${subPropTypes}` - } - return propString - }) - .filter(a => a) - .join(',\n') + const { checkProps, propTypeString } = prepareCheckProps(magic, config) - propTypeString += '\n}' + let depString = '' + // Reserved JavaScript keywords that can't be used as variable names + const reservedKeywords = new Set([ + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'else', + 'export', + 'extends', + 'false', + 'finally', + 'for', + 'function', + 'if', + 'import', + 'in', + 'instanceof', + 'let', + 'new', + 'null', + 'return', + 'static', + 'super', + 'switch', + 'this', + 'throw', + 'true', + 'try', + 'typeof', + 'undefined', + 'var', + 'void', + 'while', + 'with', + 'yield', + ]) + + const toVarName = name => { + const camelName = cases.camel(name) + return reservedKeywords.has(camelName) ? `${camelName}El` : camelName } - let depString = '' let htmlTagString = '' - Object.entries(magic.modules) + Object.entries({ ...magic.modules, ...magic.tags }) .filter(([k]) => k !== 'Magic' && k !== 'component') .sort(([a], [b]) => (a > b ? 1 : -1)) .forEach(([k, v]) => { if (!is.case.upper(k[0])) { - htmlTagString += `const ${k} = C('${k}')\n` + const varName = toVarName(k) + htmlTagString += `const ${varName} = C('${k}')\n` } else { let str if (is.function(v)) { str = `\nconst ${k} = ${v.toString()}\n` - } else { + } else if (is.function(v.View)) { str = `\nconst ${k} = ${v.View.toString()}\n` + } else { + log.error(`${k} Module does not export a View`, 'E_MODULE_NO_VIEW') } const subStr = Object.entries(v) @@ -106,7 +118,9 @@ return { ${imports} } } }) - let stateString = `const initialState = ${stringifyObject(magic.state)}` + const stateForString = stringifyObject(magic.state) + + let stateString = `const initialState = ${stateForString}` let helperString = '' if (!is.empty(magic.helpers)) { @@ -137,58 +151,55 @@ return { ${imports} } let libString = '' if (!is.empty(magic.lib)) { - libString = 'const lib = {' const libPromises = Object.entries(magic.lib) .sort(([a], [b]) => (a > b ? 1 : -1)) - .map(async ([name, lib]) => { - if (lib.startsWith('@')) { - lib = path.join(process.cwd(), 'node_modules', lib) - } - if (!lib.endsWith('index.mjs')) { - lib = path.join(lib, 'src', 'index.mjs') - } - - const contents = await fs.readFile(lib, 'utf8') + .map(async ([name, fn]) => { if (name.includes('-')) { name = cases.camel(name) } - return ` ${name}: (() => {${contents - .replace(/export default/g, `return`) - .replace(/export /g, '')}})(),` + if (is.function(fn)) { + return ` ${name}: ${fn.toString()},` + } else if (is.array(fn)) { + return ` ${name}: ${JSON.stringify(fn)},` + } else if (is.object(fn) && is.objectNative({ ...fn })) { + return ` ${name}: ${stringifyObject(fn)},` + } else { + log.error( + 'UNKNOWN_LIB_TYPE', + `app.lib.${name} has to be a function, array or object, got: ${typeof app.lib[name]}`, + ) + } }) const libArray = await Promise.all(libPromises) - libString += libArray.join('\n') + + libString = 'const lib = {\n' + libString += libArray.filter(a => a).join('\n') libString += '\n}' } // create pages object, each Page is a html View - let pageString = 'const pages = {\n' - let pageTitles = {} - magic.pages + const pageString = ` +const pages = { + ${magic.pages .sort(({ name: a }, { name: b }) => (a > b ? 1 : -1)) - .forEach(page => { - pageString += `\n '${page.name}': ${page.View.toString()},` - - if (page.state && page.state.title) { - pageTitles[page.name] = page.state.title - } - }) - - pageString += '\n}\n' + .map(page => `\n '${page.name}': ${page.View.toString()},`) + .join('\n')} +} +` - const pageTitleString = Object.entries(pageTitles) - .map(([name, title]) => `"${name}": "${title}",`) - .join('\n ') + // const pageTitleString = Object.entries(pageTitles) + // .map(([name, title]) => `"${name}": "${title}",`) + // .join('\n ') // unused for now - const serviceWorkerString = ` -if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('${config.WEB_ROOT}service-worker.js') -} -` + // const serviceWorkerString = ` + // if ('serviceWorker' in navigator) { + // navigator.serviceWorker.register('${WEB_ROOT}service-worker.js') + // } + // ` let initState = `{ ...initialState, @@ -199,7 +210,26 @@ if ('serviceWorker' in navigator) { let initFunc = '' if (!is.empty(magic.init)) { - initFunc = `[${magic.init.join(',')}]` + if (is.array(magic.init)) { + if (is.string(magic.init[0]) && is.len(magic.init, 1)) { + const [name, value] = magic.init + initFunc = `[[${name}, ${stringifyObject(value)}]]` + } else { + const init = magic.init.map(fn => { + if (is.array(fn) && is.len(fn, 1) && is.string(fn[0])) { + return `[${fn[0]}, ${stringifyObject(fn[1])}]` + } else if (is.string(fn)) { + return fn + } else if (is.fn(fn)) { + return fn.toString() + } + }) + + initFunc = `[${init.join(',')}]` + } + } else { + throw error('E_BROKEN_INIT', 'magic.init must be an array.') + } } let initString = initState @@ -209,22 +239,24 @@ if ('serviceWorker' in navigator) { } let hoisted = '' - if (!is.empty(config.HOIST)) { - if (is.array(config.HOIST)) { - hoisted = config.HOIST.map(component => `${component}(state)`).join(',') - if (config.HOIST.length > 1) { + if (!is.empty(HOIST)) { + if (is.array(HOIST)) { + hoisted = HOIST.map(component => `${component}(state)`).join(',') + if (HOIST.length > 1) { hoisted = `[${hoisted}]` - } else if (config.HOIST.length === 0) { + } else if (HOIST.length === 0) { hoisted = '' } - } else if (is.string(config.HOIST)) { - hoisted = config.HOIST + } else if (is.string(HOIST)) { + hoisted = HOIST if (!hoisted.includes('state')) { hoisted = `${hoisted}(state)` } } } + app.hoisted = hoisted + // generate string to write to client js const appString = ` app({ @@ -242,9 +274,7 @@ app({ }) } - state.titles = { - ${pageTitleString} - } + state.url = url return Page({ page, state }${hoisted ? `, ${hoisted}` : ''}) }, @@ -252,7 +282,33 @@ app({ }) ` + const prependJSContents = PREPEND_JS.map(file => { + const fullFilePath = replaceSlashSlash(`/${file}`) + const fileContent = app.static[fullFilePath] + + if (is.empty(fileContent) || !is.function(fileContent.toString)) { + throw error(`fileContent undefined for config.PREPEND_JS: ${file}`, 'E_PREPEND_JS_EMPTY') + } + + return fileContent.toString() + }) + + const appendJSContents = APPEND_JS.map(file => { + const fullFilePath = replaceSlashSlash(`/${file}`) + const fileContent = app.static[fullFilePath] + + if (is.empty(fileContent) || !is.function(fileContent.toString)) { + throw error(`fileContent undefined for config.APPEND_JS: ${file}`, 'E_PREPEND_JS_EMPTY') + } + + return fileContent + }) + const clientString = [ + prependJSContents.join('\n'), + + // isolate magic js from the rest of the javascript + 'const __MAGIC__ = () => {', hyperapp, checkProps, propTypeString, @@ -267,6 +323,13 @@ app({ pageString, appString, // serviceWorkerString, + + // isolate magic js from the rest of the javascript + '}', + '__MAGIC__()', + '\n', + + appendJSContents.join('\n'), ] .join('\n') .trim() diff --git a/src/tasks/prepare/meta.mjs b/src/tasks/prepare/meta.mjs index e0468b4f..5970034b 100644 --- a/src/tasks/prepare/meta.mjs +++ b/src/tasks/prepare/meta.mjs @@ -1,5 +1,7 @@ import is from '@magic/types' +import { replaceSlashSlash } from '../../lib/index.mjs' + const sitemapHeader = ` ` -export const prepareMetaFiles = async app => { +export const prepareMetaFiles = async (app, config) => { const res = {} - const remote = config.URL + const { CNAME, ROBOTS_TXT, SITEMAP, URL, WEB_ROOT } = config + + if (ROBOTS_TXT) { + const sitemapUrl = replaceSlashSlash(`https://${URL}/sitemap.xml`) - if (config.ROBOTS_TXT) { - res['/robots.txt'] = [ - 'user-agent: *', - 'allow: /', - `sitemap: https://${remote}/sitemap.xml\n`, - ].join('\n') + res['/robots.txt'] = ['user-agent: *', 'allow: /', `sitemap: ${sitemapUrl}\n`].join('\n') } - if (config.SITEMAP) { + if (SITEMAP) { const sitemapArray = [sitemapHeader] app.pages @@ -36,6 +36,7 @@ export const prepareMetaFiles = async app => { if (month < 10) { month = `0${month}` } + let day = now.getDate() if (day < 10) { day = `0${day}` @@ -43,9 +44,11 @@ export const prepareMetaFiles = async app => { const changeDate = `${now.getFullYear()}-${month}-${day}` + const sitemapItemUrl = `https://${replaceSlashSlash(`${URL}${name.replace(WEB_ROOT, '/')}`)}` + sitemapArray.push(` - https://${config.URL}${name.replace(config.WEB_ROOT, '/')} + ${sitemapItemUrl} ${changeDate} weekly 0.5 @@ -58,8 +61,8 @@ export const prepareMetaFiles = async app => { res['/sitemap.xml'] = sitemapArray.join('').trim() } - if (config.CNAME) { - res['/CNAME'] = config.URL.trim() + if (CNAME) { + res['/CNAME'] = URL.trim() } return res diff --git a/src/tasks/prepare/modules.mjs b/src/tasks/prepare/modules.mjs index 77127c1a..6607e6f6 100644 --- a/src/tasks/prepare/modules.mjs +++ b/src/tasks/prepare/modules.mjs @@ -1,65 +1,81 @@ import deep from '@magic/deep' import is from '@magic/types' +import log from '@magic/log' -export const prepareModule = app => ([name, component]) => { - const lowerName = name.toLowerCase() +export const prepareModule = + app => + ([name, component]) => { + const lowerName = name.toLowerCase() - const glob = component.global || {} + const glob = component.global || component.globals || {} - if (!is.empty(component.state)) { - Object.entries(component.state).forEach(([key, val]) => { - if (glob.state && glob.state[key] === true) { - app.state[key] = val - } else { - app.state[lowerName] = app.state[lowerName] || {} - app.state[lowerName][key] = val - } - }) - } - - const actionTypes = ['actions', 'effects'] + const tempState = {} - actionTypes - // if component has no actions or effects, do nothing - .filter(type => !is.empty(component[type])) - .forEach(type => { - Object.entries(component[type]).forEach(([key, val]) => { - if (glob[type] && glob[type][key] === true) { - app[type][key] = val + if (!is.empty(component.state)) { + Object.entries(component.state).forEach(([key, val]) => { + if (glob.state && glob.state[key] === true) { + tempState[key] = val } else { - app[type][lowerName] = app[type][lowerName] || {} - if (lowerName === key) { - app[type][lowerName] = val + tempState[lowerName] = tempState[lowerName] || {} + tempState[lowerName][key] = val + } + }) + } + + app.state = { + ...tempState, + ...app.state, + } + + const actionTypes = ['actions', 'effects'] + + actionTypes + // if component has no actions or effects, do nothing + .filter(type => !is.empty(component[type])) + .forEach(type => { + Object.entries(component[type]).forEach(([key, val]) => { + if (glob[type] && glob[type][key] === true) { + app[type][key] = val } else { - app[type][lowerName][key] = val + app[type][lowerName] = app[type][lowerName] || {} + if (lowerName === key) { + app[type][lowerName] = val + } else { + app[type][lowerName][key] = val + } } - } + }) }) - }) - if (!is.empty(component.helpers)) { - app.helpers = deep.merge(app.helpers, component.helpers) - } + if (!is.empty(component.helpers)) { + log.warn( + 'E_HELPERS_DEPRECATED', + `module: ${name}`, + 'using app.helpers is deprecated, use actions, effects or lib instead.', + ) - if (!is.empty(component.subscriptions)) { - component.subscriptions.forEach(sub => { - app.subscriptions.push(sub) - }) - } + app.helpers = deep.merge(app.helpers, component.helpers) + } - if (!is.empty(component.cookies)) { - app.cookies = deep.merge(app.cookies, component.cookies) - } + if (!is.empty(component.subscriptions)) { + component.subscriptions.forEach(sub => { + app.subscriptions.push(sub) + }) + } + + if (!is.empty(component.cookies)) { + app.cookies = deep.merge(app.cookies, component.cookies) + } - if (!is.empty(component.init)) { - app.init = deep.merge(app.init, component.init) + if (!is.empty(component.init)) { + app.init = deep.merge(app.init, component.init) + } } -} -export const prepareModules = (app, modules) => { +export const prepareModules = app => { // merge component states into app.state[componentName]. // this makes all identical components share their state. - Object.entries(modules) + Object.entries(app.modules) .filter(([name]) => is.case.upper(name[0])) .forEach(prepareModule(app)) } diff --git a/src/tasks/prepare/page.mjs b/src/tasks/prepare/page.mjs index bb38611a..ea9cb8fb 100644 --- a/src/tasks/prepare/page.mjs +++ b/src/tasks/prepare/page.mjs @@ -6,93 +6,122 @@ import fs from '@magic/fs' import log from '@magic/log' import transmute from '@magic/transmute' -export const preparePage = ({ WEB_ROOT, PAGES }) => async file => { - const ext = path.extname(file) - - const markdownExtensions = ['.md', '.markdown'] - const htmlExtensions = ['.html', '.htm'] - - let transmuted = {} - - if (markdownExtensions.includes(ext)) { - const content = await fs.readFile(file, 'utf8') - transmuted = transmute.markdown(content) - file = file.replace('.md', '.mjs').replace('.markdown', '.mjs') - } else if (htmlExtensions.includes(ext)) { - const content = await fs.readFile(file, 'utf8') - transmuted = transmute.html(content) - file = file.replace('.html', '.mjs').replace('.htm', '.mjs') - } +import { replaceSlashSlash, saveImport } from '../../lib/index.mjs' + +export const preparePage = + ({ WEB_ROOT, pageDir, state = {}, config }) => + async file => { + const ext = path.extname(file) + + const markdownExtensions = ['.md', '.markdown'] + const htmlExtensions = ['.html', '.htm'] + + let transmuted = {} + + if (markdownExtensions.includes(ext)) { + const content = await fs.readFile(file, 'utf8') + transmuted = transmute.markdown(content, state) + file = file.replace('.md', '.mjs').replace('.markdown', '.mjs') + } else if (htmlExtensions.includes(ext)) { + const content = await fs.readFile(file, 'utf8') + transmuted = transmute.html(content, state) + file = file.replace('.html', '.mjs').replace('.htm', '.mjs') + } - let pageTmp - if (!is.empty(transmuted)) { - const viewString = `export const View = () => [${transmuted.rendered}]` + let pageTmp + if (!is.empty(transmuted)) { + const viewString = `export const View = state => [${transmuted.rendered}]` - const fileTmpPath = path.join(config.TMP_DIR, path.basename(file)) - await fs.mkdirp(config.TMP_DIR) - await fs.writeFile(fileTmpPath, viewString) - pageTmp = await import(fileTmpPath) - } else { - pageTmp = await import(file) - } + const fileTmpPath = path.join(config.TMP_DIR, file.replace(pageDir, '')) - let page - if (is.fn(pageTmp)) { - page = { - ...transmuted, - View: pageTmp, + const subDir = path.dirname(fileTmpPath) + await fs.mkdirp(subDir) + + await fs.writeFile(fileTmpPath, viewString) + pageTmp = await saveImport(fileTmpPath) + } else { + try { + pageTmp = await saveImport(file) + } catch (e) { + if (e.code === 'ERR_UNKNOWN_FILE_EXTENSION') { + log.warn('UNKNOWN_FILE_TYPE', `${file} is not an es6 component. Ignored.`) + return + } + } } - } else if (transmuted) { - const { rendered, ...t } = transmuted - page = { ...t, ...pageTmp } - } else { - page = { ...pageTmp } - } - page.file = file + let page = { + state: {}, + } - if (!is.empty(transmuted.state)) { - page.state = transmuted.state - } + if (is.fn(pageTmp)) { + page = { + ...transmuted, + View: pageTmp, + } + } else if (transmuted) { + const { rendered, ...t } = transmuted + page = { ...t, ...pageTmp } + } else { + page = { ...pageTmp } + } - const pageName = file - .replace(PAGES, '') - .replace(/index.[m]?js/gm, '') - .replace(/.[m]?js/gm, '/') + page.file = file.replace(/\\/gim, '/') - if (WEB_ROOT !== '/') { - page.name = `${WEB_ROOT}${pageName}` - } else { - page.name = pageName - } + // has to be initialized! + // we also have to make sure to create a copy, + // page.state may have been imported from a module + // and that would lead to recursion in lib/stringifyObject + if (page.state) { + page.state = { ...page.state } + } else { + page.state = {} + } - page.path = page.name + if (!is.empty(transmuted.originalState)) { + if (is.fn(transmuted.originalState)) { + transmuted.originalState = transmuted.originalState(config) + } - if (page.path.endsWith('/')) { - if (page.name === '/404/') { - // page path for 404 is /404.html, not /404/index.html - page.path = `${page.path.slice(0, -1)}.html` - } else { - page.path = path.join(page.path, 'index.html') + page.state = { + ...page.state, + ...transmuted.originalState, + } } - } - if (!page.View || !is.function(page.View.toString)) { - const pageDir = PAGES.replace(process.cwd(), '') - // remove slashes - const pageName = page.name.replace(/\//g, '') - - throw error( - ` -${pageDir}/${pageName}.mjs -needs to either -export default () => [] -or -export const View = () => [] + const pageName = file + .replace(pageDir, '') + .replace(/index.[m]?js/gm, '') + .replace(/.[m]?js/gm, '/') + .replace(/\\/gim, '/') + + page.name = replaceSlashSlash(`${WEB_ROOT}${pageName}`) + + page.path = page.name + + if (page.path.endsWith('/')) { + if (page.name === '/404/') { + // page path for 404 is /404.html, not /404/index.html + page.path = `${page.path.slice(0, -1)}.html` + } else { + page.path = `${page.path}index.html` + } + } + + if (!page.View || !is.function(page.View.toString)) { + const relativePageDir = pageDir.replace(process.cwd(), '') + // remove all slashes + const pageName = page.name.replace(/\//g, '') + + throw error( + ` +${relativePageDir}/${pageName}.mjs +needs to +export const View = state => [] `, - 'E_INVALID_PAGE', - ) - } + 'E_INVALID_PAGE', + ) + } - return page -} + return page + } diff --git a/src/tasks/prepare/pages.mjs b/src/tasks/prepare/pages.mjs index c3312c8e..abea598c 100644 --- a/src/tasks/prepare/pages.mjs +++ b/src/tasks/prepare/pages.mjs @@ -1,26 +1,39 @@ import { preparePage } from './page.mjs' -export const preparePages = async app => { - let WEB_ROOT = config.WEB_ROOT - if (WEB_ROOT && WEB_ROOT.endsWith('/')) { - WEB_ROOT = WEB_ROOT.slice(0, -1) - } +import { replaceSlashSlash } from '../../lib/index.mjs' + +export const preparePages = async (app, config) => { + const { WEB_ROOT, DIR } = config + + const pageWorker = preparePage({ WEB_ROOT, pageDir: DIR.PAGES, state: app.state, config }) - const pages = await Promise.all(app.files.map(preparePage({ WEB_ROOT, PAGES: config.DIR.PAGES }))) + let pages = await Promise.all(app.files.map(pageWorker)) + + if (app.pages) { + pages = [...app.pages, ...pages] + } - const has404 = pages.some(p => p.name === `${config.WEB_ROOT}404/`) + const has404 = pages.some(p => p && p.name === replaceSlashSlash(`${WEB_ROOT}/404/`)) if (!has404) { const page404 = { - name: `${config.WEB_ROOT}404/`, - path: `${config.WEB_ROOT}/404.html`, - View: () => div('404 - not found'), + name: replaceSlashSlash(`${WEB_ROOT}/404/`), + path: replaceSlashSlash(`${WEB_ROOT}/404.html`), + View: () => div('404 - not found.'), + state: { + title: '404 - not found', + description: '404 - not found.', + }, + originalState: { + title: '404 - not found', + description: '404 - not found.', + }, } pages.push(page404) } - return pages + return pages.filter(a => a) } export default preparePages diff --git a/src/tasks/prepare/service-worker.mjs b/src/tasks/prepare/service-worker.mjs index 22867f19..780ee0c1 100644 --- a/src/tasks/prepare/service-worker.mjs +++ b/src/tasks/prepare/service-worker.mjs @@ -1,18 +1,18 @@ -import { createFileHash } from '../../lib/index.mjs' +import { createHash, replaceSlashSlash } from '../../lib/index.mjs' -export const prepareServiceWorker = async (app, config) => { +export const prepareServiceWorker = async (app, { CLIENT_LIB_NAME, WEB_ROOT }) => { const staticFiles = Object.keys(app.static) .filter(f => !f.endsWith('sitemap.xml') || !f.endsWith('robots.txt')) - .map(file => `'${config.WEB_ROOT}${file}',`.replace(/\/\//g, '/')) + .map(file => replaceSlashSlash(`'${WEB_ROOT}${file}',`)) const cacheFileString = `[ - '${config.WEB_ROOT}', - '${config.WEB_ROOT}${config.CLIENT_LIB_NAME}.css', - '${config.WEB_ROOT}${config.CLIENT_LIB_NAME}.js', + '${WEB_ROOT}', + '${WEB_ROOT}${CLIENT_LIB_NAME}.css', + '${WEB_ROOT}${CLIENT_LIB_NAME}.js', ${staticFiles.join('')} ]` - const fileHash = await createFileHash(cacheFileString) + const fileHash = await createHash(cacheFileString) const serviceWorkerString = ` const cacheFiles = ${cacheFileString} diff --git a/src/tasks/prepare/stateLinks.mjs b/src/tasks/prepare/stateLinks.mjs new file mode 100644 index 00000000..7099d0fd --- /dev/null +++ b/src/tasks/prepare/stateLinks.mjs @@ -0,0 +1,49 @@ +import is from '@magic/types' + +import { handleLink } from '../../lib/index.mjs' + +const traverseLinks = ({ state, parent, WEB_ROOT, ...args }) => { + if (state.to && parent) { + state.to = handleLink({ href: state.to, parent, WEB_ROOT, ...args }) + + if (state.items) { + state.items = state.items.map(item => + traverseLinks({ state: item, parent: state, WEB_ROOT, ...args }), + ) + } + + return state + } + + const result = Object.entries(state).map(([key, value]) => { + if (is.array(value)) { + value = value.map(val => { + if (is.string(val)) { + return val + } + + const traversed = traverseLinks({ state: val, parent: state, WEB_ROOT, ...args }) + return traversed + }) + } else if (is.objectNative(value)) { + value = traverseLinks({ state: value, WEB_ROOT, ...args }) + } + + return [key, value] + }) + + return Object.fromEntries(result) +} + +export const prepareStateLinks = (app, { WEB_ROOT }) => { + const { logo, seo } = app.state + if (!is.empty(logo) && is.string(logo)) { + app.state.logo = handleLink({ href: logo, app, WEB_ROOT }) + } + + if (!is.empty(seo?.image) && is.string(seo?.image)) { + app.state.seo.image = handleLink({ href: seo.image, app, WEB_ROOT }) + } + + return traverseLinks({ state: app.state, app, WEB_ROOT }) +} diff --git a/src/tasks/prepare/themes.mjs b/src/tasks/prepare/themes.mjs index c8798f31..4d8f158e 100644 --- a/src/tasks/prepare/themes.mjs +++ b/src/tasks/prepare/themes.mjs @@ -1,24 +1,28 @@ import path from 'path' +import { fileURLToPath } from 'url' import error from '@magic/error' import is from '@magic/types' import deep from '@magic/deep' -import { findModuleStyles } from '../../lib/index.mjs' +import { findModuleStyles, saveImport } from '../../lib/index.mjs' import colors from '../../themes/colors.mjs' -const url = new URL(import.meta.url) -const dirName = path.dirname(url.pathname) +const dirName = path.dirname(fileURLToPath(import.meta.url)) + +export const prepareThemes = async (app, config) => { + const { DIR, NODE_MODULES, ROOT } = config + let { THEME = '', THEME_VARS = {} } = config -export const prepareThemes = async ({ app, modules }) => { const resetStyles = [] const themeStyles = [] const pageStyles = [] const appStyles = [] - let { THEME = '', THEME_VARS = {} } = config - - THEME_VARS.colors = colors + THEME_VARS.colors = { + ...THEME_VARS.colors, + ...colors, + } if (is.string(THEME)) { THEME = [THEME] @@ -27,16 +31,17 @@ export const prepareThemes = async ({ app, modules }) => { // merge user created custom reset.css into styles, if it exists try { // merge default reset css into styles - const libResetCssFile = path.join(dirName, '..', '..', 'themes', 'reset.css.mjs') - let { reset } = await import(libResetCssFile) + let { reset } = await saveImport('../themes/reset.css.mjs') resetStyles.push(reset) const themePromises = THEME.map(async theme_name => { // find reset css in theme dir if it exists - const maybeResetCssFile = path.join(config.DIR.THEMES, theme_name, 'reset.css.mjs') - const { default: maybeResetCssStyles } = await import(maybeResetCssFile) - resetStyles.push(maybeResetCssStyles) + const maybeResetCssFile = path.join(DIR.THEMES, theme_name, 'reset.css.mjs') + const { default: maybeResetCssStyles } = await saveImport(maybeResetCssFile) + if (maybeResetCssStyles) { + resetStyles.push(maybeResetCssStyles) + } }) await Promise.all(themePromises) @@ -48,9 +53,7 @@ export const prepareThemes = async ({ app, modules }) => { // load user's chosen theme, if it is set and exists, and merge it into the styles if (THEME) { - let themeVars = {} - - await Promise.all( + const result = await Promise.all( THEME.map(async theme_name => { // order is meaningful. const themeLocations = [ @@ -62,21 +65,22 @@ export const prepareThemes = async ({ app, modules }) => { // see if this is a @magic-themes theme `@magic-themes/${theme_name}`, // see if it is installed locally. - path.join(config.DIR.THEMES, theme_name, 'index.mjs'), + path.join(DIR.THEMES, theme_name, 'index.mjs'), + // if called from globally installed magic, load with absolute path + path.join(NODE_MODULES, '@magic-themes', theme_name, 'src', 'index.mjs'), ] - await Promise.all( + return await Promise.all( themeLocations.map(async location => { try { - const { default: theme, vars } = await import(location) - - if (!is.empty(vars)) { - themeVars = deep.merge(themeVars, vars) + const { default: theme, vars } = await saveImport(location) + return { + theme, + vars, } - - themeStyles.push(theme) } catch (e) { - if (!e.code || !e.code.includes('MODULE_NOT_FOUND')) { + const inc = e.message.endsWith('themes.mjs') || e.message.endsWith('themes.js') + if (!inc || !e.code || !e.code.includes('MODULE_NOT_FOUND')) { throw error(e) } } @@ -85,6 +89,23 @@ export const prepareThemes = async ({ app, modules }) => { }), ) + let themeVars = {} + + result + .filter(a => a) + .forEach(list => { + list + .filter(a => a) + .forEach(({ theme, vars }) => { + if (!is.empty(vars)) { + themeVars = deep.merge(themeVars, vars) + } + if (!is.empty(theme)) { + themeStyles.push(theme) + } + }) + }) + THEME_VARS = deep.merge(themeVars, THEME_VARS) } @@ -94,14 +115,17 @@ export const prepareThemes = async ({ app, modules }) => { pageStyles.push(page.style) }) - const maybeAppFile = path.join(config.ROOT, 'app.mjs') + const maybeAppFile = path.join(ROOT, 'app.mjs') const fileName = new URL(import.meta.url).pathname if (maybeAppFile !== fileName) { try { - const { style, styleVars } = await import(maybeAppFile) + const { style, styleVars } = await saveImport(maybeAppFile) if (style) { appStyles.push(style) } + if (styleVars) { + THEME_VARS = deep.merge(THEME_VARS, styleVars) + } } catch (e) { if (!e.code || !e.code.includes('MODULE_NOT_FOUND')) { throw error(e) @@ -110,7 +134,7 @@ export const prepareThemes = async ({ app, modules }) => { } // load all styles from all modules - const moduleStyles = findModuleStyles(modules, THEME_VARS) + const moduleStyles = findModuleStyles(app.modules, THEME_VARS) const styles = [...resetStyles, moduleStyles, ...themeStyles, ...pageStyles, ...appStyles] diff --git a/src/tasks/publish/index.mjs b/src/tasks/publish/index.mjs index d101f4f7..06d799e4 100644 --- a/src/tasks/publish/index.mjs +++ b/src/tasks/publish/index.mjs @@ -2,9 +2,9 @@ import log from '@magic/log' import { getGitConfig, xc } from '../../lib/index.mjs' -export const publish = async config => { - const git = await getGitConfig(config) - const outDir = config.DIR.PUBLIC.replace(`${process.cwd()}/`, '') +export const publish = async ({ DIR, FORCE = false, GIT }) => { + const git = await getGitConfig(GIT) + const outDir = DIR.PUBLIC.replace(`${process.cwd()}/`, '') const cmdPrefix = `--prefix=${outDir}` const cmdOnto = `--onto=${git.ORIGIN}/${git.BRANCH}` @@ -16,7 +16,7 @@ export const publish = async config => { const id = stdout.trim() log.timeEnd(cmd) - const pushCommand = `git push ${git.ORIGIN} ${id.trim()}:${git.BRANCH}` + const pushCommand = `git push${FORCE ? ' -f' : ''} ${git.ORIGIN} ${id.trim()}:${git.BRANCH}` log.time(pushCommand) await xc(pushCommand) log.timeEnd(pushCommand) diff --git a/src/tasks/serve/apiHandler.mjs b/src/tasks/serve/apiHandler.mjs new file mode 100644 index 00000000..7bafa7a5 --- /dev/null +++ b/src/tasks/serve/apiHandler.mjs @@ -0,0 +1,63 @@ +import is from '@magic/types' +import log from '@magic/log' + +export const apiHandler = async (req, res, { lambdas, rawUrl }) => { + if (rawUrl === '/api/' || rawUrl === '/api') { + const code = 200 + + const lambdaEntries = Object.fromEntries( + Object.entries(lambdas).map(([k, v]) => [k, typeof v === 'function' ? true : v]), + ) + + const body = JSON.stringify(lambdaEntries, null, 2) + res.writeHead(code, { 'Content-Type': 'text/plain' }) + res.end(body) + return + } + + const [module, action] = rawUrl + .replace('/api/', '') + .split('/') + .filter(a => a) + + let lambda = lambdas[module] + + if (is.objectNative(lambda) && action) { + lambda = lambda[action] + } + + if (is.function(lambda)) { + req.body = [] + + req.on('data', chunk => { + if (is.string(chunk)) { + chunk = Buffer.from(chunk) + } + + req.body.push(chunk) + }) + + req.on('end', async (...args) => { + req.body = Buffer.concat(req.body).toString() + + if (req.headers['content-type'] === 'application/json') { + try { + req.body = JSON.parse(req.body) + } catch (e) { + log.error('E_JSON_PARSE', e) + req.body = e + } + } + + const result = await lambda(req, res, ...args) + const { code = 500, body = 'Internal Server Error', type = 'text/plain' } = result + + res.writeHead(code, { 'Content-Type': type }) + res.end(body) + }) + + return true + } + + return false +} diff --git a/src/tasks/serve/handler.mjs b/src/tasks/serve/handler.mjs index 0fc7f955..266a8def 100644 --- a/src/tasks/serve/handler.mjs +++ b/src/tasks/serve/handler.mjs @@ -1,97 +1,115 @@ -import path from 'path' -import URL from 'url' - import fs from '@magic/fs' -import is from '@magic/types' import { addTrailingSlash } from '../../lib/index.mjs' +import { prepareApi } from './prepareApi.mjs' +import { apiHandler } from './apiHandler.mjs' + +export const handler = async (app, config) => { + const { + IS_PROD, + CLIENT_LIB_NAME, + //CLIENT_SERVICE_WORKER_NAME, + FOR_DEATH_CAN_NOT_HAVE_HIM, + HOST, + } = config + + const { + css, + client, + static: stat, + lambdas: rawLambdas, + // sw + } = app -export const handler = app => (req, res) => { - const { IS_PROD } = config - const { css, client, static: stat, lambdas, sw } = app const WEB_ROOT = addTrailingSlash(config.WEB_ROOT) - const url = URL.parse(req.url) - let { pathname } = url - if (pathname.startsWith(WEB_ROOT)) { - pathname = pathname.replace(WEB_ROOT, '/') - } + const lambdas = await prepareApi(rawLambdas) - const rawUrl = url.pathname.replace(config.WEB_ROOT, '/') + return async (req, res) => { + const url = new URL(req.url, `http://${HOST}`) + let { pathname } = url - if (rawUrl.startsWith('/api')) { - const action = rawUrl.replace('/api/', '').replace('/', '') - if (is.function(lambdas[action])) { - req.body = '' - req.on('data', chunk => (req.body += chunk)) + if (pathname.startsWith(WEB_ROOT)) { + pathname = pathname.replace(WEB_ROOT, '/') + } - req.on('end', (...args) => lambdas[action](req, res, ...args)) - return + const rawUrl = url.pathname.replace(WEB_ROOT, '/') + + if (rawUrl.startsWith('/api')) { + const handled = await apiHandler(req, res, { lambdas, rawUrl }) + if (handled) { + return + } else { + const headers = { + 'Cache-Control': 'no-cache, must-revalidate', + Pragma: 'no-cache', + 'Content-Type': 'text/plain', + } + + res.writeHead(404, headers) + res.end('Api route not found') + return + } } - } - const pages = {} - app.pages.forEach(page => { - pages[page.name] = page.rendered - }) + const pages = Object.fromEntries(app.pages.map(page => [page.name, page.rendered])) - const style = IS_PROD ? css.minified : css.css + const style = IS_PROD ? css.minified : css.css - const expiryTime = new Date(new Date().getTime() - 1000).toUTCString() - const headers = { - Expires: expiryTime, - 'Cache-Control': 'no-cache, must-revalidate', - Pragma: 'no-cache', - } + const expiryTime = new Date(new Date().getTime() - 1000).toUTCString() + const headers = { + Expires: expiryTime, + 'Cache-Control': 'no-cache, must-revalidate', + Pragma: 'no-cache', + } - const cssUrl = `/${config.CLIENT_LIB_NAME}.css` + const cssUrl = `/${CLIENT_LIB_NAME}.css` - if (rawUrl === cssUrl) { - res.writeHead(200, { ...headers, 'Content-Type': 'text/css' }) - res.end(style) - return - } + if (rawUrl === cssUrl) { + res.writeHead(200, { ...headers, 'Content-Type': 'text/css' }) + res.end(style) + return + } - const jsUrl = `/${config.CLIENT_LIB_NAME}.js` - if (rawUrl === jsUrl) { - res.writeHead(200, { ...headers, 'Content-Type': 'application/javascript' }) - res.end(client) - return - } + const jsUrl = `/${CLIENT_LIB_NAME}.js` + if (rawUrl === jsUrl) { + res.writeHead(200, { ...headers, 'Content-Type': 'application/javascript' }) + res.end(client) + return + } - // const swUrl = `/${config.CLIENT_SERVICE_WORKER_NAME}.js` - // if (rawUrl === swUrl) { - // res.writeHead(200, { ...headers, 'Content-Type': 'application/javascript' }) - // res.end(sw) - // return - // } - - if (stat[rawUrl]) { - const contentType = fs.getContentType(rawUrl) - res.writeHead(200, { ...headers, 'Content-Type': contentType }) - res.end(stat[rawUrl]) - return - } + // const swUrl = `/${CLIENT_SERVICE_WORKER_NAME}.js` + // if (rawUrl === swUrl) { + // res.writeHead(200, { ...headers, 'Content-Type': 'application/javascript' }) + // res.end(sw) + // return + // } + + if (stat[rawUrl]) { + const contentType = fs.getContentType(rawUrl) + res.writeHead(200, { ...headers, 'Content-Type': contentType }) + res.end(stat[rawUrl]) + return + } + + if (pages[url.pathname]) { + if (FOR_DEATH_CAN_NOT_HAVE_HIM) { + headers['X-Clacks-Overhead'] = 'GNU Terry Pratchet' + } - if (pages[url.pathname]) { - if (config.FOR_DEATH_CAN_NOT_HAVE_HIM) { - headers['X-Clacks-Overhead'] = 'GNU Terry Pratchet' + res.writeHead(200, { ...headers, 'Content-Type': 'text/html' }) + res.end(pages[url.pathname]) + return } - res.writeHead(200, { ...headers, 'Content-Type': 'text/html' }) - res.end(pages[url.pathname]) - return - } + // 404. we redirect to the root + let Location = WEB_ROOT - // 404. we redirect to the root - let Location = WEB_ROOT + if (pages[`${url.pathname}/`]) { + Location = `${url.pathname}/` + } - if (pages[`${url.pathname}/`]) { - Location = `${url.pathname}/` + res.writeHead(302, { Location }) + res.end() } - - res.writeHead(302, { Location }) - res.end() } - -export default handler diff --git a/src/tasks/serve/index.mjs b/src/tasks/serve/index.mjs index 7c4f603f..d0f25354 100644 --- a/src/tasks/serve/index.mjs +++ b/src/tasks/serve/index.mjs @@ -3,53 +3,58 @@ import http from 'http' import error from '@magic/error' import log from '@magic/log' -import handler from './handler.mjs' - -// return a thenable async/await struct -export const listen = async server => - await { - then(r, f) { - server.on('listening', r) - server.on('error', f) - }, - } +import { handler } from './handler.mjs' + +const MAX_PORT_RETRIES = 10 -export const startServer = async (server, options) => { +export const startServer = async (server, options, attempts = 0) => { try { server.listen(options) - await listen(server) + + // await a thenable async/await struct + // this "promise" waits for the server either erroring or starting to listen + await { + then(r, f) { + server.on('listening', r) + server.on('error', f) + }, + } } catch (e) { + // if the requested port is in use, + // increment portnumber by 1, then retry starting the server if (e.code === 'EADDRINUSE') { + if (attempts >= MAX_PORT_RETRIES) { + throw error(`Could not find available port after ${MAX_PORT_RETRIES} attempts`) + } options.port += 1 + return startServer(server, options, attempts + 1) } else { throw error(e) } - - return startServer(server, options) } return options } -export const serve = async app => { - const handle = await handler(app) +export const serve = async (app, config) => { + const handle = await handler(app, config) + + const { PORT, HOST, WEB_ROOT } = config const server = http.createServer(handle) const options = { - port: config.PORT, - host: config.HOST, - // do not make other workers listen to the same port - // exclusive: true, + port: PORT, + host: HOST, } try { const { port, host } = await startServer(server, options) - if (port !== config.PORT) { + if (port !== PORT) { log.warn('Address in use', `incrementing port to ${port}...`) } - log(`listening to http://${host}:${port}${config.WEB_ROOT}`) + log(log.paint.green('LISTENING TO'), `http://${host}:${port}${WEB_ROOT}`) } catch (e) { log.error(e) } diff --git a/src/tasks/serve/prepareApi.mjs b/src/tasks/serve/prepareApi.mjs new file mode 100644 index 00000000..bc797f4c --- /dev/null +++ b/src/tasks/serve/prepareApi.mjs @@ -0,0 +1,27 @@ +import is from '@magic/types' +import log from '@magic/log' + +export const prepareApi = async (rawLambdas = {}) => { + const lambdaPromises = await Promise.all( + Object.entries(rawLambdas).map(async ([key, lambda]) => { + try { + const result = await lambda() + + if (is.fn(result)) { + return [key, result] + } else { + log.error('E_NOT_A_FUNCTION', 'result is not a function', result) + } + } catch (e) { + // lambda does not return a creator function, + // use as is by returning it below + log.error('E_BUILDING_LAMBDA', e) + } + + return [key, lambda] + }), + ) + + const lambdas = Object.fromEntries(lambdaPromises) + return lambdas +} diff --git a/src/tasks/transpile/css.mjs b/src/tasks/transpile/css.mjs index a5bc6e21..487efe77 100644 --- a/src/tasks/transpile/css.mjs +++ b/src/tasks/transpile/css.mjs @@ -1,8 +1,3 @@ import css from '@magic/css' -export default async style => { - // reset = await css(reset, config.THEME_VARS) - style = await css(style, config.THEME_VARS) - - return style -} +export default css diff --git a/src/tasks/transpile/html.mjs b/src/tasks/transpile/html.mjs index 79fd6da7..1e4a04b2 100644 --- a/src/tasks/transpile/html.mjs +++ b/src/tasks/transpile/html.mjs @@ -1,46 +1,49 @@ -import { default as render } from 'hyperapp-render' +import { renderToString } from '@magic/hyperapp' import deep from '@magic/deep' import log from '@magic/log' -const maybePrependWebRoot = s => { - // non local links or already webrooted links - if (!s.startsWith('/') || s.startsWith(config.WEB_ROOT)) { - return s - } +import { replaceSlashSlash } from '../../lib/replaceSlashSlash.mjs' - // prepend WEB_ROOT to link - return config.WEB_ROOT.slice(0, -1) + s -} - -export default (app, hashes) => { - app.state.root = config.WEB_ROOT +export default ({ app, root }) => { + app.state.root = root return app.pages.map(page => { try { app.state.url = page.name const state = deep.merge(app.state, page.state) - const view = app.View(page, hashes) + const view = app.View(page, app.hashes) - let rendered = render.renderToString(view(state)) + const pageObj = view(state) - if (config.WEB_ROOT !== '/') { - rendered = rendered - .split('href="') - .map(maybePrependWebRoot) - .join('href="') - .split('src="') - .map(maybePrependWebRoot) - .join('src="') - } + let rendered = renderToString(pageObj) + + // dirty url cleanup. makes all local urls in html files point to WEB_ROOT + const tags = ['href', 'src'] + tags.map(tag => { + const urlsTangle = rendered.split(`${tag}="`) + + const urls = urlsTangle + .map(url => { + url = url.split('"')[0] + if (url.startsWith('/') && !url.startsWith(root)) { + return [url, replaceSlashSlash(`${root}/${url}`)] + } + }) + .filter(a => a) + + urls.forEach(([oldUrl, newUrl]) => { + rendered = rendered.replace(new RegExp(`${tag}="${oldUrl}"`, 'gim'), `${tag}="${newUrl}"`) + }) + }) return { ...page, rendered, } } catch (e) { - log.error(e, `Page url: ${page.name.replace(config.WEB_ROOT, '/')}`) + log.error('E_TRANSPILE_HTML', `Page url: ${page.name.replace(root, '/')}`, e) process.exit(1) } }) diff --git a/src/tasks/transpile/index.mjs b/src/tasks/transpile/index.mjs index 467e3c69..3cd8b2c6 100644 --- a/src/tasks/transpile/index.mjs +++ b/src/tasks/transpile/index.mjs @@ -1,49 +1,133 @@ import path from 'path' +import log from '@magic/log' +import deep from '@magic/deep' +import is from '@magic/types' + import html from './html.mjs' import js from './js.mjs' import style from './css.mjs' -import { createFileHash } from '../../lib/index.mjs' +import { addEmbeddedCss, createHash, checkLinks, addJsFiles } from '../../lib/index.mjs' export const transpile = async (app, config) => { - const { bundle, serviceWorker } = await js(app) - const css = await style(app.style) - - const hashes = { - css: createFileHash(config.ENV === 'production' ? css.minified : css.css), - js: createFileHash(bundle.code), - pages: {}, - static: {}, - // serviceWorker: createFileHash(serviceWorker.code), - } + try { + const { + PREPEND_CSS, + APPEND_CSS, + PREPEND_SCRIPTS, + APPEND_SCRIPTS, + ENV, + HASH_FILE_NAME, + INCLUDED_HASH_EXTENSIONS, + NO_CHECK_LINKS, + NO_CHECK_LINKS_EXIT, + NO_CHECK_LINKS_LIST, + WEB_ROOT: root, + NO_CHECK_LINKS_REMOTE: noRemote, + } = config + + const { code, serviceWorker } = await js({ app, config }) + app.client = code + app.sw = serviceWorker + + const css = await style(app.style, config.THEME_VARS) + + if (!is.empty(PREPEND_CSS)) { + const prependStyles = PREPEND_CSS.map(addEmbeddedCss).join('\n') + css.minified = `${prependStyles}\n${css.minified}` + css.css = `${prependStyles}\n${css.css}` + } + + if (!is.empty(APPEND_CSS)) { + const appendStyles = APPEND_CSS.map(addEmbeddedCss).join('\n') + css.minified = `${css.minified}\n${appendStyles}` + css.css = `${css.css}\n${appendStyles}` + } + + app.css = css + + app.hashes = { + '/magic.css': createHash(ENV === 'production' ? css.minified : css.css), + '/magic.js': createHash(code), + // 'worker.js': createHash(serviceWorker), + } + + if (PREPEND_SCRIPTS) { + const prependScripts = addJsFiles({ js: PREPEND_SCRIPTS, WEB_ROOT: config.WEB_ROOT }) + if (!is.empty(prependScripts)) { + app.hashes = deep.merge(app.hashes, prependScripts) + } + } + + if (APPEND_SCRIPTS) { + const appendScripts = addJsFiles({ js: APPEND_SCRIPTS, WEB_ROOT: config.WEB_ROOT }) + if (!is.empty(appendScripts)) { + app.hashes = deep.merge(app.hashes, appendScripts) + } + } + + const pages = html({ app, root: config.WEB_ROOT }) + + pages + .sort((a, b) => (a.name < b.name ? -1 : 1)) + .forEach(page => { + page.hash = createHash(page.rendered) + app.hashes[page.name] = page.hash + }) + + app.pages = pages + + // const ignored = config.IGNORED_STATIC + + const included = INCLUDED_HASH_EXTENSIONS + + if (included.length) { + Object.entries(app.static) + .filter(([name]) => included.includes(path.extname(name)) || included.includes(name)) + .sort(([a], [b]) => (a > b ? 1 : -1)) + .forEach(([name, val]) => { + app.hashes[name] = createHash(val) + }) + } + + app.static[`/${HASH_FILE_NAME}`] = JSON.stringify(app.hashes, null, 2) + + const staticUrls = Object.keys(app.static) + const links = Array.from(new Set(app.links)) - const pages = html(app, hashes) + if (!NO_CHECK_LINKS) { + const ignoredLinks = NO_CHECK_LINKS_LIST || [] - pages - .sort((a, b) => (a.name < b.name ? -1 : 1)) - .forEach(page => { - page.hash = createFileHash(page.rendered) - hashes.pages[page.name] = page.hash - }) + if (ENV === 'production') { + log('Checking page links') - const ignored = config.IGNORED_STATIC + const unresolvedLinks = await checkLinks({ + staticUrls, + links, + pages, + noRemote, + root, + ignoredLinks, + dev: config.IS_DEV, + }) - Object.entries(app.static) - .filter(([name, val]) => !ignored.includes(path.extname(name)) && !ignored.includes(name)) - .sort(([a], [b]) => (a > b ? 1 : -1)) - .forEach(([name, val]) => { - hashes.static[name] = createFileHash(val) - }) + if (unresolvedLinks.length) { + log.error('E_BROKEN_LINKS', 'Broken Links found.') + log('to ignore run "npm run build -- --no-check-links" or magic build --no-check-links') - app.static[`/${config.HASH_FILE_NAME}`] = JSON.stringify(hashes, null, 2) + if (!NO_CHECK_LINKS_EXIT) { + process.exit(0) + } + } + } else { + checkLinks({ staticUrls, links, pages, noRemote, ignoredLinks, root, dev: config.IS_DEV }) + } + } - return { - hashes, - pages, - bundle, - css, - serviceWorker, + return app + } catch (e) { + log.error('E_TRANSPILE', e) } } diff --git a/src/tasks/transpile/js.mjs b/src/tasks/transpile/js.mjs index 165d0a77..b53300ea 100644 --- a/src/tasks/transpile/js.mjs +++ b/src/tasks/transpile/js.mjs @@ -1,16 +1,17 @@ -import babel from '@babel/core' +import log from '@magic/log' -import { getBabelConf } from '../../lib/index.mjs' +import { transformClient } from '../../lib/index.mjs' -export default async app => { - const babelOpts = getBabelConf(app, config) +export default async ({ app, config }) => { + try { + const code = await transformClient(app.client, app, config) - const bundle = await babel.transform(app.client, babelOpts) - - // const serviceWorker = await babel.transform(app.serviceWorker, babelOpts) - - return { - bundle, - // serviceWorker, + // const sw = await transformClient(app.serviceWorker, app, config) + return { + code, + // serviceWorker: sw.code, + } + } catch (e) { + log.error('E_SWC', e) } } diff --git a/src/tasks/watch/index.mjs b/src/tasks/watch/index.mjs index 1604d606..e3bee644 100644 --- a/src/tasks/watch/index.mjs +++ b/src/tasks/watch/index.mjs @@ -1,16 +1,38 @@ import path from 'path' +import is from '@magic/types' import fs from '@magic/fs' -export const watch = async dir => { - const dirs = await fs.getDirectories(dir) +const onFileChange = + dir => + (evt, file = '') => { + if (!is.string(file)) { + file = '' + } - dirs.map(dir => { - fs.watch(dir, (evt, file) => { - const filePath = path.join(dir, file) - process.send({ evt, file: filePath }) - }) - }) + const filePath = path.join(dir, file) + if (!is.str(evt)) { + evt = 'change' + } + + process.send({ evt, file: filePath }) + } + +const watchListener = async src => { + const stat = await fs.stat(src) + + if (stat.isDirectory(src)) { + fs.watch(src, onFileChange(src)) + + const subDirs = await fs.getDirectories(src, { depth: 1 }) + const promises = subDirs.filter(dir => dir !== src).map(watchListener) + + await Promise.all(promises) + } else if (stat.isFile(src)) { + fs.watchFile(src, onFileChange(src)) + } } +export const watch = async files => await Promise.all(files.map(watchListener)) + export default watch diff --git a/src/tasks/write/index.mjs b/src/tasks/write/index.mjs index fa6094f8..e1248130 100644 --- a/src/tasks/write/index.mjs +++ b/src/tasks/write/index.mjs @@ -1,73 +1,46 @@ import path from 'path' +import log from '@magic/log' import fs from '@magic/fs' -import { - // compress, - // getFileType, - // minifyImages, - writeFile, - writeServer, -} from '../../lib/index.mjs' +import { writePages } from './pages.mjs' +import { writeStatic } from './static.mjs' +import { writeServer } from './server.mjs' -export const write = async app => { - const { IS_PROD } = config - // const zippable = config.FILETYPES.ZIPPABLE - // const images = config.FILETYPES.IMAGES +export const write = async (app, config) => { + const { + DIR, + CLIENT_LIB_NAME, + // CLIENT_SERVICE_WORKER_NAME, + // FILETYPES, + IS_PROD, + SPLIT_BUNDLES, + } = config - const { css, client, pages, static: stat } = app - await fs.mkdirp(config.DIR.PUBLIC) + // const zippable = FILETYPES.ZIPPABLE + // const images = FILETYPES.IMAGES - // write static first to make sure all other files below get written - // even if there is a name clash - await Promise.all( - Object.entries(stat) - // .filter(([name]) => !images.includes(getFileType(name))) - .map(async file => { - const [name] = file + const { css, client } = app + await fs.mkdirp(DIR.PUBLIC) - const extname = path.extname(name) - // do not write file if extension or filepath is in ignore list. - if (config.IGNORED_STATIC.includes(extname) || config.IGNORED_STATIC.includes(name)) { - return - } + await writeStatic(app, config) - const dir = path.join(config.DIR.PUBLIC, path.dirname(name)) - await fs.mkdirp(dir) - await writeFile(file) - }), - ) + await writePages(app, config) - pages.forEach(async page => { - const oldHash = config.HASHES.pages[page.name] - if (oldHash && oldHash === page.hash) { - return - } + if (SPLIT_BUNDLES) { + log.warn('W_NOT_IMPLENTED', 'should write client bundles for js and css.') + } else { + const jsFile = path.join(DIR.PUBLIC, `${CLIENT_LIB_NAME}.js`) + await fs.writeFile(jsFile, client) - const pagePath = page.path.replace(config.WEB_ROOT, `${config.DIR.PUBLIC}/`) + const cssFile = path.join(DIR.PUBLIC, `${CLIENT_LIB_NAME}.css`) + const usedCss = IS_PROD ? css.minified : css.css + await fs.writeFile(cssFile, usedCss) + } - const dir = path.dirname(pagePath) - await fs.mkdirp(dir) - - await fs.writeFile(pagePath, page.rendered) - }) - - const jsFile = path.join(config.DIR.PUBLIC, `${config.CLIENT_LIB_NAME}.js`) - await fs.writeFile(jsFile, client) - - const cssFile = path.join(config.DIR.PUBLIC, `${config.CLIENT_LIB_NAME}.css`) - const usedCss = IS_PROD ? css.minified : css.css - await fs.writeFile(cssFile, usedCss) - - // const serviceWorkerFile = path.join(config.DIR.PUBLIC, `${config.CLIENT_SERVICE_WORKER_NAME}.js`) + // const serviceWorkerFile = path.join(DIR.PUBLIC, `${CLIENT_SERVICE_WORKER_NAME}.js`) // await fs.writeFile(serviceWorkerFile, app.sw) - // if (IS_PROD) { - // const comp = await compress() - // await comp(zippable, images) - // await minifyImages(images) - // } - await writeServer(app) } diff --git a/src/tasks/write/pages.mjs b/src/tasks/write/pages.mjs new file mode 100644 index 00000000..c3367abc --- /dev/null +++ b/src/tasks/write/pages.mjs @@ -0,0 +1,26 @@ +import path from 'path' + +import fs from '@magic/fs' + +export const writePages = async (app, config) => { + const { DIR, HASHES, WEB_ROOT } = config + + const { pages } = app + await fs.mkdirp(DIR.PUBLIC) + + const pagePromises = pages.map(async page => { + const oldHash = HASHES[page.name] + if (oldHash && oldHash === page.hash) { + return + } + + const pagePath = page.path.replace(WEB_ROOT, `${DIR.PUBLIC}/`) + + const dir = path.dirname(pagePath) + await fs.mkdirp(dir) + + await fs.writeFile(pagePath, page.rendered) + }) + + await Promise.all(pagePromises) +} diff --git a/src/tasks/write/server.mjs b/src/tasks/write/server.mjs new file mode 100644 index 00000000..fd88195d --- /dev/null +++ b/src/tasks/write/server.mjs @@ -0,0 +1,25 @@ +import path from 'path' + +import fs from '@magic/fs' + +import { stringifyObject } from '../../lib/stringifyObject.mjs' + +export const writeLambda = async ([name, content]) => { + await fs.writeFile(name + '.mjs', content) +} + +export const writeServer = async app => { + const lambdas = Object.entries(app.lambdas) + + if (lambdas.length) { + const apiDir = config.DIR.API || path.join(process.cwd(), 'api') + await fs.mkdirp(apiDir) + + const wrappedLambdas = lambdas.map(([name, fn]) => [ + path.join(apiDir, name), + `export default ${stringifyObject(fn, false)}`, + ]) + + await Promise.all(wrappedLambdas.map(writeLambda)) + } +} diff --git a/src/tasks/write/static.mjs b/src/tasks/write/static.mjs new file mode 100644 index 00000000..d6f8fca9 --- /dev/null +++ b/src/tasks/write/static.mjs @@ -0,0 +1,44 @@ +import path from 'path' + +import fs from '@magic/fs' + +import { writeFile } from '../../lib/index.mjs' + +const handleImage = file => { + // check if file is image + // if is image, check if alternative formats (webp, avif) exist in docs + // if not in docs, check if they are in src/assets/static + // if in src/assets/static, just copy + // if not in src/assets/static + // convert using sharp. + // console.log({ file }) +} + +export const writeStatic = async (app, config) => { + const { DIR, IGNORED_STATIC } = config + + const { static: stat } = app + await fs.mkdirp(DIR.PUBLIC) + + // write static first to make sure all other files below get written + // even if there is a name clash + await Promise.all( + Object.entries(stat) + // .filter(([name]) => !images.includes(getFileType(name))) + .map(async file => { + const [name] = file + + handleImage(file) + + const extname = path.extname(name) + // do not write file if extension or filepath is in ignore list. + if (IGNORED_STATIC.includes(extname) || IGNORED_STATIC.includes(name)) { + return + } + + const dir = path.join(DIR.PUBLIC, path.dirname(name)) + await fs.mkdirp(dir) + await writeFile(file, config) + }), + ) +} diff --git a/src/themes/colors.mjs b/src/themes/colors.mjs index 6e0e1fbe..36d9687d 100644 --- a/src/themes/colors.mjs +++ b/src/themes/colors.mjs @@ -1,295 +1,295 @@ export const colors = { red: { - '50': '#FFEBEE', - '100': '#FFCDD2', - '200': '#EF9A9A', - '300': '#E57373', - '400': '#EF5350', - '500': '#F44336', - '600': '#E53935', - '700': '#D32F2F', - '800': '#C62828', - '900': '#B71C1C', + 50: '#FFEBEE', + 100: '#FFCDD2', + 200: '#EF9A9A', + 300: '#E57373', + 400: '#EF5350', + 500: '#F44336', + 600: '#E53935', + 700: '#D32F2F', + 800: '#C62828', + 900: '#B71C1C', A100: '#FF8A80', A200: '#FF5252', A400: '#FF1744', A700: '#D50000', }, pink: { - '50': '#FCE4EC', - '100': '#F8BBD0', - '200': '#F48FB1', - '300': '#F06292', - '400': '#EC407A', - '500': '#E91E63', - '600': '#D81B60', - '700': '#C2185B', - '800': '#AD1457', - '900': '#880E4F', + 50: '#FCE4EC', + 100: '#F8BBD0', + 200: '#F48FB1', + 300: '#F06292', + 400: '#EC407A', + 500: '#E91E63', + 600: '#D81B60', + 700: '#C2185B', + 800: '#AD1457', + 900: '#880E4F', A100: '#FF80AB', A200: '#FF4081', A400: '#F50057', A700: '#C51162', }, purple: { - '50': '#F3E5F5', - '100': '#E1BEE7', - '200': '#CE93D8', - '300': '#BA68C8', - '400': '#AB47BC', - '500': '#9C27B0', - '600': '#8E24AA', - '700': '#7B1FA2', - '800': '#6A1B9A', - '900': '#4A148C', + 50: '#F3E5F5', + 100: '#E1BEE7', + 200: '#CE93D8', + 300: '#BA68C8', + 400: '#AB47BC', + 500: '#9C27B0', + 600: '#8E24AA', + 700: '#7B1FA2', + 800: '#6A1B9A', + 900: '#4A148C', A100: '#EA80FC', A200: '#E040FB', A400: '#D500F9', A700: '#AA00FF', }, deeppurple: { - '50': '#EDE7F6', - '100': '#D1C4E9', - '200': '#B39DDB', - '300': '#9575CD', - '400': '#7E57C2', - '500': '#673AB7', - '600': '#5E35B1', - '700': '#512DA8', - '800': '#4527A0', - '900': '#311B92', + 50: '#EDE7F6', + 100: '#D1C4E9', + 200: '#B39DDB', + 300: '#9575CD', + 400: '#7E57C2', + 500: '#673AB7', + 600: '#5E35B1', + 700: '#512DA8', + 800: '#4527A0', + 900: '#311B92', A100: '#B388FF', A200: '#7C4DFF', A400: '#651FFF', A700: '#6200EA', }, indigo: { - '50': '#E8EAF6', - '100': '#C5CAE9', - '200': '#9FA8DA', - '300': '#7986CB', - '400': '#5C6BC0', - '500': '#3F51B5', - '600': '#3949AB', - '700': '#303F9F', - '800': '#283593', - '900': '#1A237E', + 50: '#E8EAF6', + 100: '#C5CAE9', + 200: '#9FA8DA', + 300: '#7986CB', + 400: '#5C6BC0', + 500: '#3F51B5', + 600: '#3949AB', + 700: '#303F9F', + 800: '#283593', + 900: '#1A237E', A100: '#8C9EFF', A200: '#536DFE', A400: '#3D5AFE', A700: '#304FFE', }, blue: { - '50': '#E3F2FD', - '100': '#BBDEFB', - '200': '#90CAF9', - '300': '#64B5F6', - '400': '#42A5F5', - '500': '#2196F3', - '600': '#1E88E5', - '700': '#1976D2', - '800': '#1565C0', - '900': '#0D47A1', + 50: '#E3F2FD', + 100: '#BBDEFB', + 200: '#90CAF9', + 300: '#64B5F6', + 400: '#42A5F5', + 500: '#2196F3', + 600: '#1E88E5', + 700: '#1976D2', + 800: '#1565C0', + 900: '#0D47A1', A100: '#82B1FF', A200: '#448AFF', A400: '#2979FF', A700: '#2962FF', }, lightblue: { - '50': '#E1F5FE', - '100': '#B3E5FC', - '200': '#81D4FA', - '300': '#4FC3F7', - '400': '#29B6F6', - '500': '#03A9F4', - '600': '#039BE5', - '700': '#0288D1', - '800': '#0277BD', - '900': '#01579B', + 50: '#E1F5FE', + 100: '#B3E5FC', + 200: '#81D4FA', + 300: '#4FC3F7', + 400: '#29B6F6', + 500: '#03A9F4', + 600: '#039BE5', + 700: '#0288D1', + 800: '#0277BD', + 900: '#01579B', A100: '#80D8FF', A200: '#40C4FF', A400: '#00B0FF', A700: '#0091EA', }, cyan: { - '50': '#E0F7FA', - '100': '#B2EBF2', - '200': '#80DEEA', - '300': '#4DD0E1', - '400': '#26C6DA', - '500': '#00BCD4', - '600': '#00ACC1', - '700': '#0097A7', - '800': '#00838F', - '900': '#006064', + 50: '#E0F7FA', + 100: '#B2EBF2', + 200: '#80DEEA', + 300: '#4DD0E1', + 400: '#26C6DA', + 500: '#00BCD4', + 600: '#00ACC1', + 700: '#0097A7', + 800: '#00838F', + 900: '#006064', A100: '#84FFFF', A200: '#18FFFF', A400: '#00E5FF', A700: '#00B8D4', }, teal: { - '50': '#E0F2F1', - '100': '#B2DFDB', - '200': '#80CBC4', - '300': '#4DB6AC', - '400': '#26A69A', - '500': '#009688', - '600': '#00897B', - '700': '#00796B', - '800': '#00695C', - '900': '#004D40', + 50: '#E0F2F1', + 100: '#B2DFDB', + 200: '#80CBC4', + 300: '#4DB6AC', + 400: '#26A69A', + 500: '#009688', + 600: '#00897B', + 700: '#00796B', + 800: '#00695C', + 900: '#004D40', A100: '#A7FFEB', A200: '#64FFDA', A400: '#1DE9B6', A700: '#00BFA5', }, green: { - '50': '#E8F5E9', - '100': '#C8E6C9', - '200': '#A5D6A7', - '300': '#81C784', - '400': '#66BB6A', - '500': '#4CAF50', - '600': '#43A047', - '700': '#388E3C', - '800': '#2E7D32', - '900': '#1B5E20', + 50: '#E8F5E9', + 100: '#C8E6C9', + 200: '#A5D6A7', + 300: '#81C784', + 400: '#66BB6A', + 500: '#4CAF50', + 600: '#43A047', + 700: '#388E3C', + 800: '#2E7D32', + 900: '#1B5E20', A100: '#B9F6CA', A200: '#69F0AE', A400: '#00E676', A700: '#00C853', }, lightgreen: { - '50': '#F1F8E9', - '100': '#DCEDC8', - '200': '#C5E1A5', - '300': '#AED581', - '400': '#9CCC65', - '500': '#8BC34A', - '600': '#7CB342', - '700': '#689F38', - '800': '#558B2F', - '900': '#33691E', + 50: '#F1F8E9', + 100: '#DCEDC8', + 200: '#C5E1A5', + 300: '#AED581', + 400: '#9CCC65', + 500: '#8BC34A', + 600: '#7CB342', + 700: '#689F38', + 800: '#558B2F', + 900: '#33691E', A100: '#CCFF90', A200: '#B2FF59', A400: '#76FF03', A700: '#64DD17', }, lime: { - '50': '#F9FBE7', - '100': '#F0F4C3', - '200': '#E6EE9C', - '300': '#DCE775', - '400': '#D4E157', - '500': '#CDDC39', - '600': '#C0CA33', - '700': '#AFB42B', - '800': '#9E9D24', - '900': '#827717', + 50: '#F9FBE7', + 100: '#F0F4C3', + 200: '#E6EE9C', + 300: '#DCE775', + 400: '#D4E157', + 500: '#CDDC39', + 600: '#C0CA33', + 700: '#AFB42B', + 800: '#9E9D24', + 900: '#827717', A100: '#F4FF81', A200: '#EEFF41', A400: '#C6FF00', A700: '#AEEA00', }, yellow: { - '50': '#FFFDE7', - '100': '#FFF9C4', - '200': '#FFF59D', - '300': '#FFF176', - '400': '#FFEE58', - '500': '#FFEB3B', - '600': '#FDD835', - '700': '#FBC02D', - '800': '#F9A825', - '900': '#F57F17', + 50: '#FFFDE7', + 100: '#FFF9C4', + 200: '#FFF59D', + 300: '#FFF176', + 400: '#FFEE58', + 500: '#FFEB3B', + 600: '#FDD835', + 700: '#FBC02D', + 800: '#F9A825', + 900: '#F57F17', A100: '#FFFF8D', A200: '#FFFF00', A400: '#FFEA00', A700: '#FFD600', }, amber: { - '50': '#FFF8E1', - '100': '#FFECB3', - '200': '#FFE082', - '300': '#FFD54F', - '400': '#FFCA28', - '500': '#FFC107', - '600': '#FFB300:', - '700': '#FFA000', - '800': '#FF8F00', - '900': '#FF6F00', + 50: '#FFF8E1', + 100: '#FFECB3', + 200: '#FFE082', + 300: '#FFD54F', + 400: '#FFCA28', + 500: '#FFC107', + 600: '#FFB300', + 700: '#FFA000', + 800: '#FF8F00', + 900: '#FF6F00', A100: '#FFE57F', A200: '#FFD740', - A400: '#FFC400:', + A400: '#FFC400', A700: '#FFAB00', }, orange: { - '50': '#FFF3E0', - '100': '#FFE0B2', - '200': '#FFCC80', - '300': '#FFB74D', - '400': '#FFA726', - '500': '#FF9800', - '600': '#FB8C00', - '700': '#F57C00', - '800': '#EF6C00', - '900': '#E65100:', + 50: '#FFF3E0', + 100: '#FFE0B2', + 200: '#FFCC80', + 300: '#FFB74D', + 400: '#FFA726', + 500: '#FF9800', + 600: '#FB8C00', + 700: '#F57C00', + 800: '#EF6C00', + 900: '#E65100', A100: '#FFD180', A200: '#FFAB40', - A400: '#FF9100:', + A400: '#FF9100', A700: '#FF6D00', }, deeporange: { - '50': '#FBE9E7', - '100': '#FFCCBC', - '200': '#FFAB91', - '300': '#FF8A65', - '400': '#FF7043', - '500': '#FF5722', - '600': '#F4511E', - '700': '#E64A19', - '800': '#D84315', - '900': '#BF360C', + 50: '#FBE9E7', + 100: '#FFCCBC', + 200: '#FFAB91', + 300: '#FF8A65', + 400: '#FF7043', + 500: '#FF5722', + 600: '#F4511E', + 700: '#E64A19', + 800: '#D84315', + 900: '#BF360C', A100: '#FF9E80', A200: '#FF6E40', A400: '#FF3D00', A700: '#DD2C00', }, brown: { - '50': '#EFEBE9', - '100': '#D7CCC8', - '200': '#BCAAA4', - '300': '#A1887F', - '400': '#8D6E63', - '500': '#795548', - '600': '#6D4C41', - '700': '#5D4037', - '800': '#4E342E', - '900': '#3E2723', + 50: '#EFEBE9', + 100: '#D7CCC8', + 200: '#BCAAA4', + 300: '#A1887F', + 400: '#8D6E63', + 500: '#795548', + 600: '#6D4C41', + 700: '#5D4037', + 800: '#4E342E', + 900: '#3E2723', }, gray: { - '50': '#FAFAFA', - '100': '#F5F5F5', - '200': '#EEEEEE', - '300': '#E0E0E0', - '400': '#BDBDBD', - '500': '#9E9E9E', - '600': '#757575', - '700': '#616161', - '800': '#424242', - '900': '#212121', + 50: '#FAFAFA', + 100: '#F5F5F5', + 200: '#EEEEEE', + 300: '#E0E0E0', + 400: '#BDBDBD', + 500: '#9E9E9E', + 600: '#757575', + 700: '#616161', + 800: '#424242', + 900: '#212121', }, bluegray: { - '50': '#ECEFF1', - '100': '#CFD8DC', - '200': '#B0BEC5', - '300': '#90A4AE', - '400': '#78909C', - '500': '#607D8B', - '600': '#546E7A', - '700': '#455A64', - '800': '#37474F', - '900': '#263238', + 50: '#ECEFF1', + 100: '#CFD8DC', + 200: '#B0BEC5', + 300: '#90A4AE', + 400: '#78909C', + 500: '#607D8B', + 600: '#546E7A', + 700: '#455A64', + 800: '#37474F', + 900: '#263238', }, black: '#000000', white: '#FFFFFF', diff --git a/src/themes/reset.css.mjs b/src/themes/reset.css.mjs index 0c648c84..a748f9b9 100644 --- a/src/themes/reset.css.mjs +++ b/src/themes/reset.css.mjs @@ -27,12 +27,6 @@ input[type="file"] > input[type="button"]` // all other styles will be added as keys onto this map, // which means that order should be preserved export const reset = (vars = {}) => ({ - html: { - scrollBehavior: 'smooth', - // always show scrollbar to prevent layout glitch if page resizes - overflowY: 'scroll', - }, - [all]: { border: 0, boxSizing: 'border-box', @@ -52,7 +46,10 @@ export const reset = (vars = {}) => ({ margin: 0, maxWidth: '100vw', overflowX: 'hidden', + // always show scrollbar to prevent layout glitch if page resizes + overflowY: 'scroll', padding: 0, + scrollBehavior: 'smooth', }, ul: { display: 'inline-block', @@ -82,6 +79,14 @@ export const reset = (vars = {}) => ({ whiteSpace: 'nowrap', }, + 'b, strong': { + fontWeight: 700, + }, + + 'i, em': { + fontStyle: 'italic', + }, + [buttons]: { backgroundColor: vars.buttonBackgroundColor || vars.colors.gray[500], color: vars.buttonColor || vars.colors.gray[900], @@ -103,6 +108,13 @@ export const reset = (vars = {}) => ({ height: '100%', minHeight: '100vh', width: '100%', + + '.icon': { + width: '1.5em', + height: 'auto', + animation: 'showHoisted 500ms 0s forwards', + opacity: 0, + }, }, '.Wrapper': { @@ -156,4 +168,13 @@ export const reset = (vars = {}) => ({ h3: { fontSize: '1.1em', }, + + '@keyframes showHoisted': { + '0%': { + opacity: 0, + }, + '100%': { + opacity: 1, + }, + }, }) diff --git a/test/beforeAll.mjs b/test/beforeAll.js similarity index 75% rename from test/beforeAll.mjs rename to test/beforeAll.js index 7eae1d24..ce46440f 100644 --- a/test/beforeAll.mjs +++ b/test/beforeAll.js @@ -7,6 +7,5 @@ export default async () => { const App = await runApp(config) - const app = await tasks.prepare(App, config) - // console.log(config, app) + await tasks.prepare(App, config) } diff --git a/test/lib/CHECK_PROPS.mjs b/test/lib/CHECK_PROPS.mjs new file mode 100644 index 00000000..ea68214c --- /dev/null +++ b/test/lib/CHECK_PROPS.mjs @@ -0,0 +1,303 @@ +import { is, tryCatch } from '@magic/test' + +import { CHECK_PROPS } from '../../src/lib/CHECK_PROPS.mjs' + +const tests = [ + { + props: { str: 'teststring' }, + types: { + TestRequiredMissing: [ + { key: 'str', type: 'string' }, + { key: 'str2', type: 'string', required: true }, + ], + }, + name: 'TestRequiredMissing', + expect: false, + info: 'can test required value if missing', + }, + { + props: { str: 'teststring', str2: 'str2 exists' }, + types: { + TestRequiredExists: [ + { key: 'str', type: 'string' }, + { key: 'str2', type: 'string', required: true }, + ], + }, + name: 'TestRequiredExists', + expect: true, + info: 'can test required value if exists', + }, + { + props: { obj: {} }, + types: { TestRequiredEmptyObj: [{ key: 'obj', type: 'object', required: true }] }, + name: 'TestRequiredEmptyObj', + expect: false, + info: 'required empty object returns false', + }, + { + props: { obj: {} }, + types: { TestRequiredNonEmptyObj: [{ key: 'obj', type: 'object', required: true }] }, + name: 'TestRequiredNonEmptyObj', + expect: false, + info: 'required empty object returns false', + }, + { + props: { arr: [] }, + types: { TestRequiredEmptyArray: [{ key: 'arr', type: 'array', required: true }] }, + name: 'TestRequiredEmptyArray', + expect: false, + info: 'required empty array returns false', + }, + { + props: { arr: [1, 2, 3] }, + types: { TestRequiredNonEmptyArray: [{ key: 'arr', type: 'array', required: true }] }, + name: 'TestRequiredNonEmptyArray', + expect: true, + info: 'required non-empty array returns true.', + }, + + { + props: { str: 'teststring' }, + types: { TestStringTrue: [{ key: 'str', type: 'string' }] }, + name: 'TestStringTrue', + expect: true, + info: 'can test string type if true', + }, + { + props: { str: 122 }, + types: { TestStringFalse: [{ key: 'str', type: 'string' }] }, + name: 'TestStringFalse', + expect: false, + info: 'can test string type if false', + }, + { + props: { num: 123 }, + types: { TestNumTrue: [{ key: 'num', type: 'number' }] }, + name: 'TestNumTrue', + expect: true, + info: 'can test number type if true', + }, + { + props: { num: 'not a number.' }, + types: { TestNumFalse: [{ key: 'num', type: 'number' }] }, + name: 'TestNumFalse', + expect: false, + info: 'can test number type if false', + }, + { + props: { num: 123 }, + types: { TestIntTrue: [{ key: 'num', type: 'integer' }] }, + name: 'TestIntTrue', + expect: true, + info: 'can test int type if true', + }, + { + props: { num: 'not an int' }, + types: { TestIntFalse: [{ key: 'num', type: 'integer' }] }, + name: 'TestIntFalse', + expect: false, + info: 'can test int type if false', + }, + { + props: { num: 123 }, + types: { TestIntMax: [{ key: 'num', type: 'integer', max: 122 }] }, + name: 'TestIntMax', + expect: false, + info: 'can test if int is bigger than max', + }, + { + props: { num: 123 }, + types: { TestIntMin: [{ key: 'num', type: 'integer', min: 124 }] }, + name: 'TestIntMin', + expect: false, + info: 'can test if int is smaller than min', + }, + + { + props: { str: 'abcdefg' }, + types: { TestStringTooLong: [{ key: 'str', type: 'string', max: 3 }] }, + name: 'TestStringTooLong', + expect: false, + info: 'can test if string is longer than max', + }, + { + props: { str: 'abcdefg' }, + types: { TestStringTooShort: [{ key: 'str', type: 'string', min: 124 }] }, + name: 'TestStringTooShort', + expect: false, + info: 'can test if string is shorter than min', + }, + { + props: { str: 'teststring' }, + types: { TestOneOfString: [{ key: 'str', type: 'string', oneOf: ['teststring'] }] }, + name: 'TestOneOfString', + expect: true, + info: 'oneOf works if true', + }, + { + props: { str: 'teststring' }, + types: { Test: [{ key: 'str', oneOf: ['teststring2'] }] }, + name: 'Test', + expect: false, + info: 'oneOf works if false', + }, + { + props: { arr: [1, 2, 3, 'testing'] }, + types: { + TestArrayItemTypesTrue: [ + { + key: 'arr', + type: 'array', + item: { + type: ['string', 'number'], + }, + }, + ], + }, + name: 'TestArrayItemTypesTrue', + expect: true, + info: 'array item types work for strings and numbers in one array if true', + }, + { + props: { arr: [1, 2, 3, 'testing', false] }, + types: { + TestArrayItemTypesFalse: [ + { + key: 'arr', + type: 'array', + item: { + type: ['string', 'number'], + }, + }, + ], + }, + name: 'TestArrayItemTypesFalse', + expect: false, + info: 'array item types work for strings and numbers in one array if false', + }, + { + props: { arr: ['testing', { str: 'string' }] }, + types: { + TestArrayItemObjectTrue: [ + { + key: 'arr', + type: 'array', + item: { + type: ['string', 'object'], + keys: [{ type: 'string', key: 'str' }], + }, + }, + ], + }, + name: 'TestArrayItemObjectTrue', + expect: true, + info: 'array item types work for strings and objects in one array if true', + }, + { + props: { arr: ['testing', { str: 123 }] }, + types: { + TestArrayItemObjectTrue: [ + { + key: 'arr', + type: 'array', + item: { + type: ['string', 'object'], + keys: [{ type: 'string', key: 'str' }], + }, + }, + ], + }, + name: 'TestArrayItemObjectMismatch', + expect: false, + info: 'array item types can check objcets for wrong values if false', + }, + { + props: { arr: ['testing', 1, 2, 3] }, + types: { + TestArrayItemObjectFalse: [ + { + key: 'arr', + type: 'array', + item: { + type: ['string', 'object'], + keys: [{ key: 'str', type: 'string' }], + }, + }, + ], + }, + name: 'TestArrayItemObjectFalse', + expect: false, + info: 'array item types work for strings and objects in one array if false', + }, + + { + props: { str: 'str' }, + types: { + ShouldNotLog: [{ key: 'str', type: 'string' }], + }, + name: 'ShouldNotLog', + expect: true, + info: 'array item types work for strings and objects in one array if false', + }, +] + +const errored = {} + +const expectedErrors = { + TestRequiredMissing: 1, + TestRequiredEmptyObj: 1, + TestRequiredNonEmptyObj: 1, + TestRequiredEmptyArray: 1, + TestStringFalse: 1, + TestNumFalse: 1, + TestIntFalse: 1, + TestIntMax: 1, + TestIntMin: 1, + TestStringTooLong: 1, + TestStringTooShort: 1, + Test: 2, + TestArrayItemTypesFalse: 1, + expected: 1, + TestArrayItemObjectFalse: 3, +} + +const log = { + error: err => { + if (!err.message) { + console.log('E_NO_MESSAGE', 'error without a message received!', { err }, typeof err) + } + + const name = err.message.split(' ')[0] + if (!errored.hasOwnProperty(name)) { + errored[name] = 1 + } else { + errored[name] += 1 + } + }, +} + +export default { + tests: [ + { + fn: tryCatch(CHECK_PROPS, undefined, undefined, undefined, false), + expect: is.err, + info: 'without arguments errors', + }, + // { fn: tryCatch(CHECK_PROPS, { key: false }, { key: 'boolean' }), expect: t => t[0].message.includes('expected Module name as third argument'), info: 'CHECK_PROPS without arguments errors with third arg error.' }, + { + fn: tryCatch(CHECK_PROPS, undefined, undefined, undefined, false), + expect: false, + info: 'CHECK_PROPS without second argument errors and returns false.', + }, + ...tests.map(t => ({ + fn: CHECK_PROPS(t.props, t.types, t.name, log), + expect: t.expect, + info: t.info, + })), + ], + afterAll: () => { + if (!is.deep.equal(errored, expectedErrors)) { + console.error('log error messages did not match.', { errored, expectedErrors }) + } + }, +} diff --git a/test/lib/applyWebRoot.mjs b/test/lib/applyWebRoot.mjs deleted file mode 100644 index 439e84cc..00000000 --- a/test/lib/applyWebRoot.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import { applyWebRoot } from '../../src/lib/applyWebRoot.mjs' - -const config = { - WEB_ROOT: 'WEB_ROOT', - ENV: 'production', -} - -const configWithSlash = { - WEB_ROOT: '/WEB_ROOT/', - ENV: 'production', -} - -export default [ - { - fn: applyWebRoot(config, 'href="/testing'), - expect: 'href="/WEB_ROOT/testing', - info: 'adds WEB_ROOT to t.startsWith('sha384-') }, - { fn: createFileHash('testing-string'), expect: createFileHash('testing-string') }, -] diff --git a/test/lib/createHash.mjs b/test/lib/createHash.mjs new file mode 100644 index 00000000..0f170ce8 --- /dev/null +++ b/test/lib/createHash.mjs @@ -0,0 +1,9 @@ +import { is } from '@magic/test' + +import { createHash } from '../../src/lib/index.mjs' + +export default [ + { fn: createHash('testing-string'), expect: is.string }, + { fn: createHash('testing-string'), expect: t => t.startsWith('sha384-') }, + { fn: createHash('testing-string'), expect: createHash('testing-string') }, +] diff --git a/test/lib/isHashedUrl.mjs b/test/lib/isHashedUrl.mjs new file mode 100644 index 00000000..75c54205 --- /dev/null +++ b/test/lib/isHashedUrl.mjs @@ -0,0 +1,19 @@ +import { isHashedUrl } from '../../src/lib/index.mjs' + +export default [ + { + fn: isHashedUrl([], 'testing'), + expect: false, + info: 'normal string is not a hashed url', + }, + { + fn: isHashedUrl([{ name: 'page', rendered: 'id="testing"' }], 'page#testing'), + expect: true, + info: 'valid list and url args work.', + }, + { + fn: isHashedUrl([{ name: 'page', rendered: 'id="testing"' }], 'page2#testing'), + expect: false, + info: 'valid list and non existant page return false.', + }, +] diff --git a/test/lib/isModuleName.mjs b/test/lib/isModuleName.mjs new file mode 100644 index 00000000..32c96ed1 --- /dev/null +++ b/test/lib/isModuleName.mjs @@ -0,0 +1,10 @@ +import { is } from '@magic/test' + +import { isModuleName } from '../../src/lib/index.mjs' + +export default [ + { fn: isModuleName('noModule'), expect: is.boolean, info: '"noModule" returns a boolean' }, + { fn: isModuleName('Module'), expect: is.boolean, info: '"Module" returns a boolean' }, + { fn: isModuleName('noModule'), expect: false, info: 'noModule is not a module name' }, + { fn: isModuleName('Module'), info: 'Module is a module name' }, +] diff --git a/test/lib/isStaticUrl.mjs b/test/lib/isStaticUrl.mjs new file mode 100644 index 00000000..5246705b --- /dev/null +++ b/test/lib/isStaticUrl.mjs @@ -0,0 +1,34 @@ +import { is } from '@magic/test' + +import { isStaticUrl } from '../../src/lib/index.mjs' + +// export const isStaticUrl = (list, link, root) => { +// if (link.startsWith(root)) { +// link = link.replace(root, '/') +// } + +// return list.includes(link) +// } + +const root = '/root/' +const rootedLink = `${root}rooted/` +const matchingLink = `/local/` +const notMatchingLink = `/not-static/` + +const list = ['/rooted/', '/local/'] + +export default [ + { + fn: isStaticUrl(list, rootedLink, root), + info: 'isStaticUrl: rooted links in list return true', + }, + { + fn: isStaticUrl(list, matchingLink, root), + info: 'isStaticUrl: non-rooted links in list return true', + }, + { + fn: isStaticUrl(list, notMatchingLink, root), + expect: false, + info: 'isStaticUrl: links not in list return false', + }, +] diff --git a/test/lib/replacePathSepForImport.mjs b/test/lib/replacePathSepForImport.mjs new file mode 100644 index 00000000..74942df8 --- /dev/null +++ b/test/lib/replacePathSepForImport.mjs @@ -0,0 +1,19 @@ +import { replacePathSepForImport } from '../../src/lib/index.mjs' + +export default [ + { + fn: replacePathSepForImport('testing'), + expect: 'testing', + info: 'normal string is not changed', + }, + { + fn: replacePathSepForImport('\\testing\\', '\\'), + expect: '/testing/', + info: '\\ gets replaced to / on windows', + }, + { + fn: replacePathSepForImport('#testing#', '#'), + expect: '/testing/', + info: 'if # would be a separator, it would get replaced.', + }, +] diff --git a/test/lib/replaceSlashSlash.mjs b/test/lib/replaceSlashSlash.mjs new file mode 100644 index 00000000..09c76cdf --- /dev/null +++ b/test/lib/replaceSlashSlash.mjs @@ -0,0 +1,25 @@ +import { replaceSlashSlash } from '../../src/lib/index.mjs' + +const testString1 = '//testing//test' + +const expectWithSlash = '/testing/test' +const expectWithoutSlash = 'testingtest' +const expectWithSpace = ' testing test' + +export default [ + { + fn: replaceSlashSlash(testString1), + expect: expectWithSlash, + info: 'replaceSlashSlash by default replaces // with /', + }, + { + fn: replaceSlashSlash(testString1, ''), + expect: expectWithoutSlash, + info: 'replaceSlashSlash can replace // with ""', + }, + { + fn: replaceSlashSlash(testString1, ' '), + expect: expectWithSpace, + info: 'replaceSlashSlash can replace // with " "', + }, +] diff --git a/test/lib/swc/moduleViewToString.mjs b/test/lib/swc/moduleViewToString.mjs new file mode 100644 index 00000000..d1f9ab2c --- /dev/null +++ b/test/lib/swc/moduleViewToString.mjs @@ -0,0 +1,40 @@ +import { is } from '@magic/test' +import { moduleViewToString } from '../../../src/lib/swc/moduleViewToString.mjs' + +const pkg = `moduleViewToString` + +const arrowFnModule = a => a +const fnModule = function named() {} + +const objectArrowFunctionModule = { + View: () => {}, +} + +const objectFunctionModule = { + View() {}, +} + +export default [ + { fn: moduleViewToString(arrowFnModule), expect: is.string, info: `${pkg} returns a string` }, + { fn: moduleViewToString(undefined), expect: undefined, info: `${pkg} returns a string` }, + { + fn: moduleViewToString(arrowFnModule), + expect: arrowFnModule.toString(), + info: `${pkg} arrow function returns a correctly stringified function`, + }, + { + fn: moduleViewToString(fnModule), + expect: fnModule.toString(), + info: `${pkg} function returns a correctly stringified function`, + }, + { + fn: moduleViewToString(objectArrowFunctionModule), + expect: objectArrowFunctionModule.View.toString(), + info: `${pkg} object arrow function returns a correctly stringified function`, + }, + { + fn: moduleViewToString(objectFunctionModule), + expect: objectFunctionModule.View.toString(), + info: `${pkg} object function returns a correctly stringified function`, + }, +] diff --git a/test/lib/uniqueMerge.mjs b/test/lib/uniqueMerge.mjs new file mode 100644 index 00000000..b12e059c --- /dev/null +++ b/test/lib/uniqueMerge.mjs @@ -0,0 +1,27 @@ +import { is } from '@magic/test' + +import { uniqueMerge } from '../../src/lib/index.mjs' + +const a = { a: ['a', 'b', 'c', 'e'], b: ['aa', 'bb', 'cc', 'dd'] } +const b = { a: ['b', 'c', 'd'], b: ['cc', 'dd', 'ee', 'ff'], c: ['aa', 'bb', 'dd'] } +const c = { + a: ['a', 'b', 'c', 'd', 'e'], + b: ['aa', 'bb', 'cc', 'dd', 'ee', 'ff'], + c: ['aa', 'bb', 'dd'], +} + +export default [ + { fn: uniqueMerge(a, b), expect: is.objectNative, info: 'uniqueMerge returns an array' }, + { + fn: uniqueMerge(a, undefined), + expect: is.deep.equal(a), + info: 'uniqueMerge returns a if b is falsy', + }, + { + fn: uniqueMerge(undefined, b), + expect: is.deep.equal(b), + info: 'uniqueMerge returns b if a is falsy', + }, + { fn: uniqueMerge(a, b), expect: is.deep.equal(c), info: 'uniqueMerge merges a and b into c' }, + { fn: uniqueMerge(b, a), expect: is.deep.equal(c), info: 'uniqueMerge merges b and a into c' }, +] diff --git a/test/spec.mjs b/test/spec.mjs new file mode 100644 index 00000000..5987687c --- /dev/null +++ b/test/spec.mjs @@ -0,0 +1,66 @@ +import { version } from '@magic/test' + +import * as lib from '../src/index.mjs' + +const rawColorObject = { + 50: 'str', + 100: 'str', + 200: 'str', + 300: 'str', + 400: 'str', + 500: 'str', + 600: 'str', + 700: 'str', + 800: 'str', + 900: 'str', +} + +const colorObject = ['obj', rawColorObject] + +const colorObjectWithA = [ + 'obj', + { + ...rawColorObject, + A100: 'str', + A200: 'str', + A400: 'str', + A700: 'str', + }, +] + +const spec = { + renderToString: 'fn', + reset: 'fn', + replaceSlashSlash: 'fn', + colors: [ + 'object', + { + red: colorObjectWithA, + pink: colorObjectWithA, + purple: colorObjectWithA, + deeppurple: colorObjectWithA, + indigo: colorObjectWithA, + blue: colorObjectWithA, + lightblue: colorObjectWithA, + cyan: colorObjectWithA, + teal: colorObjectWithA, + green: colorObjectWithA, + lightgreen: colorObjectWithA, + lime: colorObjectWithA, + yellow: colorObjectWithA, + amber: colorObjectWithA, + orange: colorObjectWithA, + deeporange: colorObjectWithA, + brown: colorObject, + gray: colorObject, + bluegray: colorObject, + grey: colorObject, + bluegrey: colorObject, + + black: 'str', + white: 'str', + }, + ], +} + +export default version(lib, spec) diff --git a/test/themes/reset.css.mjs b/test/themes/reset.css.mjs index 6af89f94..94a65053 100644 --- a/test/themes/reset.css.mjs +++ b/test/themes/reset.css.mjs @@ -1,4 +1,4 @@ -import { is, css } from '@magic/test' +import { is } from '@magic/test' import { reset } from '../../src/themes/reset.css.mjs'