diff --git a/.gitignore b/.gitignore index ac019bcd..ca6ac6ee 100644 --- a/.gitignore +++ b/.gitignore @@ -10,8 +10,12 @@ build/ out/ secret.gradle -local.properties +local.* +local/ +kotlin-js-store publishing.sh server/src/resources/web/ + +features/common/common/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/Version.kt diff --git a/client/build.gradle b/client/build.gradle index 876879ab..8f44e13f 100644 --- a/client/build.gradle +++ b/client/build.gradle @@ -27,9 +27,9 @@ kotlin { api project(":postssystem.services.posts.client") - api "dev.inmo:micro_utils.fsm.common:$microutils_version" - api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version" - api "dev.inmo:micro_utils.crypto:$microutils_version" + api libs.microutils.fsm.common + api libs.microutils.fsm.repos.common + api libs.microutils.crypto implementation compose.runtime } diff --git a/client/src/jsMain/kotlin/dev/inmo/postssystem/client/fsm/ui/AuthView.kt b/client/src/jsMain/kotlin/dev/inmo/postssystem/client/fsm/ui/AuthView.kt index 1722a678..0f60e976 100644 --- a/client/src/jsMain/kotlin/dev/inmo/postssystem/client/fsm/ui/AuthView.kt +++ b/client/src/jsMain/kotlin/dev/inmo/postssystem/client/fsm/ui/AuthView.kt @@ -43,7 +43,7 @@ class AuthView( UIKitFlex.Alignment.Horizontal.Center ) { Card( - Attrs(UIKitText.Alignment.Center), + Attrs(UIKitText.Alignment.Horizontal.Center), bodyAttrs = Attrs(UIKitWidth.Fixed.Medium), ) { CardTitle { Text("Log in") } diff --git a/client/uikit/.gitignore b/client/uikit/.gitignore index c0f09851..83c4c403 100644 --- a/client/uikit/.gitignore +++ b/client/uikit/.gitignore @@ -1,8 +1,8 @@ +artifacts +!build dist/ node_modules -tests/ -custom +tests themes.json .DS_Store -/.idea -/.vscode +.idea diff --git a/client/uikit/build/.eslintrc.json b/client/uikit/build/.eslintrc.json new file mode 100644 index 00000000..47f12fa6 --- /dev/null +++ b/client/uikit/build/.eslintrc.json @@ -0,0 +1,24 @@ +{ + "root": true, + "env": { + "es2021": true, + "node": true, + "browser": false, + "commonjs": false + }, + "extends": [ + "../.eslintrc.json" + ], + "parserOptions": { + "ecmaVersion": 2022 + }, + "overrides": [ + { + "files": ["wrapper/*.js"], + "env": { + "browser": true, + "node": false + } + } + ] +} diff --git a/client/uikit/build/build.js b/client/uikit/build/build.js new file mode 100644 index 00000000..075cd090 --- /dev/null +++ b/client/uikit/build/build.js @@ -0,0 +1,91 @@ +import camelize from 'camelcase'; +import {basename, resolve} from 'path'; +import {args, compile, glob, icons} from './util.js'; + +const bundles = getBundleTasks(); +const components = await getComponentTasks(); +const buildAll = args.all || !Object.keys(args).filter(name => + !['d', 'debug', 'nominify', 'watch', '_'].includes(name) +).length; + +if (args.h || args.help) { + console.log(` + usage: + + build.js [componentA, componentB, ...] [-d|debug|nominify|watch] + + examples: + + build.js // builds all of uikit, including icons, components and does minification (implies 'all') + build.js uikit icons -d // builds uikit and the icons, skipping the minification and components + build.js core lightbox -d // builds uikit-core and the lightbox, skipping the minification + + available components: + + bundles: ${Object.keys(bundles).join(', ')} + components: ${Object.keys(components).join(', ')} + + `); + process.exit(0); +} + +let tasks; +const allTasks = {...bundles, ...components}; +if (buildAll) { + tasks = allTasks; +} else if (args.components) { + tasks = components; +} else { + tasks = Object.keys(args) + .map(step => allTasks[step]) + .filter(t => t); +} + +await Promise.all(Object.values(tasks).map(task => task())); + +function getBundleTasks() { + return { + + core: () => compile('src/js/uikit-core.js', 'dist/js/uikit-core'), + + uikit: () => compile('src/js/uikit.js', 'dist/js/uikit'), + + icons: async () => compile('build/wrapper/icons.js', 'dist/js/uikit-icons', { + name: 'icons', + replaces: {ICONS: await icons('{src/images,custom}/icons/*.svg')} + }), + + tests: async () => compile('tests/js/index.js', 'tests/js/test', { + name: 'test', + replaces: {TESTS: await getTestFiles()} + }) + + }; +} + +async function getComponentTasks() { + + const components = await glob('src/js/components/!(index).js'); + + return components.reduce((components, file) => { + + const name = basename(file, '.js'); + + components[name] = () => + compile('build/wrapper/component.js', `dist/js/components/${name}`, { + name, + external: ['uikit', 'uikit-util'], + globals: {uikit: 'UIkit', 'uikit-util': 'UIkit.util'}, + aliases: {component: resolve('src/js/components', name)}, + replaces: {NAME: `'${camelize(name)}'`} + }); + + return components; + + }, {}); +} + +async function getTestFiles() { + const files = await glob('tests/!(index).html', {nosort: true}); + return JSON.stringify(files.map(file => basename(file, '.html'))); +} diff --git a/client/uikit/build/icons.js b/client/uikit/build/icons.js new file mode 100644 index 00000000..990783fc --- /dev/null +++ b/client/uikit/build/icons.js @@ -0,0 +1,38 @@ +import {args, compile, glob, icons} from './util.js'; + +if (args.h || args.help) { + console.log(` + + Builds additional custom uikit icons found in './custom/*/icons' + + usage: + + icons.js [custom|name] + + -c|--custom + Specify custom folder to look for icons (default: './custom/*/icons') + -n|--name + Specify name regex to match against folder (default: '([a-z]+)/icons$') + + `); + process.exit(0); +} + +const path = args.c || args.custom || 'custom/*/icons'; +const match = args.n || args.name || '([a-z]+)/icons$'; + +await Promise.all((await glob(path)).map(compileIcons)); + +async function compileIcons(folder) { + const [, name] = folder.toString().match(new RegExp(match, 'i')); + return compile( + 'build/wrapper/icons.js', + `dist/js/uikit-icons-${name}`, + { + name, + replaces: { + ICONS: await icons(`{src/images/icons,${folder}}/*.svg`) + } + } + ); +} diff --git a/client/uikit/build/less.js b/client/uikit/build/less.js new file mode 100644 index 00000000..0a0d1d1b --- /dev/null +++ b/client/uikit/build/less.js @@ -0,0 +1,91 @@ +import rtlcss from 'rtlcss'; +import {basename} from 'path'; +import {args, banner, glob, minify, pathExists, read, readJson, renderLess, write} from './util.js'; + +const {rtl} = args; +const develop = args.develop || args.debug || args.d || args.nominify; +const sources = [ + {src: 'src/less/uikit.less', dist: `dist/css/uikit-core${rtl ? '-rtl' : ''}.css`}, + {src: 'src/less/uikit.theme.less', dist: `dist/css/uikit${rtl ? '-rtl' : ''}.css`} +]; + +const themes = await pathExists('themes.json') ? await readJson('themes.json') : {}; + +for (const src of await glob('custom/*.less')) { + const theme = basename(src, '.less'); + const dist = `dist/css/uikit.${theme}${rtl ? '-rtl' : ''}.css`; + + themes[theme] = {css: `../${dist}`}; + + if (await pathExists(`dist/js/uikit-icons-${theme}.js`)) { + themes[theme].icons = `../dist/js/uikit-icons-${theme}.js`; + } + + sources.push({src, dist}); +} + +await Promise.all(sources.map(({src, dist}) => compile(src, dist, develop, rtl))); + +if (!rtl && (Object.keys(themes).length || !await pathExists('themes.json'))) { + await write('themes.json', JSON.stringify(themes)); +} + +async function compile(file, dist, develop, rtl) { + + const less = await read(file); + + let output = (await renderLess(less, { + relativeUrls: true, + rootpath: '../../', + paths: ['src/less/', 'custom/'] + })).replace(/\.\.\/dist\//g, ''); + + if (rtl) { + output = rtlcss.process( + output, + { + stringMap: [{ + name: 'previous-next', + priority: 100, + search: ['previous', 'Previous', 'PREVIOUS'], + replace: ['next', 'Next', 'NEXT'], + options: { + scope: '*', + ignoreCase: false + } + }] + }, + [ + { + name: 'customNegate', + priority: 50, + directives: { + control: {}, + value: [] + }, + processors: [ + { + expr: ['--uk-position-translate-x', 'stroke-dashoffset'].join('|'), + action(prop, value, context) { + return {prop, value: context.util.negate(value)}; + } + } + ] + } + ], + { + pre(root, postcss) { + root.prepend(postcss.comment({text: 'rtl:begin:rename'})); + root.append(postcss.comment({text: 'rtl:end:rename'})); + } + } + ); + } + + await write(dist, banner + output); + + if (!develop) { + await minify(dist); + } + +} diff --git a/client/uikit/build/package.json b/client/uikit/build/package.json new file mode 100644 index 00000000..e986b24b --- /dev/null +++ b/client/uikit/build/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "module" +} diff --git a/client/uikit/build/prefix.js b/client/uikit/build/prefix.js new file mode 100644 index 00000000..5bb1c911 --- /dev/null +++ b/client/uikit/build/prefix.js @@ -0,0 +1,70 @@ +import inquirer from 'inquirer'; +import {args, glob, read, replaceInFile, validClassName} from './util.js'; + +if (args.h || args.help) { + console.log(` + usage: + + prefix.js [-p{refix}=your_great_new_prefix] + + example: + + prefix.js // will prompt for a prefix to replace the current one with + prefix.js -p=xyz // will replace any existing prefix with xyz + + `); + process.exit(0); +} + +const currentPrefix = await findExistingPrefix(); +const prefix = await getPrefix(); + +if (currentPrefix === prefix) { + throw new Error(`already prefixed with: ${prefix}`); +} + +await replacePrefix(currentPrefix, prefix); + +async function findExistingPrefix() { + const data = await read('dist/css/uikit.css'); + const res = data.match(new RegExp(`(${validClassName.source})(-[a-z]+)?-grid`)); + return res && res[1]; +} + +async function getPrefix() { + + const prefixFromInput = args.p || args.prefix; + + if (!prefixFromInput) { + + const prompt = inquirer.createPromptModule(); + + return (await prompt({ + name: 'prefix', + message: 'enter a prefix', + validate: (val, res) => val.length && val.match(validClassName) ? !!(res.prefix = val) : 'invalid prefix' + })).prefix; + } + + if (validClassName.test(prefixFromInput)) { + return prefixFromInput; + } else { + throw `illegal prefix: ${prefixFromInput}`; + } +} + +async function replacePrefix(from, to) { + for (const file of await glob('dist/**/*.css')) { + await replaceInFile(file, data => data.replace( + new RegExp(`${from}-${/([a-z\d-]+)/.source}`, 'g'), + `${to}-$1` + )); + } + + for (const file of await glob('dist/**/*.js')) { + await replaceInFile(file, data => data + .replace(new RegExp(`${from}-`, 'g'), `${to}-`) + .replace(new RegExp(`(${from})?UIkit`, 'g'), `${to === 'uk' ? '' : to}UIkit`) + ); + } +} diff --git a/client/uikit/build/publishDev.js b/client/uikit/build/publishDev.js new file mode 100755 index 00000000..7cbb6638 --- /dev/null +++ b/client/uikit/build/publishDev.js @@ -0,0 +1,36 @@ +import semver from 'semver'; +import {args, getVersion, run} from './util.js'; + +const {inc} = semver; + +// default exec options +if (args.f || args.force || await isDevCommit()) { + + // increase version patch number + const version = inc(await getVersion(), 'patch'); + + // get current git hash + const hash = (await run('git rev-parse --short HEAD')).trim(); + + // set version of package.json + await run(`npm version ${version}-dev.${hash} --git-tag-version false`); + + // create dist files + await run('yarn compile && yarn compile-rtl && yarn build-scss'); + + // publish to dev tag + await run('npm publish --tag dev'); + +} + +async function isDevCommit() { + + // check for changes to publish (%B: raw body (unwrapped subject and body) + const message = await run('git log -1 --pretty=%B'); + + // https://www.conventionalcommits.org/en/v1.0.0/ + const type = message.match(/^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/); + + return type && ['feat', 'fix', 'refactor', 'perf'].includes(type[2]); + +} diff --git a/client/uikit/build/release.js b/client/uikit/build/release.js new file mode 100644 index 00000000..4cd81e19 --- /dev/null +++ b/client/uikit/build/release.js @@ -0,0 +1,62 @@ +import archiver from 'archiver'; +import inquirer from 'inquirer'; +import dateFormat from 'dateformat/lib/dateformat.js'; +import {createWriteStream} from 'fs'; +import semver from 'semver'; +import {args, getVersion, glob, logFile, replaceInFile, run} from './util.js'; + +const {coerce, inc, prerelease, valid} = semver; + +const prevVersion = await getVersion(); +const version = await inquireVersion(args.v || args.version); + +await Promise.all([ + run(`npm version ${version} --git-tag-version false`), + replaceInFile('CHANGELOG.md', data => + data.replace(/^##\s*WIP/m, `## ${versionFormat(version)} (${dateFormat(Date.now(), 'mmmm d, yyyy')})`) + ), + replaceInFile('.github/ISSUE_TEMPLATE/bug-report.md', data => + data.replace(prevVersion, version) + ) +]); + +await run('yarn compile'); +await run('yarn compile-rtl'); +await run('yarn build-scss'); + +await createPackage(version); + +async function inquireVersion(v) { + + if (valid(v)) { + return v; + } + + const prompt = inquirer.createPromptModule(); + + return (await prompt({ + name: 'version', + message: 'Enter a version', + default: () => inc(prevVersion, prerelease(prevVersion) ? 'prerelease' : 'patch'), + validate: val => !!val.length || 'Invalid version' + })).version; + +} + +async function createPackage(version) { + const file = `dist/uikit-${version}.zip`; + const archive = archiver('zip'); + + archive.pipe(createWriteStream(file)); + + (await glob('dist/{js,css}/uikit?(-icons|-rtl)?(.min).{js,css}')).forEach(file => + archive.file(file, {name: file.substring(5)}) + ); + + await archive.finalize(); + await logFile(file); +} + +function versionFormat(version) { + return [coerce(version).version].concat(prerelease(version) || []).join(' '); +} diff --git a/client/uikit/build/scope.js b/client/uikit/build/scope.js new file mode 100644 index 00000000..2f6151be --- /dev/null +++ b/client/uikit/build/scope.js @@ -0,0 +1,91 @@ +import {args, glob, minify, read, renderLess, replaceInFile, validClassName} from './util.js'; + +if (args.h || args.help) { + console.log(` + usage: + + scope.js [-s{cope}=your_great_new_scope_name][cleanup] + + example: + + scope.js // will scope with uk-scope + scope.js -s "my-scope" // will replace any existing scope with my-scope + scope.js cleanup // will remove current scope + + `); + process.exit(0); +} +const currentScopeRe = /\/\* scoped: ([^*]*) \*\/\n/; +const currentScopeLegacyRe = /\.(uk-scope)/; + +const files = await glob('dist/**/!(*.min).css'); +const prevScope = await getScope(files); + +if (args.cleanup && prevScope) { + await cleanup(files, prevScope); +} else if (prevScope) { + const newScope = getNewScope(); + + if (prevScope === newScope) { + console.warn(`Already scoped with: ${prevScope}`); + process.exit(0); + } + + await cleanup(files, prevScope); + await scope(files, newScope); + +} else { + await scope(files, getNewScope()); +} + +async function getScope(files) { + for (const file of files) { + const data = await read(file); + const [, scope] = (data.match(currentScopeRe) || data.match(currentScopeLegacyRe) || []); + if (scope) { + return scope; + } + } + return ''; +} + +function getNewScope() { + + const scopeFromInput = args.scope || args.s || 'uk-scope'; + + if (validClassName.test(scopeFromInput)) { + return scopeFromInput; + } else { + throw `Illegal scope-name: ${scopeFromInput}`; + } +} + +async function scope(files, scope) { + for (const file of files) { + await replaceInFile(file, async data => { + const output = await renderLess(`.${scope} {\n${stripComments(data)}\n}`); + return `/* scoped: ${scope} */\n${ + output.replace(new RegExp(`.${scope}\\s((\\.(uk-(drag|modal-page|offcanvas-page|offcanvas-flip)))|html|:root)`, 'g'), '$1') // unescape + }`; + }); + await minify(file); + } +} + +async function cleanup(files, scope) { + const string = scope.split(' ').map(scope => `.${scope}`).join(' '); + for (const file of files) { + await replaceInFile(file, data => data + .replace(currentScopeRe, '') // remove scope comment + .replace(new RegExp(` *${string} ({[\\s\\S]*?})?`, 'g'), '') // replace classes + ); + } +} + +function stripComments(input) { + return input + .replace(/\/\*(.|\n)*?\*\//gm, '') + .split('\n') + .filter(line => line.trim().substr(0, 2) !== '//') + .join('\n'); +} diff --git a/client/uikit/build/scss.js b/client/uikit/build/scss.js new file mode 100644 index 00000000..d271f04b --- /dev/null +++ b/client/uikit/build/scss.js @@ -0,0 +1,291 @@ +import NP from 'number-precision'; +import {glob, read, write} from './util.js'; + +NP.enableBoundaryChecking(false); + +const themeMixins = {}; +const coreMixins = {}; +const themeVar = {}; +const coreVar = {}; + +/* template for the new components/mixins.scss file*/ +const mixinTemplate = `// +// Component: Mixin +// Description: Defines mixins which are used across all components +// +// ======================================================================== + + +// SVG +// ======================================================================== + +/// Replace \`$search\` with \`$replace\` in \`$string\` +/// @author Hugo Giraudel +/// @param {String} $string - Initial string +/// @param {String} $search - Substring to replace +/// @param {String} $replace ('') - New value +/// @return {String} - Updated string +@function str-replace($string, $search, $replace: '') { + $index: str-index($string, $search); + + @if $index { + @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace); + } + + @return $string; +} + +@mixin svg-fill($src, $color-default, $color-new){ + + $replace-src: str-replace($src, $color-default, $color-new) !default; + $replace-src: str-replace($replace-src, "#", "%23"); + background-image: url(quote($replace-src)); +}`; + +/* template for the inverse components */ +const inverseTemplate = ` @include hook-inverse-component-base(); + @include hook-inverse-component-link(); + @include hook-inverse-component-heading(); + @include hook-inverse-component-divider(); + @include hook-inverse-component-list(); + @include hook-inverse-component-icon(); + @include hook-inverse-component-form(); + @include hook-inverse-component-button(); + @include hook-inverse-component-grid(); + @include hook-inverse-component-close(); + @include hook-inverse-component-totop(); + @include hook-inverse-component-badge(); + @include hook-inverse-component-label(); + @include hook-inverse-component-article(); + @include hook-inverse-component-search(); + @include hook-inverse-component-nav(); + @include hook-inverse-component-navbar(); + @include hook-inverse-component-subnav(); + @include hook-inverse-component-breadcrumb(); + @include hook-inverse-component-pagination(); + @include hook-inverse-component-tab(); + @include hook-inverse-component-slidenav(); + @include hook-inverse-component-dotnav(); + @include hook-inverse-component-accordion(); + @include hook-inverse-component-iconnav(); + @include hook-inverse-component-text(); + @include hook-inverse-component-column(); + @include hook-inverse-component-utility();`; + +/* First Step: Go through all files */ +for (const file of await glob('src/less/**/*.less')) { + + const data = await read(file); + + /* replace all Less stuff with SCSS */ + let scssData = data.replace(/\/less\//g, '/scss/') // change less/ dir to scss/ on imports + .replace(/\.less/g, '.scss') // change .less extensions to .scss on imports + .replace(/@/g, '$') // convert variables + .replace(/(:[^'"]*?\([^'"]+?)\s*\/\s*([0-9.-]+)\)/g, (exp, m1, m2) => `${m1} * ${NP.round(1 / parseFloat(m2), 5)})`) + .replace(/--uk-[^\s]+: (\$[^\s]+);/g, (exp, name) => exp.replace(name, `#{${name}}`)) + .replace(/\\\$/g, '\\@') // revert classes using the @ symbol + .replace(/ e\(/g, ' unquote(') // convert escape function + .replace(/\.([\w-]*)\s*\((.*)\)\s*{/g, '@mixin $1($2){') // hook -> mixins + .replace(/(\$[\w-]*)\s*:(.*);/g, '$1: $2 !default;') // make variables optional + .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\s*}/g, '// @mixin $1($2){}') // comment empty mixins + .replace(/\.(hook[a-zA-Z\-\d]+)(\(\))?;/g, '@if(mixin-exists($1)) {@include $1();}') // hook calls surrounded by a mixin-exists + .replace(/\$(import|supports|media|font-face|page|-ms-viewport|keyframes|-webkit-keyframes|-moz-document)/g, '@$1') // replace valid '@' statements + .replace(/tint\((\$[\w-]+),\s([^)]*)\)/g, 'mix(white, $1, $2)') // replace Less function tint with mix + .replace(/fade\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `rgba(${p1}, ${p2 / 100})`;}) // replace Less function fade with rgba + .replace(/fadeout\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `fade-out(${p1}, ${p2 / 100})`;}) // replace Less function fadeout with fade-out + .replace(/\.svg-fill/g, '@include svg-fill') // include svg-fill mixin + .replace(/(.*):extend\((\.[\w-\\@]*) all\) when \((\$[\w-]*) = ([\w]+)\) {}/g, '@if ( $3 == $4 ) { $1 { @extend $2 !optional;} }') // update conditional extend and add !optional to ignore warnings + .replace(/(\.[\w-\\@]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*{\s*@if\(mixin-exists\(([\w-]*)\)\) {@include\s([\w-]*)\(\);\s*}\s*}/g, '@if ($2 == $3) { $1 { @if (mixin-exists($4)) {@include $4();}}}') // update conditional hook + .replace(/(\.[\w-\\@]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*({\s*.*?\s*})/gs, '@if ($2 == $3) {\n$1 $4\n}') // replace conditionals + .replace(/\${/g, '#{$') // string literals: from: /~"(.*)"/g, to: '#{"$1"}' + .replace(/[^(](-\$[\w-]*)/g, ' ($1)') // surround negative variables with brackets + .replace(/(--[\w-]+:\s*)~'([^']+)'/g, '$1$2') // string literals in custom properties + .replace(/~('[^']+')/g, 'unquote($1)'); // string literals: for real + + /* File name of the current file */ + const [filename] = file.split('/').pop().split('.less'); + + if (filename !== 'inverse') { + scssData = scssData.replace(/hook-inverse(?!-)/g, `hook-inverse-component-${filename}`); + } else { + const joinedHook = `@mixin hook-inverse(){\n${inverseTemplate}\n}\n`; + scssData = scssData.replace(/\*\//, '*/\n' + joinedHook); + } + + /* get all the mixins and remove them from the file */ + scssData = getMixinsFromFile(file, scssData); + + /* get all Variables but not from the mixin.less file */ + if (filename !== 'mixin') { + scssData = await getVariablesFromFile(file, scssData); + } + + if (filename === 'uikit.theme') { + /* remove the theme import first place */ + scssData = scssData.replace(/\/\/\n\/\/ Theme\n\/\/\n\n@import "theme\/_import.scss";/, ''); + /* add uikit-mixins and uikit-variables include to the uikit.scss file and change order, to load theme files first */ + scssData = scssData.replace(/\/\/ Core\n\/\//g, '// Theme\n//\n\n@import "theme/_import.scss";'); + } + + /* mixin.less needs to be fully replaced by the new mixin file*/ + if (filename === 'mixin') { + scssData = mixinTemplate; + } + + await write(file.replace(/less/g, 'scss').replace('.theme.', '-theme.'), scssData); + +} + +/* Second Step write all new needed files for SASS */ + +/* write mixins into new file */ +const mixins_theme = Object.keys(themeMixins).map(function (key) { return themeMixins[key]; }); +await write('src/scss/mixins-theme.scss', mixins_theme.join('\n')); + +const mixins_core = Object.keys(coreMixins).map(function (key) { return coreMixins[key]; }); +await write('src/scss/mixins.scss', mixins_core.join('\n')); + +/* write core variables */ +const compactCoreVar = new Set(); +Object.keys(coreVar).map(key => getAllDependencies(coreVar, key).forEach(dependency => compactCoreVar.add(dependency))); + +await write('src/scss/variables.scss', Array.from(compactCoreVar).join('\n')); + +/* write theme variables */ +const compactThemeVar = new Set(); +Object.keys(themeVar).map(key => getAllDependencies(themeVar, key).forEach(dependency => compactThemeVar.add(dependency))); + +await write('src/scss/variables-theme.scss', Array.from(compactThemeVar).join('\n')); + +/* + * recursive function to get a dependencie Set which is ordered so that no depencies exist to a later on entry + * @return Set with all the dependencies. + */ +function getAllDependencies(allVariables, currentKey, dependencies = new Set()) { + + if (!allVariables[currentKey].dependencies.length) { + + dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`); + return Array.from(dependencies); + } else { + + allVariables[currentKey].dependencies.forEach(dependecy => { + getAllDependencies(allVariables, dependecy, dependencies).forEach(newDependency => dependencies.add(newDependency)); + }); + + dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`); + return Array.from(dependencies); + } +} + +/* + * function to extract all the mixins from a given file with its data. + * @return an updated data where the mixins have been removed. + */ +function getMixinsFromFile(file, data) { + + /* Step 1: get all includes and insert them, so that at least empty mixins exist. */ + let regex = /@include ([a-z0-9-]+)/g; + let match = regex.exec(data); + + while (match) { + if (!(match[1] in themeMixins)) { themeMixins[match[1]] = `@mixin ${match[1]}(){}`; } + if (!(match[1] in coreMixins)) { coreMixins[match[1]] = `@mixin ${match[1]}(){}`; } + + match = regex.exec(data); + } + + /* Step 2: get all multiline mixins */ + regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g; + match = regex.exec(data); + + while (match) { + [themeMixins[match[1]]] = match; + if (file.indexOf('theme/') < 0) { + [coreMixins[match[1]]] = match; + } + match = regex.exec(data); + } + + /* Step 3: get all singleline mixins */ + regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g; + match = regex.exec(data); + + while (match) { + [themeMixins[match[1]]] = match; + if (file.indexOf('theme/') < 0) { + [coreMixins[match[1]]] = match; + } + + match = regex.exec(data); + } + + /* Step 4: remove the mixins from the file, so that users can overwrite them in their custom code. */ + return data + .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g, '') + .replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g, ''); +} + +/* + * function to extract all the variables from a given file with its data. + * @return an updated data where the icons have been replaced by the actual SVG data. + */ +async function getVariablesFromFile(file, data) { + const regex = /(\$[\w-]*)\s*:\s*(.*);/g; + let match = regex.exec(data); + + while (match) { + + /* check if variable is a background icon, if so replace it directly by the SVG */ + if (match[0].indexOf('../../images/backgrounds') >= 0) { + + const iconregex = /(\$[\w-]+)\s*:\s*"\.\.\/\.\.\/images\/backgrounds\/([\w./-]+)" !default;/g; + const iconmatch = iconregex.exec(match[0]); + let svg = (await read(`src/images/backgrounds/${iconmatch[2]}`)).toString(); + svg = `"${svg.replace(/\r?\n|\r/g, '%0A') + .replace(/"/g, '\'') + .replace(/\s/g, '%20') + .replace(//g, '%3E') + .replace(/%3Csvg/, 'data:image/svg+xml;charset=UTF-8,%3Csvg')}"`; + + /* add SVG to the coreVar and themeVar only if it is a theme file and make it optional */ + if (file.indexOf('theme/') < 0) { + coreVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []}; + } + + themeVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []}; + + /* add SVG to the variable within the file itself as well */ + const inlineSVG = `${iconmatch[1]}: ${svg} !default;`; + data = data.replace(match[0], inlineSVG); + + /* when it is not an SVG add the variable and search for its dependencies */ + } else { + + const variablesRegex = /(\$[\w-]+)/g; + let variablesMatch = variablesRegex.exec(match[2]); + const dependencies = []; + + while (variablesMatch) { + dependencies.push(variablesMatch[1]); + variablesMatch = variablesRegex.exec(match[2]); + } + + /* add variables only to the core Variables if it is not a theme file */ + if (file.indexOf('theme/') < 0) { + coreVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)}; + } + + themeVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)}; + } + + match = regex.exec(data); + } + + return data; +} diff --git a/client/uikit/build/util.js b/client/uikit/build/util.js new file mode 100644 index 00000000..4be5dbff --- /dev/null +++ b/client/uikit/build/util.js @@ -0,0 +1,246 @@ +import less from 'less'; +import fs from 'fs-extra'; +import pLimit from 'p-limit'; +import globImport from 'glob'; +import {optimize} from 'svgo'; +import {promisify} from 'util'; +import minimist from 'minimist'; +import CleanCSS from 'clean-css'; +import html from 'rollup-plugin-html'; +import buble from '@rollup/plugin-buble'; +import alias from '@rollup/plugin-alias'; +import modify from 'rollup-plugin-modify'; +import replace from '@rollup/plugin-replace'; +import {basename, dirname, join} from 'path'; +import {exec as execImport} from 'child_process'; +import {rollup, watch as rollupWatch} from 'rollup'; +import {minify as rollupMinify} from 'rollup-plugin-esbuild'; + +const limit = pLimit(Number(process.env.cpus || 2)); + +export const exec = promisify(execImport); +export const glob = promisify(globImport); +export const {pathExists, readJson} = fs; + +export const banner = `/*! UIkit ${await getVersion()} | https://www.getuikit.com | (c) 2014 - ${new Date().getFullYear()} YOOtheme | MIT License */\n`; +export const validClassName = /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/; + +const argv = minimist(process.argv.slice(2)); + +argv._.forEach(arg => { + const tokens = arg.split('='); + argv[tokens[0]] = tokens[1] || true; +}); + +export const args = argv; + +export function read(file) { + return fs.readFile(file, 'utf8'); +} + +export async function write(dest, data) { + + const err = await fs.writeFile(dest, data); + + if (err) { + console.log(err); + throw err; + } + + await logFile(dest); + + return dest; + +} + +export async function logFile(file) { + const data = await read(file); + console.log(`${cyan(file)} ${getSize(data)}`); +} + +export async function minify(file) { + + const {styles} = await limit(() => new CleanCSS({ + advanced: false, + keepSpecialComments: 0, + rebase: false, + returnPromise: true + }).minify([file])); + + await write(`${join(dirname(file), basename(file, '.css'))}.min.css`, styles); + + return styles; + +} + +export function renderLess(data, options) { + return limit(async () => (await less.render(data, options)).css); +} + +export async function compile(file, dest, {external, globals, name, aliases, replaces} = {}) { + + const minify = !args.nominify; + const debug = args.d || args.debug; + const watch = args.w || args.watch; + + name = (name || '').replace(/[^\w]/g, '_'); + + const inputOptions = { + external, + input: file, + plugins: [ + replace({ + preventAssignment: true, + values: { + VERSION: `'${await getVersion()}'`, + ...replaces + } + }), + alias({ + entries: { + 'uikit-util': './src/js/util/index.js', + ...aliases + } + }), + html({ + include: '**/*.svg', + htmlMinifierOptions: { + collapseWhitespace: true + } + }), + buble({namedFunctionExpressions: false}), + modify({ + find: /(>)\\n\s+|\\n\s+(<)/, + replace: (m, m1, m2) => `${m1 || ''} ${m2 || ''}` + }) + ] + }; + + const outputOptions = { + globals, + banner, + format: 'umd', + amd: {id: `UIkit${name}`.toLowerCase()}, + name: `UIkit${ucfirst(name)}`, + sourcemap: debug ? 'inline' : false + }; + + const output = [{ + ...outputOptions, + file: `${dest}.js` + }]; + + if (minify) { + output.push({ + ...outputOptions, + file: `${dest}.min.js`, + plugins: [minify ? rollupMinify() : undefined] + }); + } + + if (!watch) { + const bundle = await rollup(inputOptions); + + for (const options of output) { + await limit(() => bundle.write(options)); + logFile(options.file); + } + + await bundle.close(); + } else { + + console.log('UIkit is watching the files...'); + + const watcher = rollupWatch({ + ...inputOptions, + output + }); + + watcher.on('event', ({code, result, output, error}) => { + if (result) { + result.close(); + + } + if (code === 'BUNDLE_END' && output) { + output.map(logFile); + } + if (error) { + console.error(error); + } + }); + + watcher.close(); + } + +} + +export async function icons(src) { + + const options = { + plugins: [ + { + name: 'preset-default', + params: { + overrides: { + removeViewBox: false, + cleanupNumericValues: { + floatPrecision: 3 + }, + convertPathData: false, + convertShapeToPath: false, + mergePaths: false, + removeDimensions: false, + removeStyleElement: false, + removeScriptElement: false, + removeUnknownsAndDefaults: false, + removeUselessStrokeAndFill: false + } + } + } + ] + }; + + const files = await glob(src, {nosort: true}); + const icons = await Promise.all( + files.map(file => + limit(async () => + (await optimize(await read(file), options)).data + ) + ) + ); + + return JSON.stringify(files.reduce((result, file, i) => { + result[basename(file, '.svg')] = icons[i]; + return result; + }, {}), null, ' '); + +} + +export async function run(cmd, options) { + const {stdout, stderr} = await limit(() => exec(cmd, options)); + + stdout && console.log(stdout.trim()); + stderr && console.log(stderr.trim()); + + return stdout; +} + +export function ucfirst(str) { + return str.length ? str.charAt(0).toUpperCase() + str.slice(1) : ''; +} + +export async function getVersion() { + return (await readJson('package.json')).version; +} + +export async function replaceInFile(file, fn) { + await write(file, await fn(await read(file))); +} + +function cyan(str) { + return `\x1b[1m\x1b[36m${str}\x1b[39m\x1b[22m`; +} + +function getSize(data) { + return `${(data.length / 1024).toFixed(2)}kb`; +} diff --git a/client/uikit/build/wrapper/component.js b/client/uikit/build/wrapper/component.js new file mode 100644 index 00000000..01236857 --- /dev/null +++ b/client/uikit/build/wrapper/component.js @@ -0,0 +1,7 @@ +import Component from 'component'; + +if (typeof window !== 'undefined' && window.UIkit) { + window.UIkit.component(NAME, Component); +} + +export default Component; diff --git a/client/uikit/build/wrapper/icons.js b/client/uikit/build/wrapper/icons.js new file mode 100644 index 00000000..122a6b6e --- /dev/null +++ b/client/uikit/build/wrapper/icons.js @@ -0,0 +1,15 @@ +function plugin(UIkit) { + + if (plugin.installed) { + return; + } + + UIkit.icon.add(ICONS); + +} + +if (typeof window !== 'undefined' && window.UIkit) { + window.UIkit.use(plugin); +} + +export default plugin; diff --git a/features/common/client/build.gradle b/features/common/client/build.gradle index 8e304e7f..1ab95599 100644 --- a/features/common/client/build.gradle +++ b/features/common/client/build.gradle @@ -11,7 +11,7 @@ kotlin { commonMain { dependencies { api project(":postssystem.features.common.common") - api "dev.inmo:micro_utils.repos.ktor.client:$microutils_version" + api libs.microutils.repos.ktor.client api "io.ktor:ktor-client-auth:$ktor_version" api "io.ktor:ktor-client-logging:$ktor_version" } diff --git a/features/common/common/build.gradle b/features/common/common/build.gradle index 7f69df12..bad8ab0e 100644 --- a/features/common/common/build.gradle +++ b/features/common/common/build.gradle @@ -10,8 +10,8 @@ kotlin { sourceSets { commonMain { dependencies { - api "dev.inmo:micro_utils.common:$microutils_version" - api "dev.inmo:micro_utils.serialization.typed_serializer:$microutils_version" + api libs.microutils.common + api libs.microutils.serialization.typedserializer api "io.insert-koin:koin-core:$koin_version" api "com.benasher44:uuid:$uuid_version" api "com.soywiz.korlibs.klock:klock:$klock_version" diff --git a/features/common/common/create_version_file.gradle b/features/common/common/create_version_file.gradle new file mode 100644 index 00000000..ce17310c --- /dev/null +++ b/features/common/common/create_version_file.gradle @@ -0,0 +1,52 @@ +String versionLine() { return " const val project = \"$project.version\"" } +File versionFile() { return project.file("src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/Version.kt") } + +void createVersionFile() { + String versionLine = versionLine() + File versionFile = versionFile() + versionFile.parentFile.mkdirs() + if (versionFile.exists()) versionFile.delete() + versionFile.createNewFile() + versionFile.text = """package dev.inmo.postssystem.features.common.common + +/* + * THIS FILE HAS BEEN CREATED AUTOMATICALLY. + * DO NOT EDIT THIS FILE, ITS CONTENT WILL BE OVERWRITTEN + * WITH ANY NEW BUILD + */ +object Version { +$versionLine +} +""" +} + +Boolean isVersionFileUpToDate() { + String versionLine = versionLine() + File versionFile = versionFile() + + boolean upToDate = false + if (versionFile.exists()) { + versionFile.withReader { reader -> + String line + while ((line = reader.readLine()) != null && !upToDate) { + upToDate = line == versionLine + } + } + } + + return upToDate +} + +task createVersionFile { + doLast { + createVersionFile() + } + outputs.upToDateWhen { + isVersionFileUpToDate() + } +} + + +if (!isVersionFileUpToDate()) { + createVersionFile() +} diff --git a/features/common/server/build.gradle b/features/common/server/build.gradle index dc631750..461a4a90 100644 --- a/features/common/server/build.gradle +++ b/features/common/server/build.gradle @@ -10,9 +10,9 @@ kotlin { commonMain { dependencies { api project(":postssystem.features.common.common") - api "dev.inmo:micro_utils.repos.exposed:$microutils_version" - api "dev.inmo:micro_utils.repos.ktor.server:$microutils_version" - api "dev.inmo:micro_utils.ktor.server:$microutils_version" + api libs.microutils.repos.exposed + api libs.microutils.repos.ktor.server + api libs.microutils.ktor.server } } jvmMain { diff --git a/features/content/binary/common/build.gradle b/features/content/binary/common/build.gradle index df874449..08809807 100644 --- a/features/content/binary/common/build.gradle +++ b/features/content/binary/common/build.gradle @@ -12,7 +12,7 @@ kotlin { dependencies { api project(":postssystem.features.common.common") api project(":postssystem.features.content.common") - api "dev.inmo:micro_utils.mime_types:$microutils_version" + api libs.microutils.mimetypes } } } diff --git a/features/content/common/build.gradle b/features/content/common/build.gradle index 00ae37d6..5b05dc30 100644 --- a/features/content/common/build.gradle +++ b/features/content/common/build.gradle @@ -11,7 +11,7 @@ kotlin { commonMain { dependencies { api project(":postssystem.features.common.common") - api "dev.inmo:micro_utils.mime_types:$microutils_version" + api libs.microutils.mimetypes } } } diff --git a/features/files/common/build.gradle b/features/files/common/build.gradle index df71df3b..f46e3b83 100644 --- a/features/files/common/build.gradle +++ b/features/files/common/build.gradle @@ -11,8 +11,8 @@ kotlin { commonMain { dependencies { api project(":postssystem.features.common.common") - api "dev.inmo:micro_utils.mime_types:$microutils_version" - api "dev.inmo:micro_utils.repos.common:$microutils_version" + api libs.microutils.mimetypes + api libs.microutils.repos.common } } } diff --git a/features/users/common/build.gradle b/features/users/common/build.gradle index 398ec6ca..ecd1b752 100644 --- a/features/users/common/build.gradle +++ b/features/users/common/build.gradle @@ -11,12 +11,12 @@ kotlin { commonMain { dependencies { api project(":postssystem.features.common.common") - api "dev.inmo:micro_utils.repos.common:$microutils_version" + api libs.microutils.repos.common } } jvmMain { dependencies { - api "dev.inmo:micro_utils.repos.exposed:$microutils_version" + api libs.microutils.repos.exposed } } } diff --git a/gradle.properties b/gradle.properties index ef3d2544..2dcc2a9b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,17 +13,14 @@ kotlin_version=1.6.10 kotlin_serialisation_core_version=1.3.2 koin_version=3.1.2 -microutils_version=0.9.4 ktor_version=1.6.7 logback_version=1.2.10 uuid_version=0.3.1 -klock_version=2.4.12 - -tgbotapi_version=0.38.3 +klock_version=2.5.2 # Server -kotlin_exposed_version=0.37.2 +kotlin_exposed_version=0.37.3 psql_version=42.3.0 # JS diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c6dba6f6..6cca7049 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,13 +1,35 @@ [versions] -jsuikit = "0.0.27" -compose = "1.0.1" -microutils = "0.9.4" +kotlin = "1.6.10" +kotlin-serialization = "1.3.2" +jsuikit = "0.0.41" +compose = "1.1.0" +microutils = "0.9.9" +tgbotapi = "0.38.6" +ktor = "1.6.7" +exposed = "0.37.3" +psql = "42.3.0" + +android-dexcount = "3.0.1" +android-junit = "4.12" +android-test-junit = "1.1.2" +android-espresso-core = "3.3.0" [libraries] +kotlin-std = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } +kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" } +kotlin-serialization-properties = { module = "org.jetbrains.kotlinx:kotlinx-serialization-properties", version.ref = "kotlin-serialization" } + jsuikit = { module = "dev.inmo:kjsuikit", version.ref = "jsuikit" } +postgresql = { module = "org.postgresql:postgresql", version.ref = "psql" } + +exposed-jdbs = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } + +ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } +ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" } + microutils-common = { module = "dev.inmo:micro_utils.common", version.ref = "microutils" } microutils-pagination-common = { module = "dev.inmo:micro_utils.pagination.common", version.ref = "microutils" } microutils-fsm-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" } @@ -19,8 +41,14 @@ microutils-repos-ktor-server = { module = "dev.inmo:micro_utils.repos.ktor.serve microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" } microutils-mimetypes = { module = "dev.inmo:micro_utils.mime_types", version.ref = "microutils" } microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" } +microutils-ktor-server = { module = "dev.inmo:micro_utils.ktor.server", version.ref = "microutils" } microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" } +tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" } + +androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test-junit" } +androidx-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso-core" } + [plugins] compose = { id = "org.jetbrains.compose", version.ref = "compose" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9b50a1ea..11ec8ce8 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip zipStoreBase=GRADLE_USER_HOME diff --git a/mppAndroidProject.gradle b/mppAndroidProject.gradle index af688eec..669e24e4 100644 --- a/mppAndroidProject.gradle +++ b/mppAndroidProject.gradle @@ -20,6 +20,13 @@ kotlin { implementation kotlin('test-annotations-common') } } + androidTest { + dependencies { + implementation kotlin('test-junit') + implementation libs.androidx.test.junit + implementation libs.androidx.espresso + } + } } } diff --git a/mppProjectWithSerialization.gradle b/mppProjectWithSerialization.gradle index dbd59f01..1fc1f408 100644 --- a/mppProjectWithSerialization.gradle +++ b/mppProjectWithSerialization.gradle @@ -16,8 +16,8 @@ kotlin { sourceSets { commonMain { dependencies { - implementation kotlin('stdlib') - api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version" + implementation libs.kotlin.std + api libs.kotlin.serialization } } commonTest { @@ -40,8 +40,8 @@ kotlin { androidTest { dependencies { implementation kotlin('test-junit') - implementation "androidx.test.ext:junit:$test_ext_junit_version" - implementation "androidx.test.espresso:espresso-core:$espresso_core" + implementation libs.androidx.test.junit + implementation libs.androidx.espresso } } } diff --git a/prepare_client.sh b/prepare_client.sh new file mode 100755 index 00000000..62f08ccc --- /dev/null +++ b/prepare_client.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +function assert_success() { + "${@}" + local status=${?} + if [ ${status} -ne 0 ]; then + send_notification "### Error ${status} at: ${BASH_LINENO[*]} ###" + exit ${status} + fi +} + +rootPath="`pwd`" +localPath="$rootPath/local/" + +mkdir "$localPath" +assert_success cd "$localPath" + +assert_success git clone https://github.com/uikit/uikit.git +assert_success cd uikit +assert_success git checkout 4f815b6482fb93c98452c857b7a9a1450fecd660 # https://github.com/uikit/uikit/releases/tag/v3.11.1 +assert_success cd - + +assert_success cp -r "uikit/dist" "uikit/build" "uikit/tests" "$rootPath/client/uikit/" +assert_success cd "$rootPath" + +rm -rf "$localPath/uikit" diff --git a/server/build.gradle b/server/build.gradle index aa4064a4..969ada4b 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -27,10 +27,10 @@ dependencies { api project(":postssystem.targets.telegram.publication.server") - api "io.ktor:ktor-server-netty:$ktor_version" - api "io.ktor:ktor-websockets:$ktor_version" - api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version" - api "org.postgresql:postgresql:$psql_version" + api libs.ktor.server.netty + api libs.ktor.websockets + api libs.exposed.jdbs + api libs.postgresql } diff --git a/services/posts/common/build.gradle b/services/posts/common/build.gradle index 5ec15af9..f3f4e727 100644 --- a/services/posts/common/build.gradle +++ b/services/posts/common/build.gradle @@ -12,8 +12,8 @@ kotlin { dependencies { api project(":postssystem.features.common.common") api project(":postssystem.features.posts.common") - api "dev.inmo:micro_utils.repos.common:$microutils_version" - api "dev.inmo:micro_utils.repos.ktor.client:$microutils_version" + api libs.microutils.repos.common + api libs.microutils.repos.ktor.client } } } diff --git a/services/posts/server/build.gradle b/services/posts/server/build.gradle index e677259a..c2948aab 100644 --- a/services/posts/server/build.gradle +++ b/services/posts/server/build.gradle @@ -14,8 +14,8 @@ kotlin { api project(":postssystem.features.content.server") api project(":postssystem.features.posts.server") api project(":postssystem.features.users.server") - api "dev.inmo:micro_utils.common:$microutils_version" - api "org.jetbrains.kotlinx:kotlinx-serialization-properties:$kotlin_serialisation_core_version" + api libs.microutils.common + api libs.kotlin.serialization.properties } } } diff --git a/targets/telegram/publication/server/build.gradle b/targets/telegram/publication/server/build.gradle index 862f96d5..0e6a037c 100644 --- a/targets/telegram/publication/server/build.gradle +++ b/targets/telegram/publication/server/build.gradle @@ -15,7 +15,7 @@ kotlin { api project(":postssystem.features.content.binary.server") api project(":postssystem.features.content.text.server") - api "dev.inmo:tgbotapi:$tgbotapi_version" + api libs.tgbotapi } } }