Skip to content

Commit e2a005b

Browse files
feat: workspace generator (#1795)
* feat: workspace generator * chore: fix * chore: fix * chore: update tsconfig * chore: update package json * chore: adjust test script * feat: use vite only for theme and crepe * chore: adjust export * chore: fix lock * chore: optimize changelog script * chore: remove unused lib * chore: adjust logger * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e997d05 commit e2a005b

File tree

99 files changed

+1046
-580
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

99 files changed

+1046
-580
lines changed

Diff for: dev/package.json

+17-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,24 @@
1010
"./vite": {
1111
"import": "./lib/vite.js",
1212
"types": "./lib/vite.d.ts"
13+
},
14+
"./logger": {
15+
"import": "./lib/logger.js",
16+
"types": "./lib/logger.d.ts"
17+
},
18+
"./exec": {
19+
"import": "./lib/exec.js",
20+
"types": "./lib/exec.d.ts"
21+
},
22+
"./generate": {
23+
"import": "./lib/generate.js",
24+
"types": "./lib/generate.d.ts"
1325
}
1426
},
15-
"scripts": {
16-
"build": "rimraf './lib' && tsc"
27+
"dependencies": {
28+
"@types/lodash-es": "^4.17.12",
29+
"chalk": "^5.4.1",
30+
"jsonc-parser": "^3.3.1",
31+
"lodash-es": "^4.17.21"
1732
}
1833
}

Diff for: dev/src/exec.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { execSync } from 'node:child_process'
2+
3+
import { Logger } from './logger'
4+
5+
export function exec(
6+
tag: string,
7+
cmd: string,
8+
{ silent }: { silent: boolean } = { silent: false }
9+
): string {
10+
const logger = new Logger(tag)
11+
!silent && logger.info(cmd)
12+
const result = execSync(cmd, { encoding: 'utf8' }).trim()
13+
!silent && logger.log(result)
14+
return result
15+
}

Diff for: dev/src/generate.ts

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { applyEdits, modify } from 'jsonc-parser'
2+
import { Workspace } from './workspace'
3+
import type { Path } from './path'
4+
import { type BuiltInParserName, format } from 'prettier'
5+
import { readFileSync, writeFileSync } from 'node:fs'
6+
import { Logger } from './logger'
7+
import type { Package } from './package'
8+
9+
export function generateTsConfig() {
10+
const generator = new Generator()
11+
generator.run()
12+
}
13+
14+
export class Generator {
15+
workspace: Workspace
16+
logger: Logger
17+
18+
constructor() {
19+
this.workspace = new Workspace()
20+
this.workspace.join('tsconfig.json')
21+
this.logger = new Logger('TS Config')
22+
}
23+
24+
async run() {
25+
this.logger.info('Generating workspace files')
26+
await this.generateWorkspaceFiles()
27+
this.logger.info('Workspace files generated')
28+
}
29+
30+
generateWorkspaceFiles = async () => {
31+
const filesToGenerate: [
32+
Path,
33+
(prev: string) => string,
34+
BuiltInParserName?,
35+
][] = [
36+
[this.workspace.join('tsconfig.json'), this.genProjectTsConfig, 'json'],
37+
...this.workspace.packages
38+
.filter((p) => p.isTsProject)
39+
.map(
40+
(p) =>
41+
[
42+
p.join('tsconfig.json'),
43+
this.genPackageTsConfig.bind(this, p),
44+
'json',
45+
] as any
46+
),
47+
]
48+
49+
for (const [path, content, formatter] of filesToGenerate) {
50+
this.logger.info(`Generating: ${path}`)
51+
const previous = readFileSync(path.value, 'utf-8')
52+
let file = content(previous)
53+
if (formatter) {
54+
file = await this.format(file, formatter)
55+
}
56+
writeFileSync(path.value, file)
57+
}
58+
}
59+
60+
format = (content: string, parser: BuiltInParserName) => {
61+
const config = JSON.parse(
62+
readFileSync(this.workspace.join('.prettierrc').value, 'utf-8')
63+
)
64+
return format(content, { parser, ...config })
65+
}
66+
67+
genProjectTsConfig = (prev: string) => {
68+
return applyEdits(
69+
prev,
70+
modify(
71+
prev,
72+
['references'],
73+
this.workspace.packages
74+
.filter((p) => p.isTsProject)
75+
.map((p) => ({ path: p.path.relativePath })),
76+
{}
77+
)
78+
)
79+
}
80+
81+
genPackageTsConfig = (pkg: Package, prev: string) => {
82+
return applyEdits(
83+
prev,
84+
modify(
85+
prev,
86+
['references'],
87+
pkg.deps
88+
.filter((p) => p.isTsProject)
89+
.map((d) => ({ path: pkg.path.relative(d.path.value) })),
90+
{}
91+
)
92+
)
93+
}
94+
}

Diff for: dev/src/logger.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import chalk from 'chalk'
2+
import { identity } from 'lodash-es'
3+
4+
export const newLineSeparator = /\r\n|[\n\r\x85\u2028\u2029]/g
5+
6+
interface StringLike {
7+
toString: () => string
8+
}
9+
10+
export class Logger {
11+
log = this.getLineLogger(console.log.bind(console))
12+
info = this.getLineLogger(console.info.bind(console), chalk.blue)
13+
warn = this.getLineLogger(
14+
console.warn.bind(console),
15+
chalk.bgHex('#322b08').hex('#fadea6')
16+
)
17+
error = this.getLineLogger(
18+
console.error.bind(console),
19+
chalk.bgHex('#250201').hex('#ef8784')
20+
)
21+
success = this.getLineLogger(console.log.bind(console), chalk.green)
22+
23+
constructor(private readonly tag: string = '') {}
24+
25+
getLineLogger(
26+
logLine: (...line: string[]) => void,
27+
color: (...text: string[]) => string = identity
28+
) {
29+
return (...args: StringLike[]) => {
30+
args.forEach((arg) => {
31+
arg
32+
.toString()
33+
.split(newLineSeparator)
34+
.forEach((line) => {
35+
if (line.length !== 0) {
36+
if (this.tag) {
37+
logLine(color(`[${this.tag}] ${line}`))
38+
} else {
39+
logLine(color(line))
40+
}
41+
}
42+
})
43+
})
44+
}
45+
}
46+
}

Diff for: dev/src/package.ts

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { readFileSync } from 'node:fs'
2+
import { parse } from 'node:path'
3+
4+
import { Path } from './path'
5+
import type { CommonPackageJsonContent } from './types'
6+
import type { Workspace } from './workspace'
7+
import type { PackageItem } from './pnpm'
8+
import { pnpmList } from './pnpm'
9+
10+
export function readPackageJson(path: Path): CommonPackageJsonContent {
11+
const content = readFileSync(path.join('package.json').toString(), 'utf-8')
12+
13+
return JSON.parse(content)
14+
}
15+
16+
export class Package {
17+
readonly name: string
18+
readonly packageJson: CommonPackageJsonContent
19+
readonly dirname: string
20+
readonly path: Path
21+
readonly srcPath: Path
22+
readonly nodeModulesPath: Path
23+
readonly libPath: Path
24+
readonly distPath: Path
25+
readonly version: string
26+
readonly isTsProject: boolean
27+
readonly workspaceDependencies: string[]
28+
deps: Package[] = []
29+
private _workspace: Workspace | null = null
30+
31+
get entry() {
32+
return this.packageJson.main || this.packageJson.exports?.['.']
33+
}
34+
35+
get dependencies() {
36+
return this.packageJson.dependencies || {}
37+
}
38+
39+
get devDependencies() {
40+
return this.packageJson.devDependencies || {}
41+
}
42+
43+
get workspace() {
44+
if (!this._workspace) {
45+
throw new Error('Workspace is not initialized')
46+
}
47+
48+
return this._workspace
49+
}
50+
51+
set workspace(workspace: Workspace) {
52+
this._workspace = workspace
53+
}
54+
55+
constructor(name: string, meta?: PackageItem) {
56+
this.name = name
57+
meta ??= pnpmList().find((item) => item.name === name)!
58+
59+
// parse paths
60+
this.path = new Path(meta.path)
61+
this.dirname = parse(meta.path).name
62+
this.srcPath = this.path.join('src')
63+
this.libPath = this.path.join('lib')
64+
this.distPath = this.path.join('dist')
65+
this.nodeModulesPath = this.path.join('node_modules')
66+
67+
// parse workspace
68+
const packageJson = readPackageJson(this.path)
69+
this.packageJson = packageJson
70+
this.version = packageJson.version
71+
this.workspaceDependencies = Object.keys(
72+
packageJson.dependencies ?? {}
73+
).filter((dep) => dep.startsWith('@milkdown/'))
74+
this.isTsProject = this.path.join('tsconfig.json').isFile()
75+
}
76+
77+
get scripts() {
78+
return this.packageJson.scripts || {}
79+
}
80+
81+
join(...paths: string[]) {
82+
return this.path.join(...paths)
83+
}
84+
}

Diff for: dev/src/path.ts

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { existsSync, statSync } from 'node:fs'
2+
import { join, relative, sep } from 'node:path'
3+
import { fileURLToPath, pathToFileURL } from 'node:url'
4+
5+
export class Path {
6+
static dir(url: string) {
7+
return new Path(fileURLToPath(url)).join('..')
8+
}
9+
10+
get value() {
11+
return this.path
12+
}
13+
14+
get relativePath() {
15+
return './' + this.path.slice(ProjectRoot.path.length).replace(/\\/g, '/')
16+
}
17+
18+
constructor(private readonly path: string) {}
19+
20+
join(...paths: string[]) {
21+
return new Path(join(this.path, ...paths))
22+
}
23+
24+
parent() {
25+
return this.join('..')
26+
}
27+
28+
toPosixString() {
29+
if (sep === '\\') {
30+
return this.path.replaceAll('\\', '/')
31+
}
32+
33+
return this.path
34+
}
35+
36+
toString() {
37+
return this.path
38+
}
39+
40+
exists() {
41+
return existsSync(this.path)
42+
}
43+
44+
stats() {
45+
return statSync(this.path)
46+
}
47+
48+
isFile() {
49+
return this.exists() && this.stats().isFile()
50+
}
51+
52+
isDirectory() {
53+
return this.exists() && this.stats().isDirectory()
54+
}
55+
56+
toFileUrl() {
57+
return pathToFileURL(this.path)
58+
}
59+
60+
relative(to: string) {
61+
const re = relative(this.value, to)
62+
if (sep === '\\') {
63+
return re.replaceAll('\\', '/')
64+
}
65+
66+
return re
67+
}
68+
}
69+
70+
export const ProjectRoot = Path.dir(import.meta.url).join('../../')

Diff for: dev/src/pnpm.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { once } from 'lodash-es'
2+
3+
import { exec } from './exec'
4+
5+
export type PackageItem = {
6+
name: string
7+
version: string
8+
path: string
9+
private: boolean
10+
}
11+
12+
export const pnpmList = once(() => {
13+
const command = `pnpm list --recursive --depth -1 --json`
14+
15+
const output = exec('', command, { silent: true })
16+
17+
let packageList = JSON.parse(output) as PackageItem[]
18+
19+
packageList.forEach((p) => {
20+
p.path = p.path.replaceAll(/\\/g, '/')
21+
})
22+
23+
return packageList.filter((p) => p.name !== '@milkdown/monorepo')
24+
})

Diff for: dev/src/types.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export interface CommonPackageJsonContent {
2+
name: string
3+
version: string
4+
private?: boolean
5+
dependencies?: { [key: string]: string }
6+
devDependencies?: { [key: string]: string }
7+
scripts?: { [key: string]: string }
8+
main?: string
9+
exports?: {
10+
[key: string]: string
11+
}
12+
}

0 commit comments

Comments
 (0)