2023-04-25 11:01:25 +00:00
|
|
|
import { execSync } from 'child_process'
|
|
|
|
import { fetch } from 'cross-fetch'
|
2023-09-26 15:36:32 +00:00
|
|
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from 'fs'
|
2023-05-04 09:25:31 +00:00
|
|
|
import path, { join } from 'path'
|
2023-04-25 11:01:25 +00:00
|
|
|
import { compare, parse } from 'semver'
|
2023-09-26 15:36:32 +00:00
|
|
|
import { exec } from './exec'
|
2024-01-16 14:38:05 +00:00
|
|
|
import { REPO_ROOT } from './file'
|
2023-06-01 18:01:49 +00:00
|
|
|
import { nicelog } from './nicelog'
|
2023-04-25 11:01:25 +00:00
|
|
|
|
|
|
|
export type PackageDetails = {
|
|
|
|
name: string
|
|
|
|
dir: string
|
|
|
|
localDeps: string[]
|
|
|
|
version: string
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPackageDetails(dir: string): PackageDetails | null {
|
|
|
|
const packageJsonPath = path.join(dir, 'package.json')
|
|
|
|
if (!existsSync(packageJsonPath)) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
const packageJson = JSON.parse(readFileSync(path.join(dir, 'package.json'), 'utf8'))
|
|
|
|
if (packageJson.private) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
name: packageJson.name,
|
|
|
|
dir,
|
|
|
|
version: packageJson.version,
|
|
|
|
localDeps: Object.keys(packageJson.dependencies ?? {}).filter((dep) =>
|
|
|
|
dep.startsWith('@tldraw')
|
|
|
|
),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getAllPackageDetails(): Record<string, PackageDetails> {
|
2024-01-16 14:38:05 +00:00
|
|
|
const dirs = readdirSync(join(REPO_ROOT, 'packages'))
|
2023-04-25 11:01:25 +00:00
|
|
|
const results = dirs
|
2024-01-16 14:38:05 +00:00
|
|
|
.map((dir) => getPackageDetails(path.join(REPO_ROOT, 'packages', dir)))
|
2023-04-25 11:01:25 +00:00
|
|
|
.filter((x): x is PackageDetails => Boolean(x))
|
|
|
|
|
|
|
|
return Object.fromEntries(results.map((result) => [result.name, result]))
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setAllVersions(version: string) {
|
|
|
|
const packages = getAllPackageDetails()
|
|
|
|
for (const packageDetails of Object.values(packages)) {
|
|
|
|
const manifest = JSON.parse(readFileSync(path.join(packageDetails.dir, 'package.json'), 'utf8'))
|
|
|
|
manifest.version = version
|
|
|
|
writeFileSync(
|
|
|
|
path.join(packageDetails.dir, 'package.json'),
|
|
|
|
JSON.stringify(manifest, null, '\t') + '\n'
|
|
|
|
)
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
if (manifest.name === '@tldraw/editor') {
|
2023-06-07 21:01:38 +00:00
|
|
|
const versionFileContents = `export const version = '${version}'\n`
|
|
|
|
writeFileSync(path.join(packageDetails.dir, 'src', 'version.ts'), versionFileContents)
|
|
|
|
}
|
tldraw zero - package shuffle (#1710)
This PR moves code between our packages so that:
- @tldraw/editor is a “core” library with the engine and canvas but no
shapes, tools, or other things
- @tldraw/tldraw contains everything particular to the experience we’ve
built for tldraw
At first look, this might seem like a step away from customization and
configuration, however I believe it greatly increases the configuration
potential of the @tldraw/editor while also providing a more accurate
reflection of what configuration options actually exist for
@tldraw/tldraw.
## Library changes
@tldraw/editor re-exports its dependencies and @tldraw/tldraw re-exports
@tldraw/editor.
- users of @tldraw/editor WITHOUT @tldraw/tldraw should almost always
only import things from @tldraw/editor.
- users of @tldraw/tldraw should almost always only import things from
@tldraw/tldraw.
- @tldraw/polyfills is merged into @tldraw/editor
- @tldraw/indices is merged into @tldraw/editor
- @tldraw/primitives is merged mostly into @tldraw/editor, partially
into @tldraw/tldraw
- @tldraw/file-format is merged into @tldraw/tldraw
- @tldraw/ui is merged into @tldraw/tldraw
Many (many) utils and other code is moved from the editor to tldraw. For
example, embeds now are entirely an feature of @tldraw/tldraw. The only
big chunk of code left in core is related to arrow handling.
## API Changes
The editor can now be used without tldraw's assets. We load them in
@tldraw/tldraw instead, so feel free to use whatever fonts or images or
whatever that you like with the editor.
All tools and shapes (except for the `Group` shape) are moved to
@tldraw/tldraw. This includes the `select` tool.
You should use the editor with at least one tool, however, so you now
also need to send in an `initialState` prop to the Editor /
<TldrawEditor> component indicating which state the editor should begin
in.
The `components` prop now also accepts `SelectionForeground`.
The complex selection component that we use for tldraw is moved to
@tldraw/tldraw. The default component is quite basic but can easily be
replaced via the `components` prop. We pass down our tldraw-flavored
SelectionFg via `components`.
Likewise with the `Scribble` component: the `DefaultScribble` no longer
uses our freehand tech and is a simple path instead. We pass down the
tldraw-flavored scribble via `components`.
The `ExternalContentManager` (`Editor.externalContentManager`) is
removed and replaced with a mapping of types to handlers.
- Register new content handlers with
`Editor.registerExternalContentHandler`.
- Register new asset creation handlers (for files and URLs) with
`Editor.registerExternalAssetHandler`
### Change Type
- [x] `major` — Breaking change
### Test Plan
- [x] Unit Tests
- [x] End to end tests
### Release Notes
- [@tldraw/editor] lots, wip
- [@tldraw/ui] gone, merged to tldraw/tldraw
- [@tldraw/polyfills] gone, merged to tldraw/editor
- [@tldraw/primitives] gone, merged to tldraw/editor / tldraw/tldraw
- [@tldraw/indices] gone, merged to tldraw/editor
- [@tldraw/file-format] gone, merged to tldraw/tldraw
---------
Co-authored-by: alex <alex@dytry.ch>
2023-07-17 21:22:34 +00:00
|
|
|
if (manifest.name === '@tldraw/tldraw') {
|
|
|
|
const versionFileContents = `export const version = '${version}'\n`
|
|
|
|
writeFileSync(
|
|
|
|
path.join(packageDetails.dir, 'src', 'lib', 'ui', 'version.ts'),
|
|
|
|
versionFileContents
|
|
|
|
)
|
|
|
|
}
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const lernaJson = JSON.parse(readFileSync('lerna.json', 'utf8'))
|
|
|
|
lernaJson.version = version
|
|
|
|
writeFileSync('lerna.json', JSON.stringify(lernaJson, null, '\t') + '\n')
|
|
|
|
|
|
|
|
execSync('yarn')
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getLatestVersion() {
|
|
|
|
const packages = getAllPackageDetails()
|
|
|
|
|
|
|
|
const allVersions = Object.values(packages).map((p) => parse(p.version)!)
|
|
|
|
allVersions.sort(compare)
|
|
|
|
|
|
|
|
const latestVersion = allVersions[allVersions.length - 1]
|
|
|
|
|
|
|
|
if (!latestVersion) {
|
|
|
|
throw new Error('Could not find latest version')
|
|
|
|
}
|
|
|
|
|
|
|
|
return latestVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
function topologicalSortPackages(packages: Record<string, PackageDetails>) {
|
|
|
|
const sorted: PackageDetails[] = []
|
|
|
|
const visited = new Set<string>()
|
|
|
|
|
|
|
|
function visit(packageName: string, path: string[]) {
|
|
|
|
if (visited.has(packageName)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
visited.add(packageName)
|
|
|
|
const packageDetails = packages[packageName]
|
|
|
|
if (!packageDetails) {
|
|
|
|
throw new Error(`Could not find package ${packageName}. path: ${path.join(' -> ')}`)
|
|
|
|
}
|
|
|
|
packageDetails.localDeps.forEach((dep) => visit(dep, [...path, dep]))
|
|
|
|
sorted.push(packageDetails)
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.keys(packages).forEach((packageName) => visit(packageName, [packageName]))
|
|
|
|
|
|
|
|
return sorted
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function publish() {
|
|
|
|
const npmToken = process.env.NPM_TOKEN
|
|
|
|
if (!npmToken) {
|
|
|
|
throw new Error('NPM_TOKEN not set')
|
|
|
|
}
|
|
|
|
|
|
|
|
execSync(`yarn config set npmAuthToken ${npmToken}`, { stdio: 'inherit' })
|
|
|
|
execSync(`yarn config set npmRegistryServer https://registry.npmjs.org`, { stdio: 'inherit' })
|
|
|
|
|
|
|
|
const packages = getAllPackageDetails()
|
|
|
|
|
|
|
|
const publishOrder = topologicalSortPackages(packages)
|
|
|
|
|
|
|
|
for (const packageDetails of publishOrder) {
|
|
|
|
const prereleaseTag = parse(packageDetails.version)?.prerelease[0] ?? 'latest'
|
2023-06-01 18:01:49 +00:00
|
|
|
nicelog(
|
2023-04-25 11:01:25 +00:00
|
|
|
`Publishing ${packageDetails.name} with version ${packageDetails.version} under tag @${prereleaseTag}`
|
|
|
|
)
|
|
|
|
|
2023-09-26 15:36:32 +00:00
|
|
|
await retry(
|
|
|
|
async () => {
|
|
|
|
let output = ''
|
|
|
|
try {
|
2023-09-26 16:44:47 +00:00
|
|
|
await exec(
|
2023-09-26 15:36:32 +00:00
|
|
|
`yarn`,
|
|
|
|
[
|
|
|
|
'npm',
|
|
|
|
'publish',
|
|
|
|
'--tag',
|
|
|
|
String(prereleaseTag),
|
|
|
|
'--tolerate-republish',
|
|
|
|
'--access',
|
|
|
|
'public',
|
|
|
|
],
|
|
|
|
{
|
|
|
|
pwd: packageDetails.dir,
|
|
|
|
processStdoutLine: (line) => {
|
|
|
|
output += line + '\n'
|
|
|
|
nicelog(line)
|
|
|
|
},
|
|
|
|
processStderrLine: (line) => {
|
|
|
|
output += line + '\n'
|
|
|
|
nicelog(line)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} catch (e) {
|
|
|
|
if (output.includes('You cannot publish over the previously published versions')) {
|
|
|
|
// --tolerate-republish seems to not work for canary versions??? so let's just ignore this error
|
|
|
|
return
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
},
|
|
|
|
{
|
|
|
|
delay: 10_000,
|
|
|
|
numAttempts: 5,
|
|
|
|
}
|
|
|
|
)
|
2023-04-25 11:01:25 +00:00
|
|
|
|
2023-09-26 15:36:32 +00:00
|
|
|
await retry(
|
|
|
|
async ({ attempt, total }) => {
|
|
|
|
nicelog('Waiting for package to be published... attempt', attempt, 'of', total)
|
2023-04-25 11:01:25 +00:00
|
|
|
// fetch the new package directly from the npm registry
|
|
|
|
const newVersion = packageDetails.version
|
|
|
|
const unscopedName = packageDetails.name.replace('@tldraw/', '')
|
|
|
|
|
|
|
|
const url = `https://registry.npmjs.org/@tldraw/${unscopedName}/-/${unscopedName}-${newVersion}.tgz`
|
2023-06-01 18:01:49 +00:00
|
|
|
nicelog('looking for package at url: ', url)
|
2023-04-25 11:01:25 +00:00
|
|
|
const res = await fetch(url, {
|
|
|
|
method: 'HEAD',
|
|
|
|
})
|
|
|
|
if (res.status >= 400) {
|
|
|
|
throw new Error(`Package not found: ${res.status}`)
|
|
|
|
}
|
2023-09-26 15:36:32 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
delay: 3000,
|
|
|
|
numAttempts: 10,
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
2023-09-26 15:36:32 +00:00
|
|
|
)
|
2023-04-25 11:01:25 +00:00
|
|
|
}
|
|
|
|
}
|
2023-09-26 15:36:32 +00:00
|
|
|
|
|
|
|
function retry(
|
|
|
|
fn: (args: { attempt: number; remaining: number; total: number }) => Promise<void>,
|
|
|
|
opts: {
|
|
|
|
numAttempts: number
|
|
|
|
delay: number
|
|
|
|
}
|
|
|
|
): Promise<void> {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let attempts = 0
|
|
|
|
function attempt() {
|
|
|
|
fn({ attempt: attempts, remaining: opts.numAttempts - attempts, total: opts.numAttempts })
|
|
|
|
.then(resolve)
|
|
|
|
.catch((err) => {
|
|
|
|
attempts++
|
|
|
|
if (attempts >= opts.numAttempts) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
setTimeout(attempt, opts.delay)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
attempt()
|
|
|
|
})
|
|
|
|
}
|