@elevasis/core 0.11.0 → 0.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -2
- package/dist/index.js +8 -13
- package/dist/organization-model/index.d.ts +3 -2
- package/dist/organization-model/index.js +8 -13
- package/dist/test-utils/index.d.ts +175 -0
- package/dist/test-utils/index.js +8 -8
- package/package.json +1 -1
- package/src/__tests__/template-core-compatibility.test.ts +6 -15
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +36 -38
- package/src/auth/multi-tenancy/index.ts +20 -17
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +142 -126
- package/src/auth/multi-tenancy/memberships/index.ts +26 -22
- package/src/auth/multi-tenancy/permissions.test.ts +42 -0
- package/src/auth/multi-tenancy/permissions.ts +104 -0
- package/src/organization-model/README.md +1 -0
- package/src/organization-model/__tests__/defaults.test.ts +19 -6
- package/src/organization-model/contracts.ts +3 -3
- package/src/organization-model/defaults.ts +3 -8
- package/src/organization-model/domains/navigation.ts +26 -32
- package/src/organization-model/foundation.ts +2 -3
- package/src/organization-model/organization-model.mdx +3 -0
- package/src/organization-model/published.ts +5 -5
- package/src/platform/constants/versions.ts +3 -3
- package/src/platform/registry/index.ts +86 -91
- package/src/platform/registry/resource-registry.ts +905 -866
- package/src/reference/_generated/contracts.md +36 -38
- package/src/scaffold-registry/__tests__/index.test.ts +125 -1
- package/src/scaffold-registry/__tests__/schema.test.ts +48 -20
- package/src/scaffold-registry/index.ts +236 -188
- package/src/scaffold-registry/schema.ts +47 -22
- package/src/supabase/database.types.ts +2880 -2719
- package/src/test-utils/fixtures/memberships.ts +82 -80
|
@@ -198,6 +198,12 @@ export type FeatureIconComponent = ComponentType<{ size?: number;
|
|
|
198
198
|
export type FeatureIconComponent = ComponentType<{ size?: number;
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
### `FeatureSidebarWidthResolver`
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
export type FeatureSidebarWidthResolver = number | ((context: { currentPath: string }) => number)
|
|
205
|
+
```
|
|
206
|
+
|
|
201
207
|
### `FeatureModule`
|
|
202
208
|
|
|
203
209
|
```typescript
|
|
@@ -212,6 +218,8 @@ export interface FeatureModule {
|
|
|
212
218
|
icon?: FeatureIconComponent
|
|
213
219
|
/** Sidebar component rendered when this feature's subtree route is active. */
|
|
214
220
|
sidebar?: FeatureSidebarComponent
|
|
221
|
+
/** Optional shell sidebar width override. Defaults to 250px. */
|
|
222
|
+
sidebarWidth?: FeatureSidebarWidthResolver
|
|
215
223
|
/** Operations-only bridge connecting this feature to the organization graph node. */
|
|
216
224
|
organizationGraph?: OrganizationGraphFeatureBridge
|
|
217
225
|
}
|
|
@@ -1007,44 +1015,34 @@ export interface HumanCheckpointDefinition extends ResourceDefinition {
|
|
|
1007
1015
|
### `DeploymentSpec`
|
|
1008
1016
|
|
|
1009
1017
|
```typescript
|
|
1010
|
-
/**
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
sdkVersion?: string
|
|
1020
|
-
/** Deployment version (semver) of the deployed bundle */
|
|
1021
|
-
deploymentVersion?: string
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Organization-specific resource collection
|
|
1026
|
-
*
|
|
1027
|
-
* Complete manifest of all automation resources for an organization.
|
|
1028
|
-
* Used by ResourceRegistry for discovery and Command View for visualization.
|
|
1018
|
+
/** Always undefined for system resources; present for API compatibility with RemoteOrgConfig consumers */
|
|
1019
|
+
sdkVersion?: never
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
/**
|
|
1023
|
+
* Organization-specific resource collection
|
|
1024
|
+
*
|
|
1025
|
+
* Complete manifest of all automation resources for an organization.
|
|
1026
|
+
* Used by ResourceRegistry for discovery and Command View for visualization.
|
|
1029
1027
|
*/
|
|
1030
|
-
export interface DeploymentSpec {
|
|
1031
|
-
/** Deployment version (semver) */
|
|
1032
|
-
version: string
|
|
1033
|
-
/** Workflow definitions */
|
|
1034
|
-
workflows?: WorkflowDefinition[]
|
|
1035
|
-
/** Agent definitions */
|
|
1036
|
-
agents?: AgentDefinition[]
|
|
1037
|
-
|
|
1038
|
-
// Resource Manifest fields (optional for backwards compatibility)
|
|
1039
|
-
/** Trigger definitions - entry points that initiate executions */
|
|
1040
|
-
triggers?: TriggerDefinition[]
|
|
1041
|
-
/** Integration definitions - external service connections */
|
|
1042
|
-
integrations?: IntegrationDefinition[]
|
|
1043
|
-
/** Explicit relationship declarations between resources */
|
|
1044
|
-
relationships?: ResourceRelationships
|
|
1045
|
-
/** External automation resources (n8n, Make, Zapier, etc.) */
|
|
1046
|
-
externalResources?: ExternalResourceDefinition[]
|
|
1047
|
-
/** Human checkpoint definitions - human decision points in automation */
|
|
1048
|
-
humanCheckpoints?: HumanCheckpointDefinition[]
|
|
1028
|
+
export interface DeploymentSpec {
|
|
1029
|
+
/** Deployment version (semver) */
|
|
1030
|
+
version: string
|
|
1031
|
+
/** Workflow definitions */
|
|
1032
|
+
workflows?: WorkflowDefinition[]
|
|
1033
|
+
/** Agent definitions */
|
|
1034
|
+
agents?: AgentDefinition[]
|
|
1035
|
+
|
|
1036
|
+
// Resource Manifest fields (optional for backwards compatibility)
|
|
1037
|
+
/** Trigger definitions - entry points that initiate executions */
|
|
1038
|
+
triggers?: TriggerDefinition[]
|
|
1039
|
+
/** Integration definitions - external service connections */
|
|
1040
|
+
integrations?: IntegrationDefinition[]
|
|
1041
|
+
/** Explicit relationship declarations between resources */
|
|
1042
|
+
relationships?: ResourceRelationships
|
|
1043
|
+
/** External automation resources (n8n, Make, Zapier, etc.) */
|
|
1044
|
+
externalResources?: ExternalResourceDefinition[]
|
|
1045
|
+
/** Human checkpoint definitions - human decision points in automation */
|
|
1046
|
+
humanCheckpoints?: HumanCheckpointDefinition[]
|
|
1049
1047
|
}
|
|
1050
1048
|
```
|
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest'
|
|
2
|
-
import { loadScaffoldRegistryFast } from '../index'
|
|
2
|
+
import { findMatchingEntries, findMissingDependentPaths, loadScaffoldRegistryFast } from '../index'
|
|
3
|
+
import type { ScaffoldRegistry, ScaffoldRegistryEntry } from '../schema'
|
|
4
|
+
|
|
5
|
+
function makeEntry(id: string, sources: string[]): ScaffoldRegistryEntry {
|
|
6
|
+
return {
|
|
7
|
+
id,
|
|
8
|
+
kind: 'manual-scaffold',
|
|
9
|
+
owner: 'packages/core',
|
|
10
|
+
sources,
|
|
11
|
+
dependents: [{ path: 'packages/core/package.json', regen: 'manual', hint: 'h' }]
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function registryFromEntries(entries: ScaffoldRegistryEntry[]): ScaffoldRegistry {
|
|
16
|
+
return { version: '1', entries }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function matchedIds(registry: ScaffoldRegistry, filePath: string): string[] {
|
|
20
|
+
return findMatchingEntries(registry, filePath).map((e) => e.id)
|
|
21
|
+
}
|
|
3
22
|
|
|
4
23
|
describe('loadScaffoldRegistryFast', () => {
|
|
5
24
|
it('preserves external sync metadata from the compiled registry', () => {
|
|
@@ -15,3 +34,108 @@ describe('loadScaffoldRegistryFast', () => {
|
|
|
15
34
|
})
|
|
16
35
|
})
|
|
17
36
|
})
|
|
37
|
+
|
|
38
|
+
describe('findMissingDependentPaths', () => {
|
|
39
|
+
const baseEntry = {
|
|
40
|
+
id: 'fake-entry',
|
|
41
|
+
kind: 'manual-scaffold' as const,
|
|
42
|
+
owner: 'packages/core',
|
|
43
|
+
sources: ['packages/core/src/**'],
|
|
44
|
+
dependents: [{ path: 'packages/core/this-file-does-not-exist.ts', regen: 'manual', hint: 'h' }]
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
it('flags dependents whose paths are missing on disk', () => {
|
|
48
|
+
const registry: ScaffoldRegistry = { version: '1', entries: [baseEntry] }
|
|
49
|
+
const missing = findMissingDependentPaths(registry, process.cwd())
|
|
50
|
+
expect(missing).toEqual([{ entryId: 'fake-entry', path: 'packages/core/this-file-does-not-exist.ts' }])
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('skips symbolic targets, globs, and (self)', () => {
|
|
54
|
+
const registry: ScaffoldRegistry = {
|
|
55
|
+
version: '1',
|
|
56
|
+
entries: [
|
|
57
|
+
{
|
|
58
|
+
...baseEntry,
|
|
59
|
+
dependents: [
|
|
60
|
+
{ path: 'docs: sync-preservation-matrix', regen: 'manual', hint: 'h' },
|
|
61
|
+
{ path: 'autogen-target:foo', regen: 'manual', hint: 'h' },
|
|
62
|
+
{ path: 'packages/core/src/**/*.ts', regen: 'manual', hint: 'h' },
|
|
63
|
+
{ path: '(self)', regen: 'manual', hint: 'h' }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
expect(findMissingDependentPaths(registry, process.cwd())).toEqual([])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('skips sync-preservation entries (their paths target derived external projects)', () => {
|
|
72
|
+
const registry: ScaffoldRegistry = {
|
|
73
|
+
version: '1',
|
|
74
|
+
entries: [
|
|
75
|
+
{
|
|
76
|
+
id: 'sync-entry',
|
|
77
|
+
kind: 'sync-preservation',
|
|
78
|
+
owner: '@elevasis/core',
|
|
79
|
+
category: 'merge',
|
|
80
|
+
strategy: 'merge-regions',
|
|
81
|
+
delete_policy: 'none',
|
|
82
|
+
sources: ['external/_template/foo.ts'],
|
|
83
|
+
dependents: [{ path: 'core/src/foo-not-in-monorepo.ts', regen: 'manual', hint: 'h' }]
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
expect(findMissingDependentPaths(registry, process.cwd())).toEqual([])
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('findMatchingEntries (pattern matching)', () => {
|
|
92
|
+
it('matches exact paths', () => {
|
|
93
|
+
const reg = registryFromEntries([makeEntry('exact', ['packages/core/src/index.ts'])])
|
|
94
|
+
expect(matchedIds(reg, 'packages/core/src/index.ts')).toEqual(['exact'])
|
|
95
|
+
expect(matchedIds(reg, 'packages/core/src/other.ts')).toEqual([])
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('matches /** suffix as deep prefix', () => {
|
|
99
|
+
const reg = registryFromEntries([makeEntry('deep', ['packages/core/src/**'])])
|
|
100
|
+
expect(matchedIds(reg, 'packages/core/src/foo.ts')).toEqual(['deep'])
|
|
101
|
+
expect(matchedIds(reg, 'packages/core/src/nested/deeply/foo.ts')).toEqual(['deep'])
|
|
102
|
+
expect(matchedIds(reg, 'packages/core/package.json')).toEqual([])
|
|
103
|
+
expect(matchedIds(reg, 'packages/core/src')).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('matches /* suffix as single-level prefix', () => {
|
|
107
|
+
const reg = registryFromEntries([makeEntry('shallow', ['packages/*/package.json'])])
|
|
108
|
+
expect(matchedIds(reg, 'packages/core/package.json')).toEqual(['shallow'])
|
|
109
|
+
expect(matchedIds(reg, 'packages/ui/package.json')).toEqual(['shallow'])
|
|
110
|
+
expect(matchedIds(reg, 'packages/core/nested/package.json')).toEqual([])
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('current matcher does NOT support mixed **/*.ext patterns (Step 3 upgrade)', () => {
|
|
114
|
+
// The simple matcher's `/*` suffix branch only handles a single trailing
|
|
115
|
+
// wildcard. Patterns like `**/*.ts` fall through and effectively miss real
|
|
116
|
+
// files. Locked in to flag any change when full micromatch lands.
|
|
117
|
+
const reg = registryFromEntries([makeEntry('ext', ['apps/api/src/**/*.ts'])])
|
|
118
|
+
expect(matchedIds(reg, 'apps/api/src/foo.ts')).toEqual([])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('normalizes Windows backslashes to forward slashes', () => {
|
|
122
|
+
const reg = registryFromEntries([makeEntry('winpath', ['packages/core/src/**'])])
|
|
123
|
+
expect(matchedIds(reg, 'packages\\core\\src\\foo.ts')).toEqual(['winpath'])
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('matches a directory-prefix pattern (no glob characters)', () => {
|
|
127
|
+
const reg = registryFromEntries([makeEntry('dir', ['packages/core/src'])])
|
|
128
|
+
expect(matchedIds(reg, 'packages/core/src/foo.ts')).toEqual(['dir'])
|
|
129
|
+
expect(matchedIds(reg, 'packages/core/srcfoo.ts')).toEqual([])
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('returns multiple entries when several match the same file', () => {
|
|
133
|
+
const reg = registryFromEntries([makeEntry('a', ['packages/core/src/**']), makeEntry('b', ['packages/core/src'])])
|
|
134
|
+
expect(matchedIds(reg, 'packages/core/src/foo.ts').sort()).toEqual(['a', 'b'])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('returns empty when no entries match', () => {
|
|
138
|
+
const reg = registryFromEntries([makeEntry('a', ['packages/core/src/**'])])
|
|
139
|
+
expect(matchedIds(reg, 'apps/api/src/foo.ts')).toEqual([])
|
|
140
|
+
})
|
|
141
|
+
})
|
|
@@ -18,12 +18,9 @@ describe('ScaffoldEntryKindSchema', () => {
|
|
|
18
18
|
const validKinds = [
|
|
19
19
|
'autogen',
|
|
20
20
|
'manual-scaffold',
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'sdk-cli-generator',
|
|
25
|
-
'validator',
|
|
26
|
-
'other'
|
|
21
|
+
'sync-preservation',
|
|
22
|
+
'validator',
|
|
23
|
+
'other'
|
|
27
24
|
]
|
|
28
25
|
for (const kind of validKinds) {
|
|
29
26
|
expect(ScaffoldEntryKindSchema.safeParse(kind).success).toBe(true)
|
|
@@ -91,9 +88,8 @@ const validEntry = {
|
|
|
91
88
|
dependents: [
|
|
92
89
|
{
|
|
93
90
|
path: '.navigation/_generated/skeleton.md',
|
|
94
|
-
regen: 'pnpm navigation:generate',
|
|
95
|
-
|
|
96
|
-
hint: 'Regenerate after rename.'
|
|
91
|
+
regen: 'pnpm navigation:generate',
|
|
92
|
+
hint: 'Regenerate after rename.'
|
|
97
93
|
}
|
|
98
94
|
]
|
|
99
95
|
}
|
|
@@ -176,10 +172,24 @@ describe('ScaffoldRegistryEntrySchema — valid entry', () => {
|
|
|
176
172
|
expect(ScaffoldRegistryEntrySchema.safeParse(withBadAutoRegen).success).toBe(false)
|
|
177
173
|
})
|
|
178
174
|
|
|
179
|
-
it('parses all
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
175
|
+
it('parses all seed-entry kinds with required invariants', () => {
|
|
176
|
+
const entries = [
|
|
177
|
+
{ ...validEntry, kind: 'autogen', regen: 'pnpm navigation:generate' },
|
|
178
|
+
{ ...validEntry, kind: 'manual-scaffold' },
|
|
179
|
+
{
|
|
180
|
+
...validEntry,
|
|
181
|
+
kind: 'sync-preservation',
|
|
182
|
+
owner: 'template',
|
|
183
|
+
category: 'replace',
|
|
184
|
+
strategy: 'replace-all',
|
|
185
|
+
delete_policy: 'none'
|
|
186
|
+
},
|
|
187
|
+
{ ...validEntry, kind: 'validator', dependents: [{ path: '(self)', regen: 'pnpm meta:verify' }] },
|
|
188
|
+
{ ...validEntry, kind: 'other', notes: 'Vendor generated.' }
|
|
189
|
+
]
|
|
190
|
+
for (const entry of entries) {
|
|
191
|
+
const result = ScaffoldRegistryEntrySchema.safeParse(entry)
|
|
192
|
+
const kind = entry.kind
|
|
183
193
|
expect(result.success, `kind ${kind} should parse`).toBe(true)
|
|
184
194
|
}
|
|
185
195
|
})
|
|
@@ -201,6 +211,21 @@ describe('ScaffoldRegistryEntrySchema — valid entry', () => {
|
|
|
201
211
|
}
|
|
202
212
|
})
|
|
203
213
|
|
|
214
|
+
it('rejects autogen entries without top-level regen', () => {
|
|
215
|
+
const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'autogen' })
|
|
216
|
+
expect(result.success).toBe(false)
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
it('rejects other entries without notes', () => {
|
|
220
|
+
const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'other' })
|
|
221
|
+
expect(result.success).toBe(false)
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
it('rejects sync-preservation entries without external sync metadata', () => {
|
|
225
|
+
const result = ScaffoldRegistryEntrySchema.safeParse({ ...validEntry, kind: 'sync-preservation' })
|
|
226
|
+
expect(result.success).toBe(false)
|
|
227
|
+
})
|
|
228
|
+
|
|
204
229
|
it('keeps legacy free-form owner values valid when external sync fields are omitted', () => {
|
|
205
230
|
const result = ScaffoldRegistryEntrySchema.safeParse({
|
|
206
231
|
...validEntry,
|
|
@@ -361,13 +386,16 @@ describe('ScaffoldRegistrySchema', () => {
|
|
|
361
386
|
sources: ['apps/docs/content/docs/**/meta.json'],
|
|
362
387
|
dependents: [{ path: '(self)', regen: 'pnpm check-docs-meta' }]
|
|
363
388
|
},
|
|
364
|
-
{
|
|
365
|
-
id: 'external-template-hook-added',
|
|
366
|
-
kind: 'sync-preservation',
|
|
367
|
-
owner: '
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
389
|
+
{
|
|
390
|
+
id: 'external-template-hook-added',
|
|
391
|
+
kind: 'sync-preservation',
|
|
392
|
+
owner: 'template',
|
|
393
|
+
category: 'replace',
|
|
394
|
+
strategy: 'replace-all',
|
|
395
|
+
delete_policy: 'none',
|
|
396
|
+
sources: ['external/_template/.claude/hooks/*.mjs'],
|
|
397
|
+
dependents: [{ path: 'external/_template/.claude/settings.json', regen: 'manual' }]
|
|
398
|
+
}
|
|
371
399
|
]
|
|
372
400
|
}
|
|
373
401
|
const result = ScaffoldRegistrySchema.safeParse(multiEntry)
|