@astrale-os/cli 1.0.0-beta.22 → 1.0.0-beta.24
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/astrale.js +2759 -625
- package/dist/public/connect-core.js +4 -4
- package/dist/types/admin/contract.d.ts +2 -2
- package/package.json +5 -4
- package/src/admin/contract.ts +3 -2
- package/src/commands/__tests__/read-commands.test.ts +56 -0
- package/src/commands/__tests__/view.test.ts +1 -2
- package/src/commands/introspect.ts +8 -9
- package/src/ui/.spec/architecture.md +8 -7
- package/src/ui/.spec/laws.ts +7 -0
- package/src/ui/__tests__/ui.test.ts +357 -5
- package/src/ui/lock.ts +1 -1
- package/src/ui/operations.ts +89 -19
- package/src/ui/project.ts +114 -0
- package/src/ui/release.ts +9 -2
- package/studio/client/dist/assets/{index-7YHdeCxp.js → index-OeJK1TjG.js} +2 -2
- package/studio/client/dist/assets/{schema-studio-CABpTfvH.js → schema-studio-DRdfu_cQ.js} +2 -2
- package/studio/client/dist/index.html +1 -1
- package/studio/package.json +3 -3
- package/studio/server/cli-consumers.test.ts +0 -1
- package/studio/server/introspect/anatomy/views.ts +1 -2
- package/studio/server/introspect/anatomy-extras.test.ts +3 -14
- package/studio/server/introspect/canonical-schema.test.ts +1 -1
- package/studio/server/introspect/canonical-schema.ts +0 -4
- package/studio/server/introspect/diff.test.ts +10 -2
- package/studio/server/introspect/diff.ts +1 -9
- package/studio/server/introspect/runtime.test.ts +1 -1
- package/studio/server/introspect/schema-ir-json.test.ts +12 -1
- package/studio/server/introspect/schema-ir-json.ts +1 -1
- package/studio/server/views/target.test.ts +1 -2
- package/studio/shared/contracts/schema.ts +1 -3
- package/studio/tsconfig.json +2 -1
- package/viewer/dist/main.js +13 -13
package/src/ui/project.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, lstat, readFile, realpath } from 'node:fs/promises'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
+
import { loadConfig } from 'tsconfig-paths'
|
|
3
4
|
|
|
4
5
|
import { UiError, type PackageManager } from './model'
|
|
5
6
|
|
|
@@ -40,6 +41,119 @@ async function readManifest(target: string): Promise<Record<string, unknown>> {
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function pathPatternMatch(pattern: string, candidate: string): string | undefined {
|
|
45
|
+
const wildcard = pattern.indexOf('*')
|
|
46
|
+
if (wildcard === -1) return pattern === candidate ? '' : undefined
|
|
47
|
+
const prefix = pattern.slice(0, wildcard)
|
|
48
|
+
const suffix = pattern.slice(wildcard + 1)
|
|
49
|
+
if (!candidate.startsWith(prefix) || !candidate.endsWith(suffix)) return undefined
|
|
50
|
+
return candidate.slice(prefix.length, candidate.length - suffix.length)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolvePackageImport(project: UiProject, candidate: string): string | undefined {
|
|
54
|
+
const imports = project.packageJson.imports
|
|
55
|
+
if (!imports || typeof imports !== 'object' || Array.isArray(imports)) return undefined
|
|
56
|
+
const matches = Object.entries(imports as Record<string, unknown>)
|
|
57
|
+
.flatMap(([pattern, target]) => {
|
|
58
|
+
const wildcard = pathPatternMatch(pattern, candidate)
|
|
59
|
+
if (wildcard === undefined || typeof target !== 'string' || !target.startsWith('./'))
|
|
60
|
+
return []
|
|
61
|
+
const wildcardIndex = pattern.indexOf('*')
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
exact: wildcardIndex === -1,
|
|
65
|
+
prefixLength: wildcardIndex === -1 ? pattern.length : wildcardIndex,
|
|
66
|
+
suffixLength: wildcardIndex === -1 ? 0 : pattern.length - wildcardIndex - 1,
|
|
67
|
+
target: target.replaceAll('*', wildcard),
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
.sort(
|
|
72
|
+
(left, right) =>
|
|
73
|
+
Number(right.exact) - Number(left.exact) ||
|
|
74
|
+
right.prefixLength - left.prefixLength ||
|
|
75
|
+
right.suffixLength - left.suffixLength,
|
|
76
|
+
)
|
|
77
|
+
return matches[0] ? path.resolve(project.root, matches[0].target) : undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function resolveAlias(project: UiProject, candidate: string): Promise<string | undefined> {
|
|
81
|
+
const config = loadConfig(project.root)
|
|
82
|
+
if (config.resultType === 'failed') return undefined
|
|
83
|
+
const matches: Array<{
|
|
84
|
+
exact: boolean
|
|
85
|
+
prefixLength: number
|
|
86
|
+
suffixLength: number
|
|
87
|
+
resolved: string
|
|
88
|
+
}> = []
|
|
89
|
+
for (const [pattern, replacements] of Object.entries(config.paths)) {
|
|
90
|
+
const wildcard = pathPatternMatch(pattern, candidate)
|
|
91
|
+
if (wildcard === undefined) continue
|
|
92
|
+
const replacement = replacements?.[0]
|
|
93
|
+
if (!replacement) continue
|
|
94
|
+
const wildcardIndex = pattern.indexOf('*')
|
|
95
|
+
matches.push({
|
|
96
|
+
exact: wildcardIndex === -1,
|
|
97
|
+
prefixLength: wildcardIndex === -1 ? pattern.length : wildcardIndex,
|
|
98
|
+
suffixLength: wildcardIndex === -1 ? 0 : pattern.length - wildcardIndex - 1,
|
|
99
|
+
resolved: path.resolve(config.absoluteBaseUrl, replacement.replaceAll('*', wildcard)),
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
matches.sort(
|
|
103
|
+
(left, right) =>
|
|
104
|
+
Number(right.exact) - Number(left.exact) ||
|
|
105
|
+
right.prefixLength - left.prefixLength ||
|
|
106
|
+
right.suffixLength - left.suffixLength,
|
|
107
|
+
)
|
|
108
|
+
const best = matches[0]
|
|
109
|
+
if (!best) return undefined
|
|
110
|
+
const ambiguous = matches.some(
|
|
111
|
+
(match) =>
|
|
112
|
+
match.exact === best.exact &&
|
|
113
|
+
match.prefixLength === best.prefixLength &&
|
|
114
|
+
match.suffixLength === best.suffixLength &&
|
|
115
|
+
match.resolved !== best.resolved,
|
|
116
|
+
)
|
|
117
|
+
if (ambiguous) {
|
|
118
|
+
throw new UiError(
|
|
119
|
+
'UI_PROJECT_UNSUPPORTED',
|
|
120
|
+
'The components alias resolves to conflicting project paths.',
|
|
121
|
+
'Keep one authoritative compilerOptions.paths mapping for the components alias.',
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
return best.resolved
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function resolveUiRegistryTarget(
|
|
128
|
+
project: UiProject,
|
|
129
|
+
declaredTarget: string,
|
|
130
|
+
): Promise<string> {
|
|
131
|
+
if (!declaredTarget.startsWith('components/')) return declaredTarget
|
|
132
|
+
const components = await readFile(project.componentsPath, 'utf8')
|
|
133
|
+
.then((value) => JSON.parse(value) as { aliases?: { components?: unknown } })
|
|
134
|
+
.catch(() => undefined)
|
|
135
|
+
const componentsAlias = components?.aliases?.components
|
|
136
|
+
if (typeof componentsAlias !== 'string' || componentsAlias.length === 0) return declaredTarget
|
|
137
|
+
const suffix = declaredTarget.slice('components/'.length)
|
|
138
|
+
const directAlias = /^(?:\.?\.?\/|src\/|app\/|frontend\/|components\/)/u.test(componentsAlias)
|
|
139
|
+
const resolved =
|
|
140
|
+
resolvePackageImport(project, componentsAlias) ??
|
|
141
|
+
(await resolveAlias(project, componentsAlias)) ??
|
|
142
|
+
(directAlias ? path.resolve(project.root, componentsAlias) : undefined)
|
|
143
|
+
if (!resolved) {
|
|
144
|
+
throw new UiError(
|
|
145
|
+
'UI_PROJECT_UNSUPPORTED',
|
|
146
|
+
'components.json alias cannot be resolved through tsconfig.json or jsconfig.json.',
|
|
147
|
+
'Define a matching compilerOptions.paths entry for ' + componentsAlias + '.',
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
const relative = path.relative(project.root, path.join(resolved, suffix))
|
|
151
|
+
if (relative === '..' || relative.startsWith('..' + path.sep) || path.isAbsolute(relative)) {
|
|
152
|
+
throw new UiError('UI_PROJECT_UNSUPPORTED', 'components.json alias escapes the project.')
|
|
153
|
+
}
|
|
154
|
+
return relative.split(path.sep).join('/')
|
|
155
|
+
}
|
|
156
|
+
|
|
43
157
|
function hasReactTailwind(manifest: Record<string, unknown>): boolean {
|
|
44
158
|
const dependencies = {
|
|
45
159
|
...(manifest.dependencies as Record<string, string> | undefined),
|
package/src/ui/release.ts
CHANGED
|
@@ -250,8 +250,14 @@ function isInstallableItem(item: unknown): item is UiRegistry['items'][number] {
|
|
|
250
250
|
/^(?:pattern|block)-[a-z0-9-]+$/u.test(candidate.name) &&
|
|
251
251
|
typeof address === 'string' &&
|
|
252
252
|
/^(?:pattern|block)\/[a-z0-9-]+\/[a-z0-9-/]+$/u.test(address)
|
|
253
|
+
const isComponent =
|
|
254
|
+
candidate.type === 'registry:component' &&
|
|
255
|
+
typeof candidate.name === 'string' &&
|
|
256
|
+
/^component-[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(candidate.name) &&
|
|
257
|
+
typeof address === 'string' &&
|
|
258
|
+
/^component\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(address)
|
|
253
259
|
return (
|
|
254
|
-
(isTheme || isComposition) &&
|
|
260
|
+
(isTheme || isComposition || isComponent) &&
|
|
255
261
|
(candidate.dependencies === undefined ||
|
|
256
262
|
(Array.isArray(candidate.dependencies) &&
|
|
257
263
|
candidate.dependencies.every(
|
|
@@ -274,7 +280,8 @@ function isInstallableItem(item: unknown): item is UiRegistry['items'][number] {
|
|
|
274
280
|
(candidate.files?.length === 1 &&
|
|
275
281
|
file.type === 'registry:file' &&
|
|
276
282
|
file.target ===
|
|
277
|
-
'components/astrale/theme/' + address!.slice('theme/'.length) + '.css'))
|
|
283
|
+
'components/astrale/theme/' + address!.slice('theme/'.length) + '.css')) &&
|
|
284
|
+
(!isComponent || file.target.startsWith('components/astrale/component/')),
|
|
278
285
|
) &&
|
|
279
286
|
typeof address === 'string'
|
|
280
287
|
)
|