Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
70 lines
2.6 KiB
TypeScript
70 lines
2.6 KiB
TypeScript
import { join } from 'path'
|
|
import { REPO_ROOT, readJsonIfExists } from './lib/file'
|
|
import { PRODUCT_CONFIG_KEY, ProductConfig } from './lib/types'
|
|
import { getAllWorkspacePackages } from './lib/workspace'
|
|
|
|
interface ManifestComponent extends ProductConfig {
|
|
packages: { name: string; description?: string }[]
|
|
features: ManifestComponent[]
|
|
}
|
|
|
|
/**
|
|
* Prints the product manifest: the commercial components customers license, as a tree. Top-level
|
|
* entries are order form line items; their `features` are the parts that can be licensed
|
|
* individually, listed indented beneath them on the order form.
|
|
*
|
|
* This is the aggregate view of the `tldraw_product` fields in each package's package.json — those
|
|
* fields are the source of truth, and are validated by `yarn check-packages`. See
|
|
* internal/docs/product-stable-ids.md.
|
|
*/
|
|
async function main() {
|
|
const lernaJson = (await readJsonIfExists(join(REPO_ROOT, 'lerna.json'))) as {
|
|
version: string
|
|
} | null
|
|
if (!lernaJson) throw new Error('Could not read lerna.json')
|
|
|
|
const componentsById = new Map<string, ManifestComponent>()
|
|
|
|
for (const { packageJson, relativePath } of await getAllWorkspacePackages()) {
|
|
if (!relativePath.startsWith('packages/') || packageJson.private) continue
|
|
const product = packageJson[PRODUCT_CONFIG_KEY]
|
|
if (!product) continue
|
|
|
|
// packages sharing a stable id are required to have identical product metadata; this is
|
|
// enforced by `yarn check-packages`, so taking the first package's config here is safe
|
|
const component = componentsById.get(product.stableId) ?? {
|
|
...product,
|
|
packages: [],
|
|
features: [],
|
|
}
|
|
component.packages.push({
|
|
name: packageJson.name,
|
|
description: packageJson.description,
|
|
})
|
|
componentsById.set(product.stableId, component)
|
|
}
|
|
|
|
const byName = (a: { name: string }, b: { name: string }) => a.name.localeCompare(b.name)
|
|
const all = [...componentsById.values()].sort(byName)
|
|
for (const component of all) {
|
|
component.packages.sort(byName)
|
|
}
|
|
|
|
// nest features under the component they belong to. `check-packages` guarantees every parent
|
|
// resolves, so nothing can be silently dropped here.
|
|
for (const component of all) {
|
|
if (component.parent) {
|
|
componentsById.get(component.parent)!.features.push(component)
|
|
}
|
|
}
|
|
// the standard offering leads, then the premium modules, matching the order form's layout
|
|
const products = all
|
|
.filter((component) => !component.parent)
|
|
.sort((a, b) => Number(a.premium) - Number(b.premium) || byName(a, b))
|
|
|
|
// eslint-disable-next-line no-console
|
|
console.log(JSON.stringify({ version: lernaJson.version, products }, null, '\t'))
|
|
}
|
|
|
|
main()
|