ci: move workflow helpers into tools
This commit is contained in:
parent
e870e01521
commit
7d516bf82b
85
.github/workflows/build.yml
vendored
85
.github/workflows/build.yml
vendored
|
|
@ -145,75 +145,7 @@ jobs:
|
||||||
|
|
||||||
- name: Prepare publish directories
|
- name: Prepare publish directories
|
||||||
run: |
|
run: |
|
||||||
node - <<'NODE'
|
bun tools/release-packages/src/prepare-publish-dirs.ts
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const root = process.cwd()
|
|
||||||
const outRoot = path.join(root, '.publish')
|
|
||||||
const coreVersion = require('./packages/core/package.json').version
|
|
||||||
|
|
||||||
fs.rmSync(outRoot, { recursive: true, force: true })
|
|
||||||
fs.mkdirSync(outRoot, { recursive: true })
|
|
||||||
|
|
||||||
const copyRecursive = (from, to) => {
|
|
||||||
const stat = fs.statSync(from)
|
|
||||||
if (stat.isDirectory()) {
|
|
||||||
fs.mkdirSync(to, { recursive: true })
|
|
||||||
for (const entry of fs.readdirSync(from)) {
|
|
||||||
copyRecursive(path.join(from, entry), path.join(to, entry))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fs.mkdirSync(path.dirname(to), { recursive: true })
|
|
||||||
fs.copyFileSync(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
const packages = [
|
|
||||||
{ dir: 'packages/core', include: ['dist', 'src', 'assets'], extraFiles: [] },
|
|
||||||
{ dir: 'packages/cli', include: ['bin', 'dist'], extraFiles: [] },
|
|
||||||
{ dir: 'packages/mcp', include: ['dist'], extraFiles: [] },
|
|
||||||
{ dir: 'packages/vue', include: ['dist'], extraFiles: ['README.md'] }
|
|
||||||
]
|
|
||||||
|
|
||||||
for (const pkg of packages) {
|
|
||||||
const srcDir = path.join(root, pkg.dir)
|
|
||||||
const destDir = path.join(outRoot, path.basename(pkg.dir))
|
|
||||||
fs.mkdirSync(destDir, { recursive: true })
|
|
||||||
|
|
||||||
for (const rel of pkg.include) {
|
|
||||||
const from = path.join(srcDir, rel)
|
|
||||||
if (fs.existsSync(from)) copyRecursive(from, path.join(destDir, rel))
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const rel of pkg.extraFiles) {
|
|
||||||
const from = path.join(srcDir, rel)
|
|
||||||
if (fs.existsSync(from)) copyRecursive(from, path.join(destDir, rel))
|
|
||||||
}
|
|
||||||
|
|
||||||
const pkgJsonPath = path.join(srcDir, 'package.json')
|
|
||||||
const json = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))
|
|
||||||
for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
||||||
if (!json[field]) continue
|
|
||||||
for (const [name, version] of Object.entries(json[field])) {
|
|
||||||
if (typeof version === 'string' && version.startsWith('workspace:')) {
|
|
||||||
json[field][name] = '^' + coreVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete json.scripts
|
|
||||||
delete json.devDependencies
|
|
||||||
if (json.publishConfig) {
|
|
||||||
for (const [key, value] of Object.entries(json.publishConfig)) {
|
|
||||||
if (key === 'access' || key === 'provenance' || key === 'registry') continue
|
|
||||||
json[key] = value
|
|
||||||
}
|
|
||||||
delete json.publishConfig
|
|
||||||
}
|
|
||||||
fs.writeFileSync(path.join(destDir, 'package.json'), JSON.stringify(json, null, 2) + '\n')
|
|
||||||
console.log(`Prepared ${destDir}`)
|
|
||||||
}
|
|
||||||
NODE
|
|
||||||
find .publish -maxdepth 3 -type f | sort
|
find .publish -maxdepth 3 -type f | sort
|
||||||
|
|
||||||
- name: Pack packages for publish
|
- name: Pack packages for publish
|
||||||
|
|
@ -239,20 +171,7 @@ jobs:
|
||||||
tar -tf .npm-packages/open-pencil-cli-*.tgz | sed -n '1,40p' || true
|
tar -tf .npm-packages/open-pencil-cli-*.tgz | sed -n '1,40p' || true
|
||||||
tar -tf .npm-packages/open-pencil-mcp-*.tgz | sed -n '1,40p' || true
|
tar -tf .npm-packages/open-pencil-mcp-*.tgz | sed -n '1,40p' || true
|
||||||
tar -tf .npm-packages/open-pencil-vue-*.tgz | sed -n '1,60p' || true
|
tar -tf .npm-packages/open-pencil-vue-*.tgz | sed -n '1,60p' || true
|
||||||
node --input-type=module <<'NODE'
|
bun tools/release-packages/src/validate-tarballs.ts
|
||||||
import fs from 'node:fs'
|
|
||||||
import { execFileSync } from 'node:child_process'
|
|
||||||
for (const tarball of fs.readdirSync('.npm-packages').filter((name) => name.endsWith('.tgz'))) {
|
|
||||||
const entries = new Set(execFileSync('tar', ['-tf', `.npm-packages/${tarball}`], { encoding: 'utf8' }).trim().split('\n'))
|
|
||||||
const packageJson = JSON.parse(execFileSync('tar', ['-xOf', `.npm-packages/${tarball}`, 'package/package.json'], { encoding: 'utf8' }))
|
|
||||||
const bin = packageJson.bin
|
|
||||||
const targets = typeof bin === 'string' ? { [packageJson.name]: bin } : bin || {}
|
|
||||||
for (const [name, target] of Object.entries(targets)) {
|
|
||||||
const entry = `package/${String(target).replace(/^\.\//, '')}`
|
|
||||||
if (!entries.has(entry)) throw new Error(`${tarball}: bin ${name} target missing from tarball: ${entry}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
NODE
|
|
||||||
|
|
||||||
- name: Publish packages to npm
|
- name: Publish packages to npm
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@
|
||||||
"test:unit": "bun test ./tests/engine",
|
"test:unit": "bun test ./tests/engine",
|
||||||
"test:coverage": "bun test --coverage ./tests/engine",
|
"test:coverage": "bun test --coverage ./tests/engine",
|
||||||
"test:type-shapes": "bun scripts/type-shapes.ts",
|
"test:type-shapes": "bun scripts/type-shapes.ts",
|
||||||
"test:tools": "bun --cwd tools/pr-review-guidance test",
|
"test:tools": "bun tools/test.ts",
|
||||||
"test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0",
|
"test:dupes": "jscpd packages/core/src packages/cli/src src --min-lines 5 --min-tokens 50 --format typescript --threshold 0",
|
||||||
"test:packages": "bun scripts/check-package-metadata.ts && bun scripts/smoke-packages.ts",
|
"test:packages": "bun scripts/check-package-metadata.ts && bun scripts/smoke-packages.ts",
|
||||||
"build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build",
|
"build:packages": "bun --filter @open-pencil/core build && bun --filter @open-pencil/vue build && bun --filter @open-pencil/mcp build && bun --filter @open-pencil/cli build",
|
||||||
|
|
|
||||||
|
|
@ -59,14 +59,19 @@ export function markdownTableCells(line: string): string[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizedCheckName(value: string): string {
|
export function normalizedCheckName(value: string): string {
|
||||||
return value.replace(/[^a-z0-9]+/gi, ' ').trim().toLowerCase()
|
return value
|
||||||
|
.replace(/[^a-z0-9]+/gi, ' ')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
export function reviewGuidanceCheckName(line: string): string | null {
|
export function reviewGuidanceCheckName(line: string): string | null {
|
||||||
const [rawCheckName = '', status = ''] = markdownTableCells(line)
|
const [rawCheckName = '', status = ''] = markdownTableCells(line)
|
||||||
const checkName = rawCheckName.replace(/^\[ignored\]\s*/i, '')
|
const checkName = rawCheckName.replace(/^\[ignored\]\s*/i, '')
|
||||||
const normalizedName = normalizedCheckName(checkName)
|
const normalizedName = normalizedCheckName(checkName)
|
||||||
const isRelevantCheck = REVIEW_GUIDANCE_PREFIXES.some((prefix) => normalizedName.startsWith(prefix))
|
const isRelevantCheck = REVIEW_GUIDANCE_PREFIXES.some((prefix) =>
|
||||||
|
normalizedName.startsWith(prefix)
|
||||||
|
)
|
||||||
const needsMaintainerAttention = /❌/u.test(status) || /\berror\b/i.test(status)
|
const needsMaintainerAttention = /❌/u.test(status) || /\berror\b/i.test(status)
|
||||||
|
|
||||||
if (!isRelevantCheck || !needsMaintainerAttention) return null
|
if (!isRelevantCheck || !needsMaintainerAttention) return null
|
||||||
|
|
@ -167,7 +172,9 @@ export async function monitorPRReviewGuidance(options: MonitorOptions = {}): Pro
|
||||||
|
|
||||||
const checks = reviewGuidanceChecks(context.text)
|
const checks = reviewGuidanceChecks(context.text)
|
||||||
if (checks.length === 0) {
|
if (checks.length === 0) {
|
||||||
log('CodeRabbit did not report a PR description/readability check that needs maintainer attention.')
|
log(
|
||||||
|
'CodeRabbit did not report a PR description/readability check that needs maintainer attention.'
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,7 +196,9 @@ export async function monitorPRReviewGuidance(options: MonitorOptions = {}): Pro
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDirectRun = process.argv[1] ? import.meta.url === pathToFileURL(process.argv[1]).href : false
|
const isDirectRun = process.argv[1]
|
||||||
|
? import.meta.url === pathToFileURL(process.argv[1]).href
|
||||||
|
: false
|
||||||
|
|
||||||
if (isDirectRun) {
|
if (isDirectRun) {
|
||||||
await monitorPRReviewGuidance()
|
await monitorPRReviewGuidance()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
import { mkdtemp, writeFile } from 'node:fs/promises'
|
import { mkdtemp, writeFile } from 'node:fs/promises'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
|
|
||||||
import { describe, expect, test } from 'bun:test'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
eventContext,
|
eventContext,
|
||||||
monitorPRReviewGuidance,
|
monitorPRReviewGuidance,
|
||||||
|
|
|
||||||
10
tools/release-packages/package.json
Normal file
10
tools/release-packages/package.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"name": "@open-pencil/release-packages",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"prepare-publish-dirs": "bun src/prepare-publish-dirs.ts",
|
||||||
|
"validate-tarballs": "bun src/validate-tarballs.ts",
|
||||||
|
"test": "bun test tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
18
tools/release-packages/src/prepare-publish-dirs.ts
Normal file
18
tools/release-packages/src/prepare-publish-dirs.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
import { DEFAULT_PACKAGES, preparePublishDirectories } from './publish-dirs'
|
||||||
|
|
||||||
|
const root = process.cwd()
|
||||||
|
const corePackageJSON = JSON.parse(
|
||||||
|
await readFile(join(root, 'packages/core/package.json'), 'utf8')
|
||||||
|
) as {
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
await preparePublishDirectories({
|
||||||
|
coreVersion: corePackageJSON.version,
|
||||||
|
packages: DEFAULT_PACKAGES,
|
||||||
|
root,
|
||||||
|
log: (message) => process.stdout.write(`${message}\n`)
|
||||||
|
})
|
||||||
117
tools/release-packages/src/publish-dirs.ts
Normal file
117
tools/release-packages/src/publish-dirs.ts
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
import { copyFile, mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises'
|
||||||
|
import { basename, dirname, join } from 'node:path'
|
||||||
|
|
||||||
|
interface PackagePublishConfig {
|
||||||
|
dir: string
|
||||||
|
extraFiles: string[]
|
||||||
|
include: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PreparePublishDirectoriesOptions {
|
||||||
|
coreVersion: string
|
||||||
|
packages: PackagePublishConfig[]
|
||||||
|
root: string
|
||||||
|
outRoot?: string
|
||||||
|
log?: (message: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type PackageJSON = Record<string, unknown> & {
|
||||||
|
dependencies?: Record<string, string>
|
||||||
|
devDependencies?: Record<string, string>
|
||||||
|
peerDependencies?: Record<string, string>
|
||||||
|
publishConfig?: Record<string, unknown>
|
||||||
|
scripts?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
const PACKAGE_FIELDS = ['dependencies', 'devDependencies', 'peerDependencies'] as const
|
||||||
|
const PUBLISH_CONFIG_FIELDS = new Set(['access', 'provenance', 'registry'])
|
||||||
|
|
||||||
|
export const DEFAULT_PACKAGES: PackagePublishConfig[] = [
|
||||||
|
{ dir: 'packages/core', include: ['dist', 'src', 'assets'], extraFiles: [] },
|
||||||
|
{ dir: 'packages/cli', include: ['bin', 'dist'], extraFiles: [] },
|
||||||
|
{ dir: 'packages/mcp', include: ['dist'], extraFiles: [] },
|
||||||
|
{ dir: 'packages/vue', include: ['dist'], extraFiles: ['README.md'] }
|
||||||
|
]
|
||||||
|
|
||||||
|
async function exists(path: string) {
|
||||||
|
try {
|
||||||
|
await stat(path)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyRecursive(from: string, to: string): Promise<void> {
|
||||||
|
const sourceStat = await stat(from)
|
||||||
|
if (sourceStat.isDirectory()) {
|
||||||
|
await mkdir(to, { recursive: true })
|
||||||
|
for (const entry of await readdir(from)) {
|
||||||
|
await copyRecursive(join(from, entry), join(to, entry))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await mkdir(dirname(to), { recursive: true })
|
||||||
|
await copyFile(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function publishPackageJSON(source: PackageJSON, coreVersion: string): PackageJSON {
|
||||||
|
const json = structuredClone(source)
|
||||||
|
|
||||||
|
for (const field of PACKAGE_FIELDS) {
|
||||||
|
const dependencies = json[field]
|
||||||
|
if (!dependencies) continue
|
||||||
|
for (const [name, version] of Object.entries(dependencies)) {
|
||||||
|
if (version.startsWith('workspace:')) dependencies[name] = `^${coreVersion}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete json.scripts
|
||||||
|
delete json.devDependencies
|
||||||
|
|
||||||
|
if (json.publishConfig) {
|
||||||
|
for (const [key, value] of Object.entries(json.publishConfig)) {
|
||||||
|
if (!PUBLISH_CONFIG_FIELDS.has(key)) json[key] = value
|
||||||
|
}
|
||||||
|
delete json.publishConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
return json
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preparePublishDirectories(
|
||||||
|
options: PreparePublishDirectoriesOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const outRoot = options.outRoot ?? join(options.root, '.publish')
|
||||||
|
const log = options.log
|
||||||
|
|
||||||
|
await rm(outRoot, { recursive: true, force: true })
|
||||||
|
await mkdir(outRoot, { recursive: true })
|
||||||
|
|
||||||
|
for (const pkg of options.packages) {
|
||||||
|
const sourceDir = join(options.root, pkg.dir)
|
||||||
|
const destinationDir = join(outRoot, basename(pkg.dir))
|
||||||
|
await mkdir(destinationDir, { recursive: true })
|
||||||
|
|
||||||
|
for (const relativePath of pkg.include) {
|
||||||
|
const from = join(sourceDir, relativePath)
|
||||||
|
if (await exists(from)) await copyRecursive(from, join(destinationDir, relativePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const relativePath of pkg.extraFiles) {
|
||||||
|
const from = join(sourceDir, relativePath)
|
||||||
|
if (await exists(from)) await copyRecursive(from, join(destinationDir, relativePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJSON = JSON.parse(
|
||||||
|
await readFile(join(sourceDir, 'package.json'), 'utf8')
|
||||||
|
) as PackageJSON
|
||||||
|
const publishJSON = publishPackageJSON(packageJSON, options.coreVersion)
|
||||||
|
await writeFile(
|
||||||
|
join(destinationDir, 'package.json'),
|
||||||
|
`${JSON.stringify(publishJSON, null, 2)}\n`
|
||||||
|
)
|
||||||
|
log?.(`Prepared ${destinationDir}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
tools/release-packages/src/tarballs.ts
Normal file
47
tools/release-packages/src/tarballs.ts
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { execFile } from 'node:child_process'
|
||||||
|
import { readdir } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import { promisify } from 'node:util'
|
||||||
|
|
||||||
|
const execFileAsync = promisify(execFile)
|
||||||
|
|
||||||
|
type PackageJSON = {
|
||||||
|
bin?: Record<string, string> | string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function packageBinTargets(packageJSON: PackageJSON): Record<string, string> {
|
||||||
|
if (typeof packageJSON.bin === 'string') return { [packageJSON.name]: packageJSON.bin }
|
||||||
|
return packageJSON.bin ?? {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tarballEntries(tarballPath: string): Promise<Set<string>> {
|
||||||
|
const { stdout } = await execFileAsync('tar', ['-tf', tarballPath], { encoding: 'utf8' })
|
||||||
|
return new Set(stdout.trim().split('\n').filter(Boolean))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tarballPackageJSON(tarballPath: string): Promise<PackageJSON> {
|
||||||
|
const { stdout } = await execFileAsync('tar', ['-xOf', tarballPath, 'package/package.json'], {
|
||||||
|
encoding: 'utf8'
|
||||||
|
})
|
||||||
|
return JSON.parse(stdout) as PackageJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validateTarballBinTargets(tarballPath: string): Promise<void> {
|
||||||
|
const entries = await tarballEntries(tarballPath)
|
||||||
|
const packageJSON = await tarballPackageJSON(tarballPath)
|
||||||
|
|
||||||
|
for (const [name, target] of Object.entries(packageBinTargets(packageJSON))) {
|
||||||
|
const entry = `package/${target.replace(/^\.\//, '')}`
|
||||||
|
if (!entries.has(entry)) {
|
||||||
|
throw new Error(`${tarballPath}: bin ${name} target missing from tarball: ${entry}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validatePackedTarballs(directory: string): Promise<void> {
|
||||||
|
const tarballs = (await readdir(directory)).filter((name) => name.endsWith('.tgz'))
|
||||||
|
for (const tarball of tarballs) {
|
||||||
|
await validateTarballBinTargets(join(directory, tarball))
|
||||||
|
}
|
||||||
|
}
|
||||||
3
tools/release-packages/src/validate-tarballs.ts
Normal file
3
tools/release-packages/src/validate-tarballs.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import { validatePackedTarballs } from './tarballs'
|
||||||
|
|
||||||
|
await validatePackedTarballs('.npm-packages')
|
||||||
72
tools/release-packages/tests/publish-dirs.test.ts
Normal file
72
tools/release-packages/tests/publish-dirs.test.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||||
|
import { tmpdir } from 'node:os'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
import { preparePublishDirectories, publishPackageJSON } from '../src/publish-dirs'
|
||||||
|
|
||||||
|
async function fixtureRoot() {
|
||||||
|
const root = join(tmpdir(), `open-pencil-release-packages-${crypto.randomUUID()}`)
|
||||||
|
await mkdir(join(root, 'packages/example/dist'), { recursive: true })
|
||||||
|
await writeFile(join(root, 'packages/example/dist/index.js'), 'export {}\n')
|
||||||
|
await writeFile(
|
||||||
|
join(root, 'packages/example/package.json'),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
name: '@open-pencil/example',
|
||||||
|
version: '1.0.0',
|
||||||
|
scripts: { build: 'tsdown' },
|
||||||
|
dependencies: { '@open-pencil/core': 'workspace:*', zod: '^4.0.0' },
|
||||||
|
devDependencies: { typescript: '^5.0.0' },
|
||||||
|
publishConfig: { access: 'public', main: './dist/index.js', types: './dist/index.d.ts' }
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('publishPackageJSON', () => {
|
||||||
|
test('rewrites workspace dependencies and strips private build fields', () => {
|
||||||
|
const json = publishPackageJSON(
|
||||||
|
{
|
||||||
|
name: '@open-pencil/example',
|
||||||
|
scripts: { build: 'tsdown' },
|
||||||
|
dependencies: { '@open-pencil/core': 'workspace:*', zod: '^4.0.0' },
|
||||||
|
devDependencies: { typescript: '^5.0.0' },
|
||||||
|
publishConfig: { access: 'public', main: './dist/index.js' }
|
||||||
|
},
|
||||||
|
'0.13.2'
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(json).toEqual({
|
||||||
|
name: '@open-pencil/example',
|
||||||
|
dependencies: { '@open-pencil/core': '^0.13.2', zod: '^4.0.0' },
|
||||||
|
main: './dist/index.js'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('preparePublishDirectories', () => {
|
||||||
|
test('copies requested files and writes publish package metadata', async () => {
|
||||||
|
const root = await fixtureRoot()
|
||||||
|
const outRoot = join(root, '.publish')
|
||||||
|
|
||||||
|
await preparePublishDirectories({
|
||||||
|
coreVersion: '0.13.2',
|
||||||
|
outRoot,
|
||||||
|
packages: [{ dir: 'packages/example', include: ['dist'], extraFiles: [] }],
|
||||||
|
root
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await readFile(join(outRoot, 'example/dist/index.js'), 'utf8')).toBe('export {}\n')
|
||||||
|
expect(JSON.parse(await readFile(join(outRoot, 'example/package.json'), 'utf8'))).toEqual({
|
||||||
|
name: '@open-pencil/example',
|
||||||
|
version: '1.0.0',
|
||||||
|
dependencies: { '@open-pencil/core': '^0.13.2', zod: '^4.0.0' },
|
||||||
|
main: './dist/index.js',
|
||||||
|
types: './dist/index.d.ts'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
19
tools/release-packages/tests/tarballs.test.ts
Normal file
19
tools/release-packages/tests/tarballs.test.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
|
||||||
|
import { packageBinTargets } from '../src/tarballs'
|
||||||
|
|
||||||
|
describe('packageBinTargets', () => {
|
||||||
|
test('normalizes string bin fields', () => {
|
||||||
|
expect(packageBinTargets({ name: '@open-pencil/cli', bin: './bin/openpencil.js' })).toEqual({
|
||||||
|
'@open-pencil/cli': './bin/openpencil.js'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('keeps named bin fields', () => {
|
||||||
|
expect(
|
||||||
|
packageBinTargets({ name: '@open-pencil/cli', bin: { openpencil: './bin/openpencil.js' } })
|
||||||
|
).toEqual({
|
||||||
|
openpencil: './bin/openpencil.js'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
26
tools/test.ts
Normal file
26
tools/test.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import { readdir, readFile } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
|
||||||
|
interface PackageJSON {
|
||||||
|
scripts?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
async function run(command: string, args: string[], cwd: string): Promise<void> {
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
const child = spawn(command, args, { cwd, stdio: 'inherit' })
|
||||||
|
child.on('error', reject)
|
||||||
|
child.on('exit', (code) => {
|
||||||
|
if (code === 0) resolve()
|
||||||
|
else reject(new Error(`${command} ${args.join(' ')} failed in ${cwd}`))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const entry of await readdir('tools', { withFileTypes: true })) {
|
||||||
|
if (!entry.isDirectory()) continue
|
||||||
|
|
||||||
|
const cwd = join('tools', entry.name)
|
||||||
|
const packageJSON = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8')) as PackageJSON
|
||||||
|
if (packageJSON.scripts?.test) await run('bun', ['test'], cwd)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue