From b55580476cae7222edc3be4ab1ace971896e5b76 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 4 Oct 2024 14:26:46 +0200 Subject: [PATCH 1/5] Ensure upgrade tool has access to a JS config --- .../src/codemods/migrate-at-apply.test.ts | 29 +++-- .../src/codemods/migrate-at-apply.ts | 14 +-- .../migrate-tailwind-directives.test.ts | 2 +- .../codemods/migrate-tailwind-directives.ts | 2 +- .../@tailwindcss-upgrade/src/index.test.ts | 19 +++- packages/@tailwindcss-upgrade/src/index.ts | 37 ++----- packages/@tailwindcss-upgrade/src/migrate.ts | 6 +- .../src/template/parseConfig.ts | 69 ------------ .../src/template/prepareConfig.ts | 101 ++++++++++++++++++ 9 files changed, 152 insertions(+), 127 deletions(-) delete mode 100644 packages/@tailwindcss-upgrade/src/template/parseConfig.ts create mode 100644 packages/@tailwindcss-upgrade/src/template/prepareConfig.ts diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts index df6b35d58ae9..7e8428b38a82 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.test.ts @@ -1,21 +1,34 @@ import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import postcss from 'postcss' +import type { Config } from 'tailwindcss' import { expect, it } from 'vitest' import { migrateAtApply } from './migrate-at-apply' const css = dedent -function migrateWithoutConfig(input: string) { +async function migrate(input: string, config: Config = {}) { + let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { base: __dirname }, + ) + return postcss() - .use(migrateAtApply()) + .use( + migrateAtApply({ + designSystem, + userConfig: config, + }), + ) .process(input, { from: expect.getState().testPath }) .then((result) => result.css) } it('should not migrate `@apply`, when there are no issues', async () => { expect( - await migrateWithoutConfig(css` + await migrate(css` .foo { @apply flex flex-col items-center; } @@ -29,7 +42,7 @@ it('should not migrate `@apply`, when there are no issues', async () => { it('should append `!` to each utility, when using `!important`', async () => { expect( - await migrateWithoutConfig(css` + await migrate(css` .foo { @apply flex flex-col !important; } @@ -44,7 +57,7 @@ it('should append `!` to each utility, when using `!important`', async () => { // TODO: Handle SCSS syntax it.skip('should append `!` to each utility, when using `#{!important}`', async () => { expect( - await migrateWithoutConfig(css` + await migrate(css` .foo { @apply flex flex-col #{!important}; } @@ -58,7 +71,7 @@ it.skip('should append `!` to each utility, when using `#{!important}`', async ( it('should move the legacy `!` prefix, to the new `!` postfix notation', async () => { expect( - await migrateWithoutConfig(css` + await migrate(css` .foo { @apply !flex flex-col! hover:!items-start items-center; } @@ -71,7 +84,7 @@ it('should move the legacy `!` prefix, to the new `!` postfix notation', async ( }) it('should apply all candidate migration when migrating with a config', async () => { - async function migrateWithConfig(input: string) { + async function migrateWithPrefix(input: string) { return postcss() .use( migrateAtApply({ @@ -91,7 +104,7 @@ it('should apply all candidate migration when migrating with a config', async () } expect( - await migrateWithConfig(css` + await migrateWithPrefix(css` .foo { @apply !tw_flex [color:--my-color] tw_bg-gradient-to-t; } diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts index 487039f48b1b..988203b2f5c5 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-at-apply.ts @@ -7,7 +7,10 @@ import { migrateCandidate } from '../template/migrate' export function migrateAtApply({ designSystem, userConfig, -}: { designSystem?: DesignSystem; userConfig?: Config } = {}): Plugin { +}: { + designSystem: DesignSystem + userConfig: Config +}): Plugin { function migrate(atRule: AtRule) { let utilities = atRule.params.split(/(\s+)/) let important = @@ -27,20 +30,13 @@ export function migrateAtApply({ utility += '!' } - // Migrate the important modifier to the end of the utility - if (utility[0] === '!') { - utility = `${utility.slice(1)}!` - } - // Reconstruct the utility with the variants return [...variants, utility].join(':') }) // If we have a valid designSystem and config setup, we can run all // candidate migrations on each utility - if (designSystem && userConfig) { - params = params.map((param) => migrateCandidate(designSystem, userConfig, param)) - } + params = params.map((param) => migrateCandidate(designSystem, userConfig, param)) atRule.params = params.join('').trim() } diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts index 63b1f9334c14..ba2d9ea9dc3d 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.test.ts @@ -6,7 +6,7 @@ import { migrateTailwindDirectives } from './migrate-tailwind-directives' const css = dedent -function migrate(input: string, options: { newPrefix?: string } = {}) { +function migrate(input: string, options: { newPrefix: string | null } = { newPrefix: null }) { return postcss() .use(migrateTailwindDirectives(options)) .use(formatNodes()) diff --git a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts index 76449c309e86..0f714301713b 100644 --- a/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts +++ b/packages/@tailwindcss-upgrade/src/codemods/migrate-tailwind-directives.ts @@ -2,7 +2,7 @@ import { AtRule, type ChildNode, type Plugin, type Root } from 'postcss' const DEFAULT_LAYER_ORDER = ['theme', 'base', 'components', 'utilities'] -export function migrateTailwindDirectives(options: { newPrefix?: string }): Plugin { +export function migrateTailwindDirectives(options: { newPrefix: string | null }): Plugin { let prefixParams = options.newPrefix ? ` prefix(${options.newPrefix})` : '' function migrate(root: Root) { diff --git a/packages/@tailwindcss-upgrade/src/index.test.ts b/packages/@tailwindcss-upgrade/src/index.test.ts index 4dbfaf46f412..581b9e3d6d9c 100644 --- a/packages/@tailwindcss-upgrade/src/index.test.ts +++ b/packages/@tailwindcss-upgrade/src/index.test.ts @@ -1,9 +1,18 @@ +import { __unstable__loadDesignSystem } from '@tailwindcss/node' import dedent from 'dedent' import { expect, it } from 'vitest' import { migrateContents } from './migrate' const css = dedent +let designSystem = await __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { base: __dirname }, +) +let config = { designSystem, userConfig: {}, newPrefix: null } + it('should print the input as-is', async () => { expect( await migrateContents( @@ -15,7 +24,7 @@ it('should print the input as-is', async () => { /* below */ } `, - {}, + config, expect.getState().testPath, ), ).toMatchInlineSnapshot(` @@ -66,7 +75,7 @@ it('should migrate a stylesheet', async () => { } } `, - {}, + config, ), ).toMatchInlineSnapshot(` "@import 'tailwindcss'; @@ -116,7 +125,7 @@ it('should migrate a stylesheet (with imports)', async () => { @import 'tailwindcss/utilities'; @import './my-utilities.css'; `, - {}, + config, ), ).toMatchInlineSnapshot(` "@import 'tailwindcss'; @@ -140,7 +149,7 @@ it('should migrate a stylesheet (with preceding rules that should be wrapped in @tailwind components; @tailwind utilities; `, - {}, + config, ), ).toMatchInlineSnapshot(` "@charset "UTF-8"; @@ -169,7 +178,7 @@ it('should keep CSS as-is before existing `@layer` at-rules', async () => { } } `, - {}, + config, ), ).toMatchInlineSnapshot(` ".foo { diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index 573c13f48cd6..89bb763e327a 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -2,12 +2,10 @@ import { globby } from 'globby' import path from 'node:path' -import type { Config } from 'tailwindcss' -import type { DesignSystem } from '../../tailwindcss/src/design-system' import { help } from './commands/help' import { migrate as migrateStylesheet } from './migrate' import { migrate as migrateTemplate } from './template/migrate' -import { parseConfig } from './template/parseConfig' +import { prepareConfig } from './template/prepareConfig' import { args, type Arg } from './utils/args' import { isRepoDirty } from './utils/git' import { eprintln, error, header, highlight, info, success } from './utils/renderer' @@ -42,28 +40,15 @@ async function run() { } } - let parsedConfig: { - designSystem: DesignSystem - globs: { pattern: string; base: string }[] - userConfig: Config - newPrefix: string | null - } | null = null - if (flags['--config']) { - try { - parsedConfig = await parseConfig(flags['--config'], { base: process.cwd() }) - } catch (e: any) { - error(`Failed to parse the configuration file: ${e.message}`) - process.exit(1) - } - } + let config = await prepareConfig(flags['--config'], { base: process.cwd() }) - if (parsedConfig) { + { // Template migrations info('Migrating templates using the provided configuration file.') let set = new Set() - for (let { pattern, base } of parsedConfig.globs) { + for (let { pattern, base } of config.globs) { let files = await globby([pattern], { absolute: true, gitignore: true, @@ -80,9 +65,7 @@ async function run() { // Migrate each file await Promise.allSettled( - files.map((file) => - migrateTemplate(parsedConfig.designSystem, parsedConfig.userConfig, file), - ), + files.map((file) => migrateTemplate(config.designSystem, config.userConfig, file)), ) success('Template migration complete.') @@ -110,15 +93,7 @@ async function run() { files = files.filter((file) => file.endsWith('.css')) // Migrate each file - await Promise.allSettled( - files.map((file) => - migrateStylesheet(file, { - newPrefix: parsedConfig?.newPrefix ?? undefined, - designSystem: parsedConfig?.designSystem, - userConfig: parsedConfig?.userConfig, - }), - ), - ) + await Promise.allSettled(files.map((file) => migrateStylesheet(file, config))) success('Stylesheet migration complete.') } diff --git a/packages/@tailwindcss-upgrade/src/migrate.ts b/packages/@tailwindcss-upgrade/src/migrate.ts index 680e7c67b04c..76990936b549 100644 --- a/packages/@tailwindcss-upgrade/src/migrate.ts +++ b/packages/@tailwindcss-upgrade/src/migrate.ts @@ -10,9 +10,9 @@ import { migrateMissingLayers } from './codemods/migrate-missing-layers' import { migrateTailwindDirectives } from './codemods/migrate-tailwind-directives' export interface MigrateOptions { - newPrefix?: string - designSystem?: DesignSystem - userConfig?: Config + newPrefix: string | null + designSystem: DesignSystem + userConfig: Config } export async function migrateContents(contents: string, options: MigrateOptions, file?: string) { diff --git a/packages/@tailwindcss-upgrade/src/template/parseConfig.ts b/packages/@tailwindcss-upgrade/src/template/parseConfig.ts deleted file mode 100644 index b365ca3883a1..000000000000 --- a/packages/@tailwindcss-upgrade/src/template/parseConfig.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { __unstable__loadDesignSystem, compile } from '@tailwindcss/node' -import path from 'node:path' -import { dirname } from 'path' -import type { Config } from 'tailwindcss' -import { fileURLToPath } from 'url' -import { loadModule } from '../../../@tailwindcss-node/src/compile' -import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' -import type { DesignSystem } from '../../../tailwindcss/src/design-system' -import { migratePrefix } from './codemods/prefix' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) - -let css = String.raw - -export async function parseConfig( - configPath: string, - options: { base: string }, -): Promise<{ - designSystem: DesignSystem - globs: { base: string; pattern: string }[] - userConfig: Config - - newPrefix: string | null -}> { - // We create a relative path from the current file to the config file. This is - // required so that the base for Tailwind CSS can bet inside the - // @tailwindcss-upgrade package and we can require `tailwindcss` properly. - let fullConfigPath = path.resolve(options.base, configPath) - let fullFilePath = path.resolve(__dirname) - let relative = path.relative(fullFilePath, fullConfigPath) - // If the path points to a file in the same directory, `path.relative` will - // remove the leading `./` and we need to add it back in order to still - // consider the path relative - if (!relative.startsWith('.')) { - relative = './' + relative - } - - let userConfig = await createResolvedUserConfig(fullConfigPath) - - let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null - let input = css` - @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''}; - @config './${relative}'; - ` - - let [compiler, designSystem] = await Promise.all([ - compile(input, { base: __dirname, onDependency: () => {} }), - __unstable__loadDesignSystem(input, { base: __dirname }), - ]) - - return { designSystem, globs: compiler.globs, userConfig, newPrefix } -} - -async function createResolvedUserConfig(fullConfigPath: string): Promise { - let [noopDesignSystem, unresolvedUserConfig] = await Promise.all([ - __unstable__loadDesignSystem( - css` - @import 'tailwindcss'; - `, - { base: __dirname }, - ), - loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config, - ]) - - return resolveConfig(noopDesignSystem, [ - { base: dirname(fullConfigPath), config: unresolvedUserConfig }, - ]) as any -} diff --git a/packages/@tailwindcss-upgrade/src/template/prepareConfig.ts b/packages/@tailwindcss-upgrade/src/template/prepareConfig.ts new file mode 100644 index 000000000000..0eb70a68d0ce --- /dev/null +++ b/packages/@tailwindcss-upgrade/src/template/prepareConfig.ts @@ -0,0 +1,101 @@ +import { __unstable__loadDesignSystem, compile } from '@tailwindcss/node' +import fs from 'node:fs/promises' +import path from 'node:path' +import { dirname } from 'path' +import type { Config } from 'tailwindcss' +import { fileURLToPath } from 'url' +import { loadModule } from '../../../@tailwindcss-node/src/compile' +import { resolveConfig } from '../../../tailwindcss/src/compat/config/resolve-config' +import type { DesignSystem } from '../../../tailwindcss/src/design-system' +import { error } from '../utils/renderer' +import { migratePrefix } from './codemods/prefix' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +let css = String.raw + +export async function prepareConfig( + configPath: string | null, + options: { base: string }, +): Promise<{ + designSystem: DesignSystem + globs: { base: string; pattern: string }[] + userConfig: Config + + newPrefix: string | null +}> { + try { + if (configPath === null) { + configPath = await detectConfigPath(options.base) + } + + // We create a relative path from the current file to the config file. This is + // required so that the base for Tailwind CSS can bet inside the + // @tailwindcss-upgrade package and we can require `tailwindcss` properly. + let fullConfigPath = path.resolve(options.base, configPath) + let fullFilePath = path.resolve(__dirname) + let relative = path.relative(fullFilePath, fullConfigPath) + // If the path points to a file in the same directory, `path.relative` will + // remove the leading `./` and we need to add it back in order to still + // consider the path relative + if (!relative.startsWith('.')) { + relative = './' + relative + } + + let userConfig = await createResolvedUserConfig(fullConfigPath) + + let newPrefix = userConfig.prefix ? migratePrefix(userConfig.prefix) : null + let input = css` + @import 'tailwindcss' ${newPrefix ? `prefix(${newPrefix})` : ''}; + @config './${relative}'; + ` + + let [compiler, designSystem] = await Promise.all([ + compile(input, { base: __dirname, onDependency: () => {} }), + __unstable__loadDesignSystem(input, { base: __dirname }), + ]) + + return { designSystem, globs: compiler.globs, userConfig, newPrefix } + } catch (e: any) { + error('Could not load the configuration file: ' + e.message) + process.exit(1) + } +} + +async function createResolvedUserConfig(fullConfigPath: string): Promise { + let [noopDesignSystem, unresolvedUserConfig] = await Promise.all([ + __unstable__loadDesignSystem( + css` + @import 'tailwindcss'; + `, + { base: __dirname }, + ), + loadModule(fullConfigPath, __dirname, () => {}).then((result) => result.module) as Config, + ]) + + return resolveConfig(noopDesignSystem, [ + { base: dirname(fullConfigPath), config: unresolvedUserConfig }, + ]) as any +} + +const DEFAULT_CONFIG_FILES = [ + './tailwind.config.js', + './tailwind.config.cjs', + './tailwind.config.mjs', + './tailwind.config.ts', + './tailwind.config.cts', + './tailwind.config.mts', +] +async function detectConfigPath(base: string) { + for (let file of DEFAULT_CONFIG_FILES) { + let fullPath = path.resolve(base, file) + try { + await fs.access(fullPath) + return file + } catch {} + } + throw new Error( + 'No configuration file found. Please provide a path to the Tailwind CSS v3 config file via the `--config` option.', + ) +} From d97ff25aabac59a5c7a9dcbc39dcb750a54b9d3f Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 4 Oct 2024 14:27:11 +0200 Subject: [PATCH 2/5] Don't require a config option --- integrations/upgrade/index.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 881f40010a78..82f3ab2e5bc3 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -29,7 +29,7 @@ test( }, }, async ({ exec, fs }) => { - await exec('npx @tailwindcss/upgrade -c tailwind.config.js') + await exec('npx @tailwindcss/upgrade') await fs.expectFileToContain( 'src/index.html', @@ -77,7 +77,7 @@ test( }, }, async ({ exec, fs }) => { - await exec('npx @tailwindcss/upgrade -c tailwind.config.js') + await exec('npx @tailwindcss/upgrade') await fs.expectFileToContain( 'src/index.html', From c3dae0bfafdd067fbb6aec6a1bc4ac848351f4fc Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Fri, 4 Oct 2024 15:59:05 +0200 Subject: [PATCH 3/5] Add config files to other integration tests --- integrations/upgrade/index.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integrations/upgrade/index.test.ts b/integrations/upgrade/index.test.ts index 82f3ab2e5bc3..c348dc4c4a04 100644 --- a/integrations/upgrade/index.test.ts +++ b/integrations/upgrade/index.test.ts @@ -111,6 +111,7 @@ test( } } `, + 'tailwind.config.js': js`module.exports = {}`, 'src/index.css': css` @import 'tailwindcss'; @@ -162,6 +163,7 @@ test( } } `, + 'tailwind.config.js': js`module.exports = {}`, 'src/index.css': css` @tailwind base; @@ -218,6 +220,7 @@ test( } } `, + 'tailwind.config.js': js`module.exports = {}`, 'src/index.css': css` @import 'tailwindcss'; From c012f15fbfb74ee99b9f27e909993bc8424231d8 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 13:31:44 +0200 Subject: [PATCH 4/5] Add change log --- CHANGELOG.md | 3 ++- .../src/template/{prepareConfig.ts => prepare-config.ts} | 3 ++- packages/tailwindcss/src/at-import.test.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) rename packages/@tailwindcss-upgrade/src/template/{prepareConfig.ts => prepare-config.ts} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b17e38d735c..6074ecce90b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add support for `tailwindcss/colors.js`, `tailwindcss/defaultTheme.js`, and `tailwindcss/plugin.js` exports ([#14595](https://github.com/tailwindlabs/tailwindcss/pull/14595)) -- Support `keyframes` in JS config file themes ([14594](https://github.com/tailwindlabs/tailwindcss/pull/14594)) +- Support `keyframes` in JS config file themes ([#14594](https://github.com/tailwindlabs/tailwindcss/pull/14594)) +- _Experimental_: The upgrade tool now automatically discovers your JavaScript config ([#14597](https://github.com/tailwindlabs/tailwindcss/pull/14597)) ### Fixed diff --git a/packages/@tailwindcss-upgrade/src/template/prepareConfig.ts b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts similarity index 99% rename from packages/@tailwindcss-upgrade/src/template/prepareConfig.ts rename to packages/@tailwindcss-upgrade/src/template/prepare-config.ts index 0eb70a68d0ce..c9667f455352 100644 --- a/packages/@tailwindcss-upgrade/src/template/prepareConfig.ts +++ b/packages/@tailwindcss-upgrade/src/template/prepare-config.ts @@ -13,7 +13,7 @@ import { migratePrefix } from './codemods/prefix' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) -let css = String.raw +const css = String.raw export async function prepareConfig( configPath: string | null, @@ -36,6 +36,7 @@ export async function prepareConfig( let fullConfigPath = path.resolve(options.base, configPath) let fullFilePath = path.resolve(__dirname) let relative = path.relative(fullFilePath, fullConfigPath) + // If the path points to a file in the same directory, `path.relative` will // remove the leading `./` and we need to add it back in order to still // consider the path relative diff --git a/packages/tailwindcss/src/at-import.test.ts b/packages/tailwindcss/src/at-import.test.ts index bbd5e6863f26..deedd554fedb 100644 --- a/packages/tailwindcss/src/at-import.test.ts +++ b/packages/tailwindcss/src/at-import.test.ts @@ -4,7 +4,7 @@ import { compile, type Config } from './index' import plugin from './plugin' import { optimizeCss } from './test-utils/run' -let css = String.raw +const css = String.raw async function run( css: string, From 84480ecaeef70b8203db39e993b0d9b81644c9f6 Mon Sep 17 00:00:00 2001 From: Philipp Spiess Date: Mon, 7 Oct 2024 14:56:23 +0200 Subject: [PATCH 5/5] Update packages/@tailwindcss-upgrade/src/index.ts Co-authored-by: Robin Malfait --- packages/@tailwindcss-upgrade/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@tailwindcss-upgrade/src/index.ts b/packages/@tailwindcss-upgrade/src/index.ts index 89bb763e327a..2d323a24de0b 100644 --- a/packages/@tailwindcss-upgrade/src/index.ts +++ b/packages/@tailwindcss-upgrade/src/index.ts @@ -5,7 +5,7 @@ import path from 'node:path' import { help } from './commands/help' import { migrate as migrateStylesheet } from './migrate' import { migrate as migrateTemplate } from './template/migrate' -import { prepareConfig } from './template/prepareConfig' +import { prepareConfig } from './template/prepare-config' import { args, type Arg } from './utils/args' import { isRepoDirty } from './utils/git' import { eprintln, error, header, highlight, info, success } from './utils/renderer'