2023-08-08 20:06:06 +02:00
import sade from 'sade'
import { $ , fs , path } from '@cspotcode/zx'
import * as core from '@actions/core'
import * as os from 'os'
const root = path . join ( __dirname , '..' )
const prog = sade ( 'cli' ) . describe ( 'CLI for Theatre.js development' )
// better quote function from https://github.com/google/zx/pull/167
$ . quote = function quote ( arg ) {
if ( /^[a-z0-9/_.-]+$/i . test ( arg ) ) {
return arg
}
return (
` $ ' ` +
arg
. replace ( /\\/g , '\\\\' )
. replace ( /'/g , "\\'" )
. replace ( /\f/g , '\\f' )
. replace ( /\n/g , '\\n' )
. replace ( /\r/g , '\\r' )
. replace ( /\t/g , '\\t' )
. replace ( /\v/g , '\\v' )
. replace ( /\0/g , '\\0' ) +
` ' `
)
}
prog
. command (
'build clean' ,
'Cleans the build artifacts and output directories of all the main packages' ,
)
. action ( async ( ) = > {
const packages = [
'theatre' ,
'@theatre/dataverse' ,
'@theatre/react' ,
'@theatre/browser-bundles' ,
'@theatre/r3f' ,
'theatric' ,
]
await Promise . all ( [
. . . packages . map ( ( workspace ) = > $ ` yarn workspace ${ workspace } run clean ` ) ,
] )
} )
prog . command ( 'build' , 'Builds all the main packages' ) . action ( async ( ) = > {
const packagesToBuild = [
'theatre' ,
'@theatre/dataverse' ,
'@theatre/react' ,
'@theatre/browser-bundles' ,
'@theatre/r3f' ,
'theatric' ,
]
async function build() {
await Promise . all ( [
$ ` yarn run build:ts ` ,
. . . packagesToBuild . map (
( workspace ) = > $ ` yarn workspace ${ workspace } run build ` ,
) ,
] )
}
void build ( )
} )
prog
. command ( 'release <version>' , 'Releases all the main packages to npm' )
2023-08-10 14:43:43 +02:00
. option ( '--skip-ts' , 'Skip emitting typescript declarations' )
2023-08-08 20:06:06 +02:00
. option ( '--skip-lint' , 'Skip typecheck and lint' )
. action ( async ( version , opts ) = > {
/ * *
* This script publishes all packages to npm .
*
* It assigns the same version number to all packages ( like lerna ' s fixed mode ) .
* * /
const packagesToBuild = [
'theatre' ,
'@theatre/dataverse' ,
'@theatre/react' ,
'@theatre/browser-bundles' ,
'@theatre/r3f' ,
'theatric' ,
]
const packagesToPublish = [
'@theatre/core' ,
'@theatre/studio' ,
'@theatre/dataverse' ,
'@theatre/react' ,
'@theatre/browser-bundles' ,
'@theatre/r3f' ,
'theatric' ,
]
/ * *
* All these packages will have the same version from monorepo / package . json
* /
const packagesWhoseVersionsShouldBump = [
'.' ,
'theatre' ,
'theatre/core' ,
'theatre/studio' ,
'packages/dataverse' ,
'packages/react' ,
'packages/browser-bundles' ,
'packages/r3f' ,
'packages/theatric' ,
]
// our packages will check for this env variable to make sure their
// prepublish script is only called from the `$ cd /path/to/monorepo; yarn run release`
// @ts-ignore ignore
process . env . THEATRE_IS_PUBLISHING = true
async function release() {
$ . verbose = false
const gitTags = ( await $ ` git tag --list ` ) . toString ( ) . split ( '\n' )
if ( typeof version !== 'string' ) {
console . error (
` You need to specify a version, like: $ yarn cli release 1.2.0-rc.4 ` ,
)
process . exit ( 1 )
} else if (
! version . match ( /^[0-9]+\.[0-9]+\.[0-9]+(\-(dev|rc)\.[0-9]+)?$/ )
) {
console . error (
` Use a semver version, like 1.2.3-rc.4. Provided: ${ version } ` ,
)
process . exit ( 1 )
}
const previousVersion = require ( '../package.json' ) . version
if ( version === previousVersion ) {
console . error (
` Version ${ version } is already assigned to root/package.json ` ,
)
process . exit ( 1 )
}
if ( gitTags . some ( ( tag ) = > tag === version ) ) {
console . error ( ` There is already a git tag for version ${ version } ` )
process . exit ( 1 )
}
let npmTag = 'latest'
if ( version . match ( /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/ ) ) {
console . log ( 'npm tag: latest' )
} else {
const matches = version . match (
/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\-(dev|rc|beta)\.[0-9]{1,3}$/ ,
)
if ( ! matches ) {
console . log (
'Invalid version. Currently xx.xx.xx or xx.xx.xx-(dev|rc|beta).xx is allowed' ,
)
process . exit ( 1 )
}
npmTag = matches [ 1 ]
console . log ( 'npm tag: ' + npmTag )
}
if ( ( await $ ` git status -s ` ) . toString ( ) . length > 0 ) {
console . error ( ` Git working directory contains uncommitted changes: ` )
$ . verbose = true
await $ ` git status -s `
console . log ( 'Commit/stash them and try again.' )
process . exit ( 1 )
}
$ . verbose = true
if ( opts [ 'skip-lint' ] !== true ) {
console . log ( 'Running a typecheck and lint pass' )
await Promise . all ( [ $ ` yarn run typecheck ` , $ ` yarn run lint:all ` ] )
} else {
console . log ( 'Skipping typecheck and lint' )
}
2023-08-10 14:43:43 +02:00
const skipTypescriptEmit = opts [ 'skip-ts' ] === true
2023-08-08 20:06:06 +02:00
console . log ( 'Assigning versions' )
await writeVersionsToPackageJSONs ( version )
console . log ( 'Building all packages' )
await Promise . all (
packagesToBuild . map ( ( workspace ) = >
skipTypescriptEmit
? $ ` yarn workspace ${ workspace } run build:js `
: $ ` yarn workspace ${ workspace } run build ` ,
) ,
)
// temporarily rolling back the version assignments to make sure they don't show
// up in `$ git status`. (would've been better to just ignore hese particular changes
// but i'm lazy)
await restoreVersions ( )
console . log (
'Checking if the build produced artifacts that must first be comitted to git' ,
)
$ . verbose = false
if ( ( await $ ` git status -s ` ) . toString ( ) . length > 0 ) {
$ . verbose = true
await $ ` git status -s `
console . error ( ` Git directory contains uncommitted changes. ` )
process . exit ( 1 )
}
$ . verbose = true
await writeVersionsToPackageJSONs ( version )
console . log ( 'Committing/tagging' )
await $ ` git add . `
await $ ` git commit -m ${ version } `
await $ ` git tag ${ version } `
// if (!gitTags.some((tag) => tag === version)) {
// console.log(
// `No git tag found for version "${version}". Run \`$ git tag ${version}\` and try again.`,
// )
// process.exit()
// }
console . log ( 'Publishing to npm' )
// await Promise.all(
// packagesToPublish.map(
// (workspace) =>
// $`yarn workspace ${workspace} npm publish --access public --tag ${npmTag}`,
// ),
// )
console . log ( 'NOT!!' )
}
void release ( )
async function writeVersionsToPackageJSONs ( monorepoVersion : string ) {
for ( const packagePathRelativeFromRoot of packagesWhoseVersionsShouldBump ) {
const pathToPackage = path . resolve (
__dirname ,
'../' ,
packagePathRelativeFromRoot ,
'./package.json' ,
)
const original = JSON . parse (
fs . readFileSync ( pathToPackage , { encoding : 'utf-8' } ) ,
)
const newJson = { . . . original , version : monorepoVersion }
fs . writeFileSync (
path . join ( pathToPackage ) ,
JSON . stringify ( newJson , undefined , 2 ) ,
{ encoding : 'utf-8' } ,
)
await $ ` prettier --write ${
packagePathRelativeFromRoot + '/package.json'
} `
}
}
async function restoreVersions() {
const wasVerbose = $ . verbose
$ . verbose = false
for ( const packagePathRelativeFromRoot of packagesWhoseVersionsShouldBump ) {
const pathToPackageInGit = packagePathRelativeFromRoot + '/package.json'
await $ ` git checkout ${ pathToPackageInGit } `
}
$ . verbose = wasVerbose
}
} )
prog
. command (
'prerelease ci' ,
"This script publishes the insider packages from the CI. You can't run it locally unless you have a a valid npm access token and you store its value in the `NPM_TOKEN` environmental variable." ,
)
. action ( async ( ) = > {
const packagesToPublish = [
'@theatre/core' ,
'@theatre/studio' ,
'@theatre/dataverse' ,
'@theatre/react' ,
'@theatre/browser-bundles' ,
'@theatre/r3f' ,
'theatric' ,
]
/ * *
* Receives a version number and returns it without the tags , if there are any
*
* @param version - Version number
* @returns Version number without the tags
*
* @example
* ` ` ` javascript
* const version_1 = '0.4.8-dev3-ec175817'
* const version_2 = '0.4.8'
*
* stripTag ( version_1 ) === stripTag ( version_2 ) === '0.4.8' // returns `true`
* ` ` `
* /
function stripTag ( version : string ) {
const regExp = /^[0-9]+\.[0-9]+\.[0-9]+/g
const matches = version . match ( regExp )
if ( ! matches ) {
throw new Error ( ` Version number not found in " ${ version } " ` )
}
return matches [ 0 ]
}
/ * *
* Creates a version number like ` 0.4.8-insiders.ec175817 `
*
* @param packageName - Name of the package
* @param commitHash - A commit hash
* /
function getNewVersionName ( packageName : string , commitHash : string ) {
// The `r3f` package has its own release schedule, so its version numbers
// are almost always different from the rest of the packages.
const pathToPackageJson =
packageName === '@theatre/r3f'
? path . resolve ( __dirname , '../' , 'packages' , 'r3f' , 'package.json' )
: path . resolve ( __dirname , '../' , './package.json' )
const jsonData = JSON . parse (
fs . readFileSync ( pathToPackageJson , { encoding : 'utf-8' } ) ,
)
const strippedVersion = stripTag ( jsonData . version )
return ` ${ strippedVersion } -insiders. ${ commitHash } `
}
/ * *
* Assigns the latest version names ( { @link getNewVersionName } ) to the packages ' ` package.json ` s
*
* @param workspacesListObjects - An Array of objects containing information about the workspaces
* @param latestCommitHash - Hash of the latest commit
* @returns - A record of ` {[packageId]: assignedVersion} `
* /
async function writeVersionsToPackageJSONs (
workspacesListObjects : { name : string ; location : string } [ ] ,
latestCommitHash : string ,
) : Promise < Record < string , string > > {
const assignedVersionByPackageName : Record < string , string > = { }
for ( const workspaceData of workspacesListObjects ) {
const pathToPackage = path . resolve (
__dirname ,
'../' ,
workspaceData . location ,
'./package.json' ,
)
const original = JSON . parse (
fs . readFileSync ( pathToPackage , { encoding : 'utf-8' } ) ,
)
let { version , dependencies , peerDependencies , devDependencies } =
original
// The @theatre/r3f package curently doesn't track the same version number of the other packages like @theatre/core,
// so we need to generate version numbers independently for each package
version = getNewVersionName ( workspaceData . name , latestCommitHash )
assignedVersionByPackageName [ workspaceData . name ] = version
// Normally we don't have to override the package versions in dependencies because yarn would already convert
// all the "workspace:*" versions to a fixed version before publishing. However, packages like @theatre/studio
// have a peerDependency on @theatre/core set to "*" (meaning they would work with any version of @theatre/core).
// This is not the desired behavior in pre-release versions, so here, we'll fix those "*" versions to the set version.
for ( const deps of [ dependencies , peerDependencies , devDependencies ] ) {
if ( ! deps ) continue
for ( const wpObject of workspacesListObjects ) {
if ( deps [ wpObject . name ] ) {
deps [ wpObject . name ] = getNewVersionName (
wpObject . name ,
latestCommitHash ,
)
}
}
}
const newJson = {
. . . original ,
version ,
dependencies ,
peerDependencies ,
devDependencies ,
}
fs . writeFileSync (
path . join ( pathToPackage ) ,
JSON . stringify ( newJson , undefined , 2 ) ,
{ encoding : 'utf-8' } ,
)
await $ ` prettier --write ${ workspaceData . location + '/package.json' } `
}
return assignedVersionByPackageName
}
async function prerelease() {
// @ts-ignore ignore
process . env . THEATRE_IS_PUBLISHING = true
// In the CI `git log -1` points to a fake merge commit,
// so we have to use the value of a special GitHub context variable
// through the `GITHUB_SHA` environmental variable.
// The length of the abbreviated commit hash can change, that's why we
// need the length of the fake merge commit's abbreviated hash.
const fakeMergeCommitHashLength = ( await $ ` git log -1 --pretty=format:%h ` )
. stdout . length
if ( ! process . env . GITHUB_SHA )
throw new Error (
'expected `process.env.GITHUB_SHA` to be defined but it was not' ,
)
const latestCommitHash = process . env . GITHUB_SHA . slice (
0 ,
fakeMergeCommitHashLength ,
)
const workspacesListString = await $ ` yarn workspaces list --json `
const workspacesListObjects = workspacesListString . stdout
. split ( os . EOL )
// strip out empty lines
. filter ( Boolean )
. map ( ( x ) = > JSON . parse ( x ) )
const assignedVersionByPackageName = await writeVersionsToPackageJSONs (
workspacesListObjects ,
latestCommitHash ,
)
await Promise . all (
packagesToPublish . map ( async ( workspaceName ) = > {
const npmTag = 'insiders'
if ( process . env . GITHUB_ACTIONS ) {
await $ ` yarn workspace ${ workspaceName } npm publish --access public --tag ${ npmTag } `
}
} ) ,
)
if ( process . env . GITHUB_ACTIONS ) {
const data = packagesToPublish . map ( ( packageName ) = > ( {
packageName ,
version : assignedVersionByPackageName [ packageName ] ,
} ) )
// set the output for github actions.
core . setOutput ( 'data' , JSON . stringify ( data ) )
} else {
for ( const packageName of packagesToPublish ) {
await $ ` echo ${ ` Published ${ packageName } @ ${ assignedVersionByPackageName [ packageName ] } ` } `
}
}
}
void prerelease ( )
} )
prog
. command ( 'dev all' , 'Starts all services to develop all of the packages' )
. action ( async ( ) = > {
await $ ` yarn workspace playground run serve `
} )
prog . parse ( process . argv )