diff --git a/packages/cli/src/cli-start.ts b/packages/cli/src/cli-start.ts new file mode 100644 index 0000000000..2e14b6bc17 --- /dev/null +++ b/packages/cli/src/cli-start.ts @@ -0,0 +1,37 @@ +import { cac } from 'cac' +import { version } from '../package.json' +import type { CliOptions } from './types' +import { build } from './index' + +export async function startCli(cwd = process.cwd(), argv = process.argv, options: CliOptions = {}) { + const cli = cac('unocss') + + cli + .command('[...patterns]', 'Glob patterns', { + ignoreOptionDefaultValue: true, + }) + .option('-o, --out-file ', 'Output file', { + default: cwd, + }) + .option('-c, --config [file]', 'Config file') + .option('-w, --watch', 'Watch for file changes') + .action(async (patterns: Array, flags) => { + Object.assign(options, { + cwd, + ...flags, + }) + + if (patterns) + options.patterns = patterns + + await build(options) + }) + + cli.help() + cli.version(version) + + // Parse CLI args without running the command to + // handle command errors globally + cli.parse(argv, { run: false }) + await cli.runMatchedCommand() +} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 7f7775e0bb..8eedb101b3 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,42 +1,4 @@ -import { cac } from 'cac' -import { version } from '../package.json' +import { startCli } from './cli-start' import { handleError } from './errors' -import type { CliOptions } from './types' -import { build } from './index' -const name = 'unocss' - -async function main(options: CliOptions = {}) { - const cli = cac(name) - - cli - .command('[...patterns]', 'Glob patterns', { - ignoreOptionDefaultValue: true, - }) - .option('-o, --out-file ', 'Output file', { - default: process.cwd(), - }) - .option('-c, --config [file]', 'Config file') - .option('-w, --watch', 'Watch for file changes') - .action(async (patterns: Array, flags) => { - Object.assign(options, { - ...flags, - }) - - if (patterns) - options.patterns = patterns - - await build(options) - }) - - cli.help() - - cli.version(version) - - // Parse CLI args without running the command to - // handle command errors globally - cli.parse(process.argv, { run: false }) - await cli.runMatchedCommand() -} - -main().catch(handleError) +startCli().catch(handleError) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c3a5ed92b9..3d45f98354 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -5,7 +5,6 @@ import consola from 'consola' import { cyan, dim, green } from 'colorette' import { debounce } from 'perfect-debounce' import { createGenerator, toArray } from '@unocss/core' -import type { UnoGenerator } from '@unocss/core' import { loadConfig } from '@unocss/config' import { version } from '../package.json' import { PrettyError, handleError } from './errors' @@ -13,29 +12,6 @@ import { defaultConfig } from './config' import type { CliOptions, ResolvedCliOptions } from './types' const name = 'unocss' -let uno: UnoGenerator - -const fileCache = new Map() - -const getAbsolutePath = (file: string) => resolve(process.cwd(), file) - -export async function generate(options: ResolvedCliOptions) { - const outFile = options.outFile ?? getAbsolutePath('uno.css') - const { css, matched } = await uno.generate([...fileCache].join('\n')) - - const dir = dirname(outFile) - if (!existsSync(dir)) - await fs.mkdir(dir, { recursive: true }) - await fs.writeFile(outFile, css, 'utf-8') - - if (!options.watch) { - consola.success( - `${[...matched].length} utilities generated to ${cyan( - relative(process.cwd(), outFile), - )}\n`, - ) - } -} export async function resolveOptions(options: CliOptions) { if (!options.patterns?.length) { @@ -48,19 +24,21 @@ export async function resolveOptions(options: CliOptions) { } export async function build(_options: CliOptions) { + const fileCache = new Map() + + const cwd = _options.cwd || process.cwd() const options = await resolveOptions(_options) - const { config, sources: configSources } = await loadConfig(process.cwd(), options.config) + const { config, sources: configSources } = await loadConfig(cwd, options.config) - uno = createGenerator( + const uno = createGenerator( config, defaultConfig, ) - const files = await fg(options.patterns) + const files = await fg(options.patterns, { cwd, absolute: true }) await Promise.all( files.map(async (file) => { - const absolutePath = getAbsolutePath(file) - fileCache.set(absolutePath, await fs.readFile(absolutePath, 'utf8')) + fileCache.set(file, await fs.readFile(file, 'utf8')) }), ) @@ -86,6 +64,7 @@ export async function build(_options: CliOptions) { ignoreInitial: true, ignorePermissionErrors: true, ignored, + cwd, }) if (configSources.length) @@ -99,7 +78,7 @@ export async function build(_options: CliOptions) { else { consola.log(`${green(type)} ${dim(file)}`) - const absolutePath = getAbsolutePath(file) + const absolutePath = resolve(cwd, file) if (type.startsWith('unlink')) fileCache.delete(absolutePath) else @@ -120,4 +99,22 @@ export async function build(_options: CliOptions) { await generate(options) startWatcher() + + async function generate(options: ResolvedCliOptions) { + const outFile = resolve(options.cwd || process.cwd(), options.outFile ?? 'uno.css') + const { css, matched } = await uno.generate([...fileCache].join('\n')) + + const dir = dirname(outFile) + if (!existsSync(dir)) + await fs.mkdir(dir, { recursive: true }) + await fs.writeFile(outFile, css, 'utf-8') + + if (!options.watch) { + consola.success( + `${[...matched].length} utilities generated to ${cyan( + relative(process.cwd(), outFile), + )}\n`, + ) + } + } } diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 5033af9bbf..373ee1a4e8 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -2,6 +2,7 @@ declare type MarkRequired = Exclude & Required> export interface CliOptions { + cwd?: string patterns?: Array outFile?: string watch?: boolean diff --git a/packages/inspector/client/auto-imports.d.ts b/packages/inspector/client/auto-imports.d.ts index bc5d936579..bee24c4fb5 100644 --- a/packages/inspector/client/auto-imports.d.ts +++ b/packages/inspector/client/auto-imports.d.ts @@ -148,6 +148,7 @@ declare global { const useGamepad: typeof import('@vueuse/core')['useGamepad'] const useGeolocation: typeof import('@vueuse/core')['useGeolocation'] const useIdle: typeof import('@vueuse/core')['useIdle'] + const useImage: typeof import('@vueuse/core')['useImage'] const useInfiniteScroll: typeof import('@vueuse/core')['useInfiniteScroll'] const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useInterval: typeof import('@vueuse/core')['useInterval'] diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 6e392801f6..a9ce4b78d1 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -1,6 +1,6 @@ { "publisher": "antfu", - "name": "unocss", + "name": "@unocss/vscode", "displayName": "UnoCSS", "version": "0.39.2", "private": true, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff775885f2..592c80bcfa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -162,7 +162,7 @@ importers: tsup: 6.1.2_typescript@4.7.4 typescript: 4.7.4 unbuild: 0.7.4 - unocss: link:packages/vscode + unocss: link:packages/unocss unplugin-auto-import: 0.8.8_jstnr3n6glzylrrm7j2wif3spq unplugin-vue-components: 0.19.6_hvpqbrwl6nbvljlgnqlmhfy5pu vite: 2.9.9 @@ -194,7 +194,7 @@ importers: local-pkg: 0.4.1 postcss: 8.4.14 tailwindcss: 3.0.24 - unocss: link:../packages/vscode + unocss: link:../packages/unocss vite: 2.9.9 vite-plugin-windicss: 1.8.4_vite@2.9.9 vue: 3.2.36 @@ -338,7 +338,7 @@ importers: '@unocss/reset': link:../reset '@unocss/vite': link:../vite '@unocss/webpack': link:../webpack - unocss: link:../vscode + unocss: link:../unocss devDependencies: '@nuxt/schema': 3.0.0-rc.3 @@ -1322,17 +1322,17 @@ packages: resolution: {integrity: sha512-S2x0zdLYLZ4/2lMnS6emSmFPJszPJf70Y4/8gpmkPb4TArEjPNaOTCIz7tKpj/Upo71fv7cfuwYw/X0F2UAntg==} engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0} dependencies: - '@nuxt/schema': 3.0.0-rc.4_rollup@2.75.6+vite@2.9.9 + '@nuxt/schema': 3.0.0-rc.3_rollup@2.75.6+vite@2.9.9 c12: 0.2.7 consola: 2.15.3 defu: 6.0.0 - globby: 13.1.2 + globby: 13.1.1 hash-sum: 2.0.0 ignore: 5.2.0 jiti: 1.13.0 - knitwork: 0.1.2 + knitwork: 0.1.1 lodash.template: 4.5.0 - mlly: 0.5.3 + mlly: 0.5.2 pathe: 0.2.0 pkg-types: 0.3.2 scule: 0.2.1 @@ -1487,6 +1487,27 @@ packages: - vite - webpack + /@nuxt/schema/3.0.0-rc.3_rollup@2.75.6+vite@2.9.9: + resolution: {integrity: sha512-vICmOpIk8SjVVUN+MadAMMF/MEXqPgY3jquSjSkmWdydtMOBoxrBpl+5nkpJCtsCq5KJAK8WI+9SCNIkEASCgw==} + engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0} + dependencies: + c12: 0.2.7 + create-require: 1.1.1 + defu: 6.0.0 + jiti: 1.13.0 + pathe: 0.3.0 + postcss-import-resolver: 2.0.0 + scule: 0.2.1 + std-env: 3.1.1 + ufo: 0.8.4 + unimport: 0.1.9_rollup@2.75.6+vite@2.9.9 + transitivePeerDependencies: + - esbuild + - rollup + - vite + - webpack + dev: true + /@nuxt/schema/3.0.0-rc.3_vmgv3jqceeji2auh6qcrxzlgbq: resolution: {integrity: sha512-vICmOpIk8SjVVUN+MadAMMF/MEXqPgY3jquSjSkmWdydtMOBoxrBpl+5nkpJCtsCq5KJAK8WI+9SCNIkEASCgw==} engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0} @@ -1588,7 +1609,7 @@ packages: dependencies: '@nuxt/kit': 3.0.0-rc.1_rollup@2.75.6+vite@2.9.9 chalk: 5.0.1 - ci-info: 3.3.2 + ci-info: 3.3.1 consola: 2.15.3 create-require: 1.1.1 defu: 6.0.0 @@ -1601,7 +1622,7 @@ packages: jiti: 1.13.0 mri: 1.2.0 nanoid: 3.3.4 - node-fetch: 3.2.6 + node-fetch: 3.2.4 ohmyfetch: 0.4.18 parse-git-config: 3.0.0 rc9: 1.2.2 @@ -1776,7 +1797,7 @@ packages: glob: 7.2.3 is-reference: 1.2.1 magic-string: 0.25.9 - resolve: 1.22.1 + resolve: 1.22.0 rollup: 2.75.6 dev: true @@ -1831,7 +1852,7 @@ packages: deepmerge: 4.2.2 is-builtin-module: 3.1.0 is-module: 1.0.0 - resolve: 1.22.0 + resolve: 1.22.1 rollup: 2.74.1 dev: true @@ -7302,15 +7323,6 @@ packages: formdata-polyfill: 4.0.10 dev: true - /node-fetch/3.2.6: - resolution: {integrity: sha512-LAy/HZnLADOVkVPubaxHDft29booGglPFDr2Hw0J1AercRh01UiVFm++KMDnJeH9sHgNB4hsXPii7Sgym/sTbw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - data-uri-to-buffer: 4.0.0 - fetch-blob: 3.1.5 - formdata-polyfill: 4.0.10 - dev: true - /node-forge/1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -8894,7 +8906,7 @@ packages: jest-worker: 26.6.2 rollup: 2.75.6 serialize-javascript: 4.0.0 - terser: 5.14.1 + terser: 5.13.1 dev: true /rollup-plugin-visualizer/5.6.0_rollup@2.74.1: @@ -8921,7 +8933,7 @@ packages: nanoid: 3.3.4 open: 8.4.0 rollup: 2.75.6 - source-map: 0.7.4 + source-map: 0.7.3 yargs: 17.5.1 dev: true @@ -10007,10 +10019,10 @@ packages: dependencies: '@rollup/pluginutils': 4.2.1 escape-string-regexp: 5.0.0 - globby: 13.1.2 + globby: 13.1.1 local-pkg: 0.4.1 magic-string: 0.26.2 - mlly: 0.5.3 + mlly: 0.5.2 pathe: 0.3.0 scule: 0.2.1 unplugin: 0.6.3_rollup@2.75.6+vite@2.9.9 diff --git a/test/cli.test.ts b/test/cli.test.ts index c0124bbc69..b7c10edd82 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,7 +1,7 @@ import { resolve } from 'path' import fs from 'fs-extra' import { afterAll, beforeAll, describe, expect, it } from 'vitest' -import { execa } from 'execa' +import { startCli } from '../packages/cli/src/cli-start' export const tempDir = resolve('.temp') export const cli = resolve(__dirname, '../packages/cli/src/cli.ts') @@ -23,6 +23,20 @@ describe('cli', () => { expect(output).toMatchSnapshot() }) + it('supports unocss.config.js', async () => { + const { output } = await runCli({ + 'views/index.html': '
', + 'unocss.config.js': ` +import { defineConfig } from 'unocss' +export default defineConfig({ + shortcuts: [{ box: 'max-w-7xl mx-auto bg-gray-100 rounded-md shadow-sm p-4' }] +}) + `.trim(), + }) + + expect(output).toMatchSnapshot() + }) + it('uno.css exclude initialized class after changing file', async () => { const fileName = 'views/index.html' const initializedContent = '
' @@ -30,58 +44,25 @@ describe('cli', () => { const testDir = getTestDir() const absolutePathOfFile = resolve(testDir, fileName) await fs.outputFile(absolutePathOfFile, initializedContent) - const subProcess = runAsyncChildProcess(testDir, './views/**/*', '-w') - const { stdout } = subProcess - const { waiting, $resolve } = createWaiting() - stdout!.on('data', (data) => { - if (data.toString().includes('[info] Watching for changes')) - $resolve() - }) - await waiting - await sleep() + runAsyncChildProcess(testDir, './views/**/*', '-w') + const outputPath = resolve(testDir, 'uno.css') + for (let i = 50; i >= 0; i--) { + await sleep(50) + if (fs.existsSync(outputPath)) + break + } await fs.writeFile(absolutePathOfFile, changedContent) // polling until update - for (let i = 20; i >= 0; i--) { + for (let i = 50; i >= 0; i--) { await sleep(50) const output = await readUnocssFile(testDir) if (i === 0 || output.includes('.bg-red')) expect(output).toContain('.bg-red') } - subProcess.cancel() - }) - - it('supports unocss.config.js', async () => { - const { output } = await runCli({ - 'views/index.html': '
', - 'unocss.config.js': ` -import { defineConfig } from 'unocss' -export default defineConfig({ - shortcuts: [{ box: 'max-w-7xl mx-auto bg-gray-100 rounded-md shadow-sm p-4' }] -}) - `.trim(), - }) - - expect(output).toMatchSnapshot() }) }) // ----- Utils ----- - -function createWaiting() { - const loop = () => { } - let $resolve = loop - let $reject = loop - const waiting = new Promise((resolve, reject) => { - $resolve = resolve - $reject = reject - }) - return { - waiting, - $resolve, - $reject, - } -} - function sleep(time = 300) { return new Promise((resolve) => { setTimeout(() => { @@ -103,10 +84,7 @@ function initOutputFiles(testDir: string, files: Record) { } function runAsyncChildProcess(cwd: string, ...args: string[]) { - return execa('npx', ['esno', cli, ...args], { - cwd, - // stdio: 'inherit', - }) + return startCli(cwd, ['', '', ...args]) } function readUnocssFile(testDir: string) { @@ -117,17 +95,11 @@ async function runCli(files: Record) { const testDir = getTestDir() await initOutputFiles(testDir, files) - - const { exitCode, stdout, stderr } = await runAsyncChildProcess(testDir, 'views/**/*') - - const logs = stdout + stderr - if (exitCode !== 0) - throw new Error(logs) + await runAsyncChildProcess(testDir, 'views/**/*') const output = await readUnocssFile(testDir) return { output, - logs, } }