@elevasis/core 0.11.2 → 0.12.0
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 +2 -1
- package/dist/index.js +8 -1
- package/dist/organization-model/index.d.ts +2 -1
- package/dist/organization-model/index.js +8 -1
- package/dist/test-utils/index.d.ts +10 -3
- package/dist/test-utils/index.js +6 -0
- package/package.json +1 -1
- package/src/_gen/__tests__/__snapshots__/contracts.md.snap +27 -270
- package/src/auth/multi-tenancy/credentials/server/encryption.ts +83 -39
- package/src/auth/multi-tenancy/credentials/server/kek-loader.ts +47 -0
- package/src/auth/multi-tenancy/index.ts +3 -0
- package/src/auth/multi-tenancy/invitations/api-schemas.ts +104 -107
- package/src/auth/multi-tenancy/memberships/api-schemas.ts +6 -5
- package/src/auth/multi-tenancy/memberships/membership.ts +130 -138
- package/src/auth/multi-tenancy/role-management/api-schemas.ts +78 -0
- package/src/auth/multi-tenancy/role-management/index.ts +16 -0
- package/src/execution/engine/tools/integration/server/adapters/apify/__tests__/apify-run-actor.integration.test.ts +299 -293
- package/src/execution/engine/tools/integration/service.test.ts +214 -0
- package/src/execution/engine/tools/integration/service.ts +169 -161
- package/src/integrations/credentials/__tests__/api-schemas.test.ts +420 -496
- package/src/integrations/credentials/api-schemas.ts +127 -143
- package/src/integrations/webhook-endpoints/__tests__/api-schemas.test.ts +327 -318
- package/src/integrations/webhook-endpoints/api-schemas.ts +103 -102
- package/src/integrations/webhook-endpoints/types.ts +58 -51
- package/src/operations/activities/api-schemas.ts +80 -79
- package/src/operations/activities/types.ts +64 -63
- package/src/organization-model/contracts.ts +1 -0
- package/src/organization-model/defaults.ts +6 -0
- package/src/organization-model/domains/navigation.ts +37 -23
- package/src/organization-model/published.ts +2 -1
- package/src/platform/constants/versions.ts +1 -1
- package/src/reference/_generated/contracts.md +27 -270
- package/src/scaffold-registry/__tests__/index.test.ts +72 -7
- package/src/scaffold-registry/index.ts +159 -26
- package/src/server.ts +281 -272
- package/src/supabase/database.types.ts +7 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { parse as parseYaml } from 'yaml'
|
|
4
5
|
import { type ScaffoldRegistry, type ScaffoldRegistryEntry, ScaffoldRegistrySchema } from './schema'
|
|
@@ -86,15 +87,23 @@ export function loadScaffoldRegistry(): ScaffoldRegistry {
|
|
|
86
87
|
|
|
87
88
|
const registry = result.data
|
|
88
89
|
|
|
89
|
-
// Drift check: if compiled JSON exists, verify
|
|
90
|
+
// Drift check: if compiled JSON exists, verify the full normalized content matches.
|
|
90
91
|
const jsonPath = path.join(root, JSON_FILENAME)
|
|
91
92
|
try {
|
|
92
93
|
const compiledRaw = readFileSync(jsonPath, 'utf-8')
|
|
93
|
-
const
|
|
94
|
-
|
|
94
|
+
const compiledParsed = JSON.parse(compiledRaw) as unknown
|
|
95
|
+
const compiledResult = ScaffoldRegistrySchema.safeParse(compiledParsed)
|
|
96
|
+
if (!compiledResult.success) {
|
|
97
|
+
const issues = compiledResult.error.issues.map((i) => ` [${i.path.join('.')}] ${i.message}`).join('\n')
|
|
98
|
+
throw new Error(`scaffold-registry: compiled JSON validation failed:\n${issues}`)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const yamlHash = stableRegistryHash(registry)
|
|
102
|
+
const compiledHash = stableRegistryHash(compiledResult.data)
|
|
103
|
+
if (compiledHash !== yamlHash) {
|
|
95
104
|
throw new Error(
|
|
96
105
|
`scaffold-registry: compiled JSON is out of sync with YAML ` +
|
|
97
|
-
`(YAML
|
|
106
|
+
`(YAML hash ${yamlHash.slice(0, 12)}, JSON hash ${compiledHash.slice(0, 12)}). ` +
|
|
98
107
|
`Run compileScaffoldRegistry() to regenerate.`
|
|
99
108
|
)
|
|
100
109
|
}
|
|
@@ -131,6 +140,17 @@ export function compileScaffoldRegistry(): ScaffoldRegistry {
|
|
|
131
140
|
)
|
|
132
141
|
}
|
|
133
142
|
|
|
143
|
+
const emptySources = findEmptySourcePatterns(registry, root)
|
|
144
|
+
if (emptySources.length > 0) {
|
|
145
|
+
const formatted = emptySources
|
|
146
|
+
.map((source) => ` [${source.entryId}] ${source.pattern}`)
|
|
147
|
+
.join('\n')
|
|
148
|
+
throw new Error(
|
|
149
|
+
`scaffold-registry: ${emptySources.length} source pattern(s) match no files or directories:\n${formatted}\n` +
|
|
150
|
+
`Fix the stale source glob, create the scaffold surface, or add explicit registry support for intentional empty patterns.`
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
const jsonPath = path.join(root, JSON_FILENAME)
|
|
135
155
|
writeFileSync(jsonPath, JSON.stringify(registry, null, 2) + '\n', 'utf-8')
|
|
136
156
|
|
|
@@ -166,6 +186,29 @@ export function findMissingDependentPaths(
|
|
|
166
186
|
return missing
|
|
167
187
|
}
|
|
168
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Return source patterns that do not currently match any file or directory.
|
|
191
|
+
* Symbolic sources are skipped because they intentionally do not resolve to
|
|
192
|
+
* monorepo paths.
|
|
193
|
+
*/
|
|
194
|
+
export function findEmptySourcePatterns(
|
|
195
|
+
registry: ScaffoldRegistry,
|
|
196
|
+
monorepoRootDir: string
|
|
197
|
+
): Array<{ entryId: string; pattern: string }> {
|
|
198
|
+
const empty: Array<{ entryId: string; pattern: string }> = []
|
|
199
|
+
|
|
200
|
+
for (const entry of registry.entries) {
|
|
201
|
+
for (const sourcePattern of entry.sources) {
|
|
202
|
+
if (isSymbolicTarget(sourcePattern)) continue
|
|
203
|
+
if (!sourcePatternMatchesAnyPath(sourcePattern, monorepoRootDir)) {
|
|
204
|
+
empty.push({ entryId: entry.id, pattern: sourcePattern })
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return empty
|
|
210
|
+
}
|
|
211
|
+
|
|
169
212
|
// ---------------------------------------------------------------------------
|
|
170
213
|
// Lookup helpers (used by hooks for fast path matching)
|
|
171
214
|
// ---------------------------------------------------------------------------
|
|
@@ -197,7 +240,9 @@ export function loadScaffoldRegistryFast(): ScaffoldRegistry {
|
|
|
197
240
|
* is implemented.
|
|
198
241
|
*/
|
|
199
242
|
export function findMatchingEntries(registry: ScaffoldRegistry, filePath: string): ScaffoldRegistryEntry[] {
|
|
200
|
-
return registry.entries.filter((entry) =>
|
|
243
|
+
return registry.entries.filter((entry) =>
|
|
244
|
+
entry.sources.some((pattern) => scaffoldPathMatchesPattern(filePath, pattern))
|
|
245
|
+
)
|
|
201
246
|
}
|
|
202
247
|
|
|
203
248
|
// ---------------------------------------------------------------------------
|
|
@@ -221,6 +266,24 @@ function loadScaffoldRegistryNoSyncCheck(root: string): ScaffoldRegistry {
|
|
|
221
266
|
return result.data
|
|
222
267
|
}
|
|
223
268
|
|
|
269
|
+
export function normalizeScaffoldPath(p: string): string {
|
|
270
|
+
return p.replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '')
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function scaffoldPathMatchesPattern(filePath: string, pattern: string): boolean {
|
|
274
|
+
const normalizedFile = normalizeScaffoldPath(filePath)
|
|
275
|
+
const normalizedPattern = normalizeScaffoldPath(pattern)
|
|
276
|
+
|
|
277
|
+
if (!normalizedFile || !normalizedPattern) return false
|
|
278
|
+
if (normalizedFile === normalizedPattern) return true
|
|
279
|
+
|
|
280
|
+
if (!isGlobPattern(normalizedPattern)) {
|
|
281
|
+
return normalizedFile.startsWith(normalizedPattern + '/')
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return globToRegExp(normalizedPattern).test(normalizedFile)
|
|
285
|
+
}
|
|
286
|
+
|
|
224
287
|
function isSymbolicTarget(p: string): boolean {
|
|
225
288
|
return p.startsWith('docs:') || p.startsWith('autogen-target:')
|
|
226
289
|
}
|
|
@@ -229,30 +292,100 @@ function isGlobPattern(p: string): boolean {
|
|
|
229
292
|
return /[*?[]/.test(p)
|
|
230
293
|
}
|
|
231
294
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
*/
|
|
236
|
-
function pathMatchesPattern(filePath: string, pattern: string): boolean {
|
|
237
|
-
// Normalize separators
|
|
238
|
-
const normalizedFile = filePath.replace(/\\/g, '/')
|
|
239
|
-
const normalizedPattern = pattern.replace(/\\/g, '/')
|
|
295
|
+
function stableRegistryHash(registry: ScaffoldRegistry): string {
|
|
296
|
+
return createHash('sha256').update(JSON.stringify(registry)).digest('hex')
|
|
297
|
+
}
|
|
240
298
|
|
|
241
|
-
|
|
242
|
-
|
|
299
|
+
function globToRegExp(pattern: string): RegExp {
|
|
300
|
+
const segments = pattern.split('/')
|
|
301
|
+
let regex = '^'
|
|
302
|
+
|
|
303
|
+
for (let index = 0; index < segments.length; index++) {
|
|
304
|
+
const segment = segments[index]
|
|
305
|
+
const isLast = index === segments.length - 1
|
|
306
|
+
|
|
307
|
+
if (segment === '**') {
|
|
308
|
+
regex += isLast ? '(?:.*)?' : '(?:[^/]+/)*'
|
|
309
|
+
continue
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
regex += segmentToRegExpSource(segment)
|
|
313
|
+
if (!isLast) regex += '/'
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
regex += '$'
|
|
317
|
+
return new RegExp(regex)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function segmentToRegExpSource(segment: string): string {
|
|
321
|
+
let source = ''
|
|
322
|
+
for (let index = 0; index < segment.length; index++) {
|
|
323
|
+
const char = segment[index]
|
|
324
|
+
if (char === '*') {
|
|
325
|
+
source += '[^/]*'
|
|
326
|
+
} else if (char === '?') {
|
|
327
|
+
source += '[^/]'
|
|
328
|
+
} else {
|
|
329
|
+
source += escapeRegExp(char)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return source
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function escapeRegExp(value: string): string {
|
|
336
|
+
return value.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
|
|
337
|
+
}
|
|
243
338
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
339
|
+
function sourcePatternMatchesAnyPath(sourcePattern: string, monorepoRootDir: string): boolean {
|
|
340
|
+
const normalizedPattern = normalizeScaffoldPath(sourcePattern)
|
|
341
|
+
|
|
342
|
+
if (!isGlobPattern(normalizedPattern)) {
|
|
343
|
+
return existsSync(path.join(monorepoRootDir, normalizedPattern))
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const baseDir = findGlobBaseDirectory(normalizedPattern)
|
|
347
|
+
const absoluteBase = path.join(monorepoRootDir, baseDir)
|
|
348
|
+
if (!existsSync(absoluteBase)) return false
|
|
349
|
+
|
|
350
|
+
return walkUntilMatch(absoluteBase, monorepoRootDir, normalizedPattern)
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function findGlobBaseDirectory(pattern: string): string {
|
|
354
|
+
const segments = pattern.split('/')
|
|
355
|
+
const baseSegments: string[] = []
|
|
356
|
+
for (const segment of segments) {
|
|
357
|
+
if (isGlobPattern(segment)) break
|
|
358
|
+
baseSegments.push(segment)
|
|
248
359
|
}
|
|
360
|
+
return baseSegments.join('/')
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function walkUntilMatch(currentPath: string, monorepoRootDir: string, pattern: string): boolean {
|
|
364
|
+
const rel = normalizeScaffoldPath(path.relative(monorepoRootDir, currentPath))
|
|
365
|
+
if (rel && scaffoldPathMatchesPattern(rel, pattern)) return true
|
|
249
366
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
367
|
+
let entries
|
|
368
|
+
try {
|
|
369
|
+
entries = readdirSync(currentPath, { withFileTypes: true })
|
|
370
|
+
} catch {
|
|
371
|
+
return false
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
for (const entry of entries) {
|
|
375
|
+
const absolutePath = path.join(currentPath, entry.name)
|
|
376
|
+
const relativePath = normalizeScaffoldPath(path.relative(monorepoRootDir, absolutePath))
|
|
377
|
+
|
|
378
|
+
if (scaffoldPathMatchesPattern(relativePath, pattern)) return true
|
|
379
|
+
if (entry.isDirectory()) {
|
|
380
|
+
try {
|
|
381
|
+
if (statSync(absolutePath).isDirectory() && walkUntilMatch(absolutePath, monorepoRootDir, pattern)) {
|
|
382
|
+
return true
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
387
|
+
}
|
|
254
388
|
}
|
|
255
389
|
|
|
256
|
-
|
|
257
|
-
return normalizedFile.startsWith(normalizedPattern + '/')
|
|
390
|
+
return false
|
|
258
391
|
}
|