Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/blocks/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@
],
"docs": "## Usage\n\nCreate one registry for the application, adapt navigation at the host boundary, and render the palette near the root layout. Pages can register commands while mounted with `usePageCommands`.\n\n```tsx\nimport { createCommandRegistry, kbd } from '@constructive-io/command-palette';\nimport { CommandPalette } from '@/blocks/command-palette/command-palette';\n\nconst registry = createCommandRegistry({\n groups: [{ id: 'navigation', label: 'Navigation', priority: 1 }],\n commands: [{\n id: 'settings',\n label: 'Open settings',\n type: 'navigation',\n group: 'navigation',\n href: '/settings',\n shortcut: kbd(',', 'mod')\n }]\n});\n\n<CommandPalette registry={registry} navigate={(href) => router.push(href)} />;\n```\n\nThe installed block owns presentation and keyboard interaction. The headless package owns command registration, execution, multi-step state, and background-task lifecycle. Route authorization, action permissions, errors, and business workflows remain the host application's responsibility.",
"dependencies": [
"@constructive-io/command-palette@^0.6.0",
"@constructive-io/command-palette",
"lucide-react",
"motion"
],
Expand Down Expand Up @@ -436,7 +436,7 @@
"description": "The leaf-independent Console Kit shell, runtime, discovery, and single modular Zustand store.",
"docs": "`console-kit-core` installed the shell, runtime, semantic routing, callback boundary, and one per-instance modular Zustand store. Core intentionally includes no feature view, so add selected `console-module-*` items.\n\nDegraded states: explicit endpoint, current `_meta`, introspection, capability, and adapter evidence fail closed independently; installation never grants authority.\n\nGuide: https://constructive-io.github.io/blocks/blocks/console-kit/",
"dependencies": [
"@constructive-io/data@^0.8.0",
"@constructive-io/data",
"@tanstack/react-query",
"graphql",
"lucide-react",
Expand Down Expand Up @@ -566,7 +566,7 @@
"description": "A current-_meta-only application data explorer with application-table filtering and spreadsheet CRUD.",
"docs": "`feature-pack-data` installed a provider-neutral view and `.constructive/feature-packs/data.json`. Import from `@/blocks/feature-packs/data/data-feature-pack`. The host must supply the view resource, policy, and actions; add `console-module-data` when Console Kit should own discovery and Constructive integration.\n\nDegraded states: The host owns Sheets state and endpoint binding; incompatible metadata stays explicit, and PostgreSQL privileges and RLS decide every query and mutation.\n\nGuide: https://constructive-io.github.io/blocks/blocks/features/data/",
"dependencies": [
"@constructive-io/data@^0.8.0",
"@constructive-io/data",
"lucide-react"
],
"registryDependencies": [
Expand Down
4 changes: 4 additions & 0 deletions apps/registry/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
createRegistryModuleOwnership,
deriveAliasDependencies,
deriveOwnedRegistryDependencies,
pinWorkspaceDependencies,
portableTargetForUiFile,
rewriteConstructiveUiImports,
type Registry,
Expand Down Expand Up @@ -284,6 +285,9 @@ for (const { source, item } of preparedItems) {
compiledSource.set(file.path, rewritten.source);
}

if (item.dependencies) item.dependencies = pinWorkspaceDependencies(item.dependencies);
if (item.devDependencies) item.devDependencies = pinWorkspaceDependencies(item.devDependencies);

const derivedDependencies = new Set(
deriveOwnedRegistryDependencies(item, compiledSource, moduleOwnership),
);
Expand Down
27 changes: 23 additions & 4 deletions apps/registry/scripts/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import {
CONSOLE_KIT_ITEM_NAME,
CONSOLE_MODULE_ITEM_NAME_PREFIX,
CONSOLE_INSTALL_ROOT_DEPENDENCIES,
CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY,
CONSTRUCTIVE_DATA_DEPENDENCY,
CONSTRUCTIVE_COMMAND_PALETTE_PACKAGE,
CONSTRUCTIVE_DATA_PACKAGE,
CONSTRUCTIVE_UI_PACKAGE,
CONSTRUCTIVE_SHEETS_PACKAGE,
CONSTRUCTIVE_THEME_DEPENDENCY,
Expand All @@ -26,8 +26,10 @@ import {
createRegistryModuleOwnership,
deriveAliasDependencies,
deriveOwnedRegistryDependencies,
pinWorkspaceDependencies,
portableTargetForUiFile,
rewriteConstructiveUiImports,
workspaceDependencyRange,
type Registry,
type RegistryItem,
} from './compiler';
Expand Down Expand Up @@ -349,13 +351,28 @@ test('rejects duplicate item names and install targets', () => {
);
});

test('derives pinned workspace ranges from the manifests Lerna bumps', () => {
const { version } = JSON.parse(
fs.readFileSync(path.join(repositoryRoot, 'packages/data/package.json'), 'utf8'),
) as { version: string };

assert.equal(workspaceDependencyRange(CONSTRUCTIVE_DATA_PACKAGE), `@constructive-io/data@^${version}`);
assert.deepEqual(
pinWorkspaceDependencies([CONSTRUCTIVE_DATA_PACKAGE, '@constructive-io/data@^0.1.0', 'zod']),
[`@constructive-io/data@^${version}`, `@constructive-io/data@^${version}`, 'zod'],
);
});

function distributionContractFixture(): RegistryItem[] {
return [
{ name: 'constructive-theme', type: 'registry:style' },
{
name: 'consumer',
type: 'registry:block',
dependencies: [CONSTRUCTIVE_DATA_DEPENDENCY, CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY],
dependencies: pinWorkspaceDependencies([
CONSTRUCTIVE_DATA_PACKAGE,
CONSTRUCTIVE_COMMAND_PALETTE_PACKAGE,
]),
registryDependencies: [CONSTRUCTIVE_THEME_DEPENDENCY],
},
{
Expand Down Expand Up @@ -399,7 +416,9 @@ test('enforces the compiled registry distribution contract', () => {
wrongRange.find((item) => item.name === 'consumer')!.dependencies = ['@constructive-io/data@^1.0.0'];
assert.throws(
() => assertRegistryDistributionContract(wrongRange),
new RegExp(`must depend on ${CONSTRUCTIVE_DATA_DEPENDENCY.replace(/[.^]/g, '\\$&')}`),
new RegExp(
`must depend on ${workspaceDependencyRange(CONSTRUCTIVE_DATA_PACKAGE).replace(/[.^]/g, '\\$&')}`,
),
);

const sourceOwnedPackage = structuredClone(distributionContractFixture());
Expand Down
43 changes: 37 additions & 6 deletions apps/registry/scripts/compiler.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import { readFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { isDeepStrictEqual } from 'node:util';
import ts from 'typescript';

export const CONSTRUCTIVE_UI_PACKAGE = '@constructive-io/ui';
export const CONSTRUCTIVE_SHEETS_PACKAGE = '@constructive-io/sheets';
export const CONSTRUCTIVE_NAMESPACE = '@constructive/';
export const CONSTRUCTIVE_THEME_DEPENDENCY = '@constructive/constructive-theme';
export const CONSTRUCTIVE_DATA_DEPENDENCY = '@constructive-io/data@^0.8.0';
export const CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY = '@constructive-io/command-palette@^0.6.0';
export const CONSTRUCTIVE_DATA_PACKAGE = '@constructive-io/data';
export const CONSTRUCTIVE_COMMAND_PALETTE_PACKAGE = '@constructive-io/command-palette';
export const NODE_TYPE_REGISTRY_DEPENDENCY = 'node-type-registry@^1.11.0';

/** Workspace packages whose registry dependency range follows their published version. */
export const WORKSPACE_PINNED_PACKAGES = [
CONSTRUCTIVE_DATA_PACKAGE,
CONSTRUCTIVE_COMMAND_PALETTE_PACKAGE,
] as const;

const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..');

/** Caret range for a workspace package, read from the manifest Lerna bumps. */
export function workspaceDependencyRange(packageName: string): string {
const directory = packageName.split('/').at(-1);
const manifestPath = path.join(repositoryRoot, 'packages', String(directory), 'package.json');
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { name: string; version: string };
if (manifest.name !== packageName) {
throw new Error(`${manifestPath} declares ${manifest.name}, expected ${packageName}.`);
}
return `${packageName}@^${manifest.version}`;
}

/** Stamps the current workspace version onto every pinned dependency reference. */
export function pinWorkspaceDependencies(dependencies: readonly string[]): string[] {
return dependencies.map((dependency) => {
const packageName = WORKSPACE_PINNED_PACKAGES.find(
(candidate) => dependency === candidate || dependency.startsWith(`${candidate}@`),
);
return packageName ? workspaceDependencyRange(packageName) : dependency;
});
}

export const FEATURE_PACK_IDS = [
'data',
'auth',
Expand Down Expand Up @@ -788,10 +820,9 @@ function referencesPackage(dependency: string, packageName: string): boolean {

export function assertRegistryDistributionContract(items: readonly RegistryItem[]): void {
const ownNames = new Set(items.map((item) => item.name));
const requiredPackageRanges = new Map([
['@constructive-io/data', CONSTRUCTIVE_DATA_DEPENDENCY],
['@constructive-io/command-palette', CONSTRUCTIVE_COMMAND_PALETTE_DEPENDENCY],
]);
const requiredPackageRanges = new Map(
WORKSPACE_PINNED_PACKAGES.map((packageName) => [packageName, workspaceDependencyRange(packageName)]),
);

for (const item of items) {
const dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion packages/schema-builder/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"schema"
],
"dependencies": [
"@constructive-io/data@^0.8.0",
"@constructive-io/data",
"@dnd-kit/core",
"@dnd-kit/utilities",
"@fluentui/react-context-selector",
Expand Down
4 changes: 2 additions & 2 deletions packages/schema-builder/scripts/build-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';

import {
CONSTRUCTIVE_DATA_DEPENDENCY,
CONSTRUCTIVE_DATA_PACKAGE,
NODE_TYPE_REGISTRY_DEPENDENCY,
collectModuleSpecifiers,
} from '../../../apps/registry/scripts/compiler';
Expand Down Expand Up @@ -62,7 +62,7 @@ buildSourceRegistry({
registrySubdirectory: 'blocks/schema-builder',
targetPrefix: '@components/schema-builder',
dependencies: [
CONSTRUCTIVE_DATA_DEPENDENCY,
CONSTRUCTIVE_DATA_PACKAGE,
'@dnd-kit/core',
'@dnd-kit/utilities',
'@fluentui/react-context-selector',
Expand Down
2 changes: 1 addition & 1 deletion packages/sheets/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"data"
],
"dependencies": [
"@constructive-io/data@^0.8.0",
"@constructive-io/data",
"@internationalized/date",
"@remixicon/react",
"@tanstack/react-form",
Expand Down
4 changes: 2 additions & 2 deletions packages/sheets/scripts/build-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';

import { CONSTRUCTIVE_DATA_DEPENDENCY } from '../../../apps/registry/scripts/compiler';
import { CONSTRUCTIVE_DATA_PACKAGE } from '../../../apps/registry/scripts/compiler';
import { buildSourceRegistry } from '../../../apps/registry/scripts/build-source-registry';

const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
Expand All @@ -21,7 +21,7 @@ buildSourceRegistry({
registrySubdirectory: 'blocks/sheets',
targetPrefix: '@ui/sheets',
dependencies: [
CONSTRUCTIVE_DATA_DEPENDENCY,
CONSTRUCTIVE_DATA_PACKAGE,
'@internationalized/date',
'@remixicon/react',
'@tanstack/react-form',
Expand Down
Loading