uniweb 1.0.0 → 1.0.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/LICENSE +201 -0
- package/README.md +532 -3
- package/package.json +60 -11
- package/partials/adding-pages.hbs +77 -0
- package/partials/agents.md +3000 -0
- package/partials/ai-assistance.hbs +28 -0
- package/partials/components-docs.hbs +13 -0
- package/partials/config-reference.hbs +66 -0
- package/partials/deployment.hbs +13 -0
- package/partials/learn-more.hbs +6 -0
- package/partials/quick-start.hbs +8 -0
- package/partials/search-docs.hbs +110 -0
- package/src/backend/asset-download.js +222 -0
- package/src/backend/client.js +986 -0
- package/src/backend/foundation-bring-along.js +692 -0
- package/src/backend/payment-handoff.js +362 -0
- package/src/backend/service-request.js +210 -0
- package/src/backend/site-media.js +255 -0
- package/src/backend/site-preview.js +77 -0
- package/src/backend/site-sync.js +2515 -0
- package/src/backend/workspace.js +234 -0
- package/src/commands/add.js +1900 -0
- package/src/commands/build.js +1214 -0
- package/src/commands/clone.js +466 -0
- package/src/commands/content.js +202 -0
- package/src/commands/deploy.js +537 -0
- package/src/commands/dev.js +202 -0
- package/src/commands/docs.js +530 -0
- package/src/commands/doctor.js +1982 -0
- package/src/commands/export.js +116 -0
- package/src/commands/families.js +82 -0
- package/src/commands/forget.js +197 -0
- package/src/commands/handoff.js +21 -0
- package/src/commands/i18n.js +1707 -0
- package/src/commands/inspect.js +375 -0
- package/src/commands/invite.js +24 -0
- package/src/commands/org.js +96 -0
- package/src/commands/publish.js +1087 -0
- package/src/commands/pull.js +1257 -0
- package/src/commands/push.js +663 -0
- package/src/commands/refresh.js +212 -0
- package/src/commands/register.js +1092 -0
- package/src/commands/rename.js +647 -0
- package/src/commands/site.js +358 -0
- package/src/commands/snapshot.js +461 -0
- package/src/commands/starter.js +206 -0
- package/src/commands/status.js +302 -0
- package/src/commands/sync.js +93 -0
- package/src/commands/template.js +107 -0
- package/src/commands/update.js +887 -0
- package/src/commands/validate.js +376 -0
- package/src/framework-index.json +248 -0
- package/src/index.js +2241 -0
- package/src/templates/fetchers/github.js +113 -0
- package/src/templates/fetchers/npm.js +85 -0
- package/src/templates/fetchers/release.js +92 -0
- package/src/templates/index.js +190 -0
- package/src/templates/processor.js +333 -0
- package/src/templates/resolver.js +232 -0
- package/src/templates/validator.js +155 -0
- package/src/utils/agents-stamp.js +110 -0
- package/src/utils/args.js +126 -0
- package/src/utils/asset-upload.js +270 -0
- package/src/utils/bytes.js +30 -0
- package/src/utils/code-upload.js +394 -0
- package/src/utils/config.js +417 -0
- package/src/utils/conformance.js +274 -0
- package/src/utils/dep-survey.js +176 -0
- package/src/utils/destination-prompt.js +146 -0
- package/src/utils/discover.js +75 -0
- package/src/utils/env.js +22 -0
- package/src/utils/exit.js +30 -0
- package/src/utils/fetch-retry.js +109 -0
- package/src/utils/flag-guard.js +246 -0
- package/src/utils/foundation-name.js +179 -0
- package/src/utils/git.js +342 -0
- package/src/utils/host-prompt.js +51 -0
- package/src/utils/install-integrity.js +210 -0
- package/src/utils/interactive.js +118 -0
- package/src/utils/json-file.js +72 -0
- package/src/utils/names.js +91 -0
- package/src/utils/placement.js +151 -0
- package/src/utils/pm.js +165 -0
- package/src/utils/preview.js +20 -0
- package/src/utils/pull-written.js +132 -0
- package/src/utils/records-guard.js +88 -0
- package/src/utils/registry-auth.js +994 -0
- package/src/utils/registry-orgs.js +290 -0
- package/src/utils/scaffold.js +483 -0
- package/src/utils/schemaless-report.js +30 -0
- package/src/utils/semver-precedence.js +80 -0
- package/src/utils/session-file.js +95 -0
- package/src/utils/site-content-refs.js +23 -0
- package/src/utils/site-copies.js +123 -0
- package/src/utils/site-data-upload.js +302 -0
- package/src/utils/site-identity.js +137 -0
- package/src/utils/snapshot-settings.js +112 -0
- package/src/utils/update-check.js +302 -0
- package/src/utils/uwx-read.js +157 -0
- package/src/utils/workspace.js +223 -0
- package/src/versions.js +467 -0
- package/starter/foundation/main.js +50 -0
- package/starter/foundation/sections/Section/index.jsx +95 -0
- package/starter/foundation/sections/Section/meta.js +44 -0
- package/starter/site/pages/about/1-about.md.hbs +13 -0
- package/starter/site/pages/about/page.yml +3 -0
- package/starter/site/pages/home/1-welcome.md.hbs +14 -0
- package/starter/site/pages/home/page.yml +3 -0
- package/templates/foundation/_gitignore +5 -0
- package/templates/foundation/components/.gitkeep +0 -0
- package/templates/foundation/main.js.hbs +33 -0
- package/templates/foundation/package.json.hbs +51 -0
- package/templates/foundation/sections/.gitkeep +0 -0
- package/templates/foundation/styles.css +6 -0
- package/templates/foundation/vite.config.js +3 -0
- package/templates/site/_gitignore +12 -0
- package/templates/site/entry.js +7 -0
- package/templates/site/index.html.hbs +13 -0
- package/templates/site/package.json.hbs +27 -0
- package/templates/site/public/favicon.svg +7 -0
- package/templates/site/site.yml.hbs +91 -0
- package/templates/site/theme.yml +99 -0
- package/templates/site/vite.config.js +7 -0
- package/templates/workspace/AGENTS.md.hbs +2 -0
- package/templates/workspace/README.md.hbs +87 -0
- package/templates/workspace/_gitignore +29 -0
- package/templates/workspace/_vscode/settings.json +6 -0
- package/templates/workspace/package.json.hbs +20 -0
- package/templates/workspace/pnpm-workspace.yaml.hbs +26 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uniweb inspect — Show parsed content shape of markdown files.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* uniweb inspect pages/home/hero.md Single section
|
|
6
|
+
* uniweb inspect pages/home/ All sections in a page folder
|
|
7
|
+
* uniweb inspect pages/home/hero.md --raw ProseMirror AST instead of flat shape
|
|
8
|
+
* uniweb inspect pages/home/ --full Include empty fields (matches runtime)
|
|
9
|
+
* uniweb inspect pages/home/ --sequence Include sequence array
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
|
13
|
+
import { resolve, extname, basename } from 'node:path'
|
|
14
|
+
import yaml from 'js-yaml'
|
|
15
|
+
|
|
16
|
+
// Colors for terminal output
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
red: '\x1b[31m',
|
|
20
|
+
yellow: '\x1b[33m',
|
|
21
|
+
dim: '\x1b[2m'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Parse CLI arguments for inspect command
|
|
26
|
+
*/
|
|
27
|
+
function parseArgs(args) {
|
|
28
|
+
const flags = {
|
|
29
|
+
target: null,
|
|
30
|
+
raw: false,
|
|
31
|
+
full: false,
|
|
32
|
+
sequence: false,
|
|
33
|
+
help: false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const arg of args) {
|
|
37
|
+
if (arg === '--raw') flags.raw = true
|
|
38
|
+
else if (arg === '--full') flags.full = true
|
|
39
|
+
else if (arg === '--sequence') flags.sequence = true
|
|
40
|
+
else if (arg === '--help' || arg === '-h') flags.help = true
|
|
41
|
+
else if (!arg.startsWith('-')) flags.target = arg
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return flags
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Dynamically import content-reader and semantic-parser.
|
|
49
|
+
* These are transitive deps of @uniweb/build, available in any project workspace.
|
|
50
|
+
*/
|
|
51
|
+
async function loadDependencies() {
|
|
52
|
+
try {
|
|
53
|
+
const [contentReader, semanticParser] = await Promise.all([
|
|
54
|
+
import('@uniweb/content-reader'),
|
|
55
|
+
import('@uniweb/semantic-parser')
|
|
56
|
+
])
|
|
57
|
+
// Optional: the inset lift a Block runs (`@uniweb/core/insets`). An older
|
|
58
|
+
// install without it shows the insets unlifted, as `inset_ref` nodes.
|
|
59
|
+
const coreInsets = await import('@uniweb/core/insets').catch(() => null)
|
|
60
|
+
return {
|
|
61
|
+
markdownToProseMirror: contentReader.markdownToProseMirror,
|
|
62
|
+
parseContent: semanticParser.parseContent,
|
|
63
|
+
liftInsets: coreInsets?.liftInsets,
|
|
64
|
+
// Optional: present from @uniweb/semantic-parser >= the staircase
|
|
65
|
+
// release; older installs simply show no findings.
|
|
66
|
+
lintContent: semanticParser.lintContent
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
console.error(
|
|
70
|
+
`${colors.red}✗${colors.reset} Could not load @uniweb/content-reader or @uniweb/semantic-parser.`
|
|
71
|
+
)
|
|
72
|
+
console.error(
|
|
73
|
+
` These packages must be installed in the workspace (they come with @uniweb/build).`
|
|
74
|
+
)
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract frontmatter and markdown body from a section file.
|
|
81
|
+
* Exact match of content-collector lines 634-639.
|
|
82
|
+
*/
|
|
83
|
+
function extractFrontmatter(content) {
|
|
84
|
+
let frontMatter = {}
|
|
85
|
+
let markdown = content
|
|
86
|
+
|
|
87
|
+
if (content.trim().startsWith('---')) {
|
|
88
|
+
const parts = content.split('---\n')
|
|
89
|
+
if (parts.length >= 3) {
|
|
90
|
+
try {
|
|
91
|
+
frontMatter = yaml.load(parts[1]) || {}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
console.warn(
|
|
94
|
+
`${colors.yellow}Warning: YAML parse error: ${err.message}${colors.reset}`
|
|
95
|
+
)
|
|
96
|
+
frontMatter = {}
|
|
97
|
+
}
|
|
98
|
+
markdown = parts.slice(2).join('---\n')
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { frontMatter, markdown }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Split frontmatter into reserved keys and custom params.
|
|
107
|
+
* Exact match of content-collector line 642.
|
|
108
|
+
*/
|
|
109
|
+
function splitParams(frontMatter) {
|
|
110
|
+
const {
|
|
111
|
+
type,
|
|
112
|
+
component,
|
|
113
|
+
preset,
|
|
114
|
+
input,
|
|
115
|
+
props,
|
|
116
|
+
fetch,
|
|
117
|
+
data,
|
|
118
|
+
id,
|
|
119
|
+
background,
|
|
120
|
+
theme,
|
|
121
|
+
...params
|
|
122
|
+
} = frontMatter
|
|
123
|
+
return {
|
|
124
|
+
type: type || component || null,
|
|
125
|
+
preset: preset || null,
|
|
126
|
+
reserved: { data, id, background, theme, input },
|
|
127
|
+
params
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Guarantee item has flat content structure.
|
|
133
|
+
* Keep in sync with @uniweb/runtime/src/prepare-props.js
|
|
134
|
+
*/
|
|
135
|
+
function guaranteeItemStructure(item) {
|
|
136
|
+
return {
|
|
137
|
+
title: item.title || '',
|
|
138
|
+
pretitle: item.pretitle || '',
|
|
139
|
+
subtitle: item.subtitle || '',
|
|
140
|
+
paragraphs: item.paragraphs || [],
|
|
141
|
+
links: item.links || [],
|
|
142
|
+
images: item.images || [],
|
|
143
|
+
lists: item.lists || [],
|
|
144
|
+
icons: item.icons || [],
|
|
145
|
+
videos: item.videos || [],
|
|
146
|
+
buttons: item.buttons || [],
|
|
147
|
+
data: item.data || {},
|
|
148
|
+
cards: item.cards || [],
|
|
149
|
+
documents: item.documents || [],
|
|
150
|
+
forms: item.forms || [],
|
|
151
|
+
quotes: item.quotes || [],
|
|
152
|
+
headings: item.headings || []
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Guarantee content structure exists.
|
|
158
|
+
* Keep in sync with @uniweb/runtime/src/prepare-props.js
|
|
159
|
+
*/
|
|
160
|
+
function guaranteeContentStructure(parsedContent) {
|
|
161
|
+
const content = parsedContent || {}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
title: content.title || '',
|
|
165
|
+
pretitle: content.pretitle || '',
|
|
166
|
+
subtitle: content.subtitle || '',
|
|
167
|
+
alignment: content.alignment || null,
|
|
168
|
+
paragraphs: content.paragraphs || [],
|
|
169
|
+
links: content.links || [],
|
|
170
|
+
images: content.images || [],
|
|
171
|
+
lists: content.lists || [],
|
|
172
|
+
icons: content.icons || [],
|
|
173
|
+
videos: content.videos || [],
|
|
174
|
+
insets: content.insets || [],
|
|
175
|
+
buttons: content.buttons || [],
|
|
176
|
+
data: content.data || {},
|
|
177
|
+
cards: content.cards || [],
|
|
178
|
+
documents: content.documents || [],
|
|
179
|
+
forms: content.forms || [],
|
|
180
|
+
quotes: content.quotes || [],
|
|
181
|
+
headings: content.headings || [],
|
|
182
|
+
items: (content.items || []).map(guaranteeItemStructure),
|
|
183
|
+
sequence: content.sequence || [],
|
|
184
|
+
raw: content.raw
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Remove empty fields from content for clean output.
|
|
190
|
+
*/
|
|
191
|
+
function removeEmptyFields(obj) {
|
|
192
|
+
const result = {}
|
|
193
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
194
|
+
if (key === 'raw') continue // Always skip raw ProseMirror in clean output
|
|
195
|
+
if (value === null || value === undefined) continue
|
|
196
|
+
if (value === '') continue
|
|
197
|
+
if (Array.isArray(value) && value.length === 0) continue
|
|
198
|
+
if (
|
|
199
|
+
typeof value === 'object' &&
|
|
200
|
+
!Array.isArray(value) &&
|
|
201
|
+
Object.keys(value).length === 0
|
|
202
|
+
)
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
// Recursively clean items
|
|
206
|
+
if (key === 'items' && Array.isArray(value)) {
|
|
207
|
+
result[key] = value.map((item) => removeEmptyFields(item))
|
|
208
|
+
} else {
|
|
209
|
+
result[key] = value
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return result
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Process a single markdown file and return its parsed shape.
|
|
217
|
+
*/
|
|
218
|
+
function processFile(fileContent, fileName, deps, options) {
|
|
219
|
+
const { raw, full, sequence } = options
|
|
220
|
+
const { markdownToProseMirror, parseContent } = deps
|
|
221
|
+
|
|
222
|
+
const { frontMatter, markdown } = extractFrontmatter(fileContent)
|
|
223
|
+
const { type, preset, reserved, params } = splitParams(frontMatter)
|
|
224
|
+
|
|
225
|
+
// Parse markdown to ProseMirror — the document as stored, insets as written.
|
|
226
|
+
const stored = markdownToProseMirror(markdown)
|
|
227
|
+
|
|
228
|
+
// What the component receives: the insets lifted the way a Block lifts them —
|
|
229
|
+
// a placeholder in the content, the inset itself listed beside it. ⛔ This
|
|
230
|
+
// file carried its own copy of the site build's extractor until 2026-09-27,
|
|
231
|
+
// and it had drifted (top-level only, no inline insets, `description` for
|
|
232
|
+
// `title`); insets are lifted by core now, so this asks core.
|
|
233
|
+
const lifted = deps.liftInsets
|
|
234
|
+
? deps.liftInsets(stored)
|
|
235
|
+
: { content: stored, leaves: [], containers: [] }
|
|
236
|
+
const doc = lifted.content
|
|
237
|
+
const insets = [
|
|
238
|
+
...lifted.leaves,
|
|
239
|
+
...lifted.containers.map(({ refId, type, params }) => ({ refId, type, params, container: true }))
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
// Build result
|
|
243
|
+
const result = {}
|
|
244
|
+
|
|
245
|
+
if (type) result.type = type
|
|
246
|
+
if (preset) result.preset = preset
|
|
247
|
+
|
|
248
|
+
// Include non-empty reserved fields
|
|
249
|
+
for (const [key, value] of Object.entries(reserved)) {
|
|
250
|
+
if (value !== undefined && value !== null) result[key] = value
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Include params if any
|
|
254
|
+
if (Object.keys(params).length > 0) result.params = params
|
|
255
|
+
|
|
256
|
+
// Check if file is a child section
|
|
257
|
+
if (basename(fileName).startsWith('@')) {
|
|
258
|
+
result.child = true
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (raw) {
|
|
262
|
+
// Raw mode: the ProseMirror AST as stored — insets in it as the author wrote them.
|
|
263
|
+
result.prosemirror = stored
|
|
264
|
+
return result
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Parse to flat content shape
|
|
268
|
+
const parsed = parseContent(doc)
|
|
269
|
+
|
|
270
|
+
// Apply guarantees
|
|
271
|
+
let content = guaranteeContentStructure(parsed)
|
|
272
|
+
|
|
273
|
+
if (!full) {
|
|
274
|
+
content = removeEmptyFields(content)
|
|
275
|
+
} else {
|
|
276
|
+
// In full mode, still remove raw
|
|
277
|
+
delete content.raw
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!sequence) {
|
|
281
|
+
delete content.sequence
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
result.content = content
|
|
285
|
+
|
|
286
|
+
if (insets.length > 0) result.insets = insets
|
|
287
|
+
|
|
288
|
+
// Grouping near-miss findings — shapes that usually mean the author got a
|
|
289
|
+
// different structure than they wanted (an entry absorbed into the
|
|
290
|
+
// headline, a hand-written eyebrow demoting the title). Observation only:
|
|
291
|
+
// the parse above is untouched.
|
|
292
|
+
const findings = deps.lintContent?.(doc) || []
|
|
293
|
+
if (findings.length > 0) result.findings = findings
|
|
294
|
+
|
|
295
|
+
return result
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Natural sort comparator for filenames (handles numeric prefixes).
|
|
300
|
+
*/
|
|
301
|
+
function naturalSort(a, b) {
|
|
302
|
+
return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' })
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Main inspect entry point.
|
|
307
|
+
*/
|
|
308
|
+
export async function inspect(args) {
|
|
309
|
+
const flags = parseArgs(args)
|
|
310
|
+
|
|
311
|
+
if (flags.help || !flags.target) {
|
|
312
|
+
console.log(`
|
|
313
|
+
Usage: uniweb inspect <path> [options]
|
|
314
|
+
|
|
315
|
+
Inspect the parsed content shape of a markdown file or page folder.
|
|
316
|
+
|
|
317
|
+
Arguments:
|
|
318
|
+
<path> Path to a .md file or a page folder
|
|
319
|
+
|
|
320
|
+
Options:
|
|
321
|
+
--raw Show ProseMirror AST instead of flat content shape
|
|
322
|
+
--full Include empty fields (matches runtime guarantees)
|
|
323
|
+
--sequence Include sequence array (document-order elements)
|
|
324
|
+
-h, --help Show this help
|
|
325
|
+
`)
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const target = resolve(process.cwd(), flags.target)
|
|
330
|
+
const deps = await loadDependencies()
|
|
331
|
+
|
|
332
|
+
let stat
|
|
333
|
+
try {
|
|
334
|
+
stat = statSync(target)
|
|
335
|
+
} catch {
|
|
336
|
+
console.error(`${colors.red}✗${colors.reset} Not found: ${flags.target}`)
|
|
337
|
+
process.exit(1)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const options = { raw: flags.raw, full: flags.full, sequence: flags.sequence }
|
|
341
|
+
|
|
342
|
+
if (stat.isFile()) {
|
|
343
|
+
if (extname(target) !== '.md') {
|
|
344
|
+
console.error(
|
|
345
|
+
`${colors.red}✗${colors.reset} Expected a .md file: ${flags.target}`
|
|
346
|
+
)
|
|
347
|
+
process.exit(1)
|
|
348
|
+
}
|
|
349
|
+
const content = readFileSync(target, 'utf8')
|
|
350
|
+
const result = processFile(content, basename(target), deps, options)
|
|
351
|
+
console.log(JSON.stringify(result, null, 2))
|
|
352
|
+
} else if (stat.isDirectory()) {
|
|
353
|
+
const files = readdirSync(target)
|
|
354
|
+
.filter(
|
|
355
|
+
(f) => extname(f) === '.md' && !f.startsWith('_') && f !== 'README.md'
|
|
356
|
+
)
|
|
357
|
+
.sort(naturalSort)
|
|
358
|
+
|
|
359
|
+
if (files.length === 0) {
|
|
360
|
+
console.error(
|
|
361
|
+
`${colors.yellow}No .md files found in: ${flags.target}${colors.reset}`
|
|
362
|
+
)
|
|
363
|
+
return
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const results = files.map((file) => {
|
|
367
|
+
const content = readFileSync(resolve(target, file), 'utf8')
|
|
368
|
+
const result = processFile(content, file, deps, options)
|
|
369
|
+
result._file = file
|
|
370
|
+
return result
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
console.log(JSON.stringify(results, null, 2))
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* invite — RESERVED (not available on the new backend yet).
|
|
3
|
+
*
|
|
4
|
+
* The foundation client-invite flow ran against the legacy platform (the PHP
|
|
5
|
+
* backend + Cloudflare Worker) the CLI no longer talks to. Client onboarding is
|
|
6
|
+
* handled in the Uniweb app for now; a new-backend CLI equivalent isn't built.
|
|
7
|
+
* The command name is kept reserved so a future rebuild can claim it without a
|
|
8
|
+
* breaking change.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export async function invite() {
|
|
12
|
+
console.error(
|
|
13
|
+
"\x1b[31m✗\x1b[0m `uniweb invite` isn't available on the new backend yet."
|
|
14
|
+
)
|
|
15
|
+
console.error(
|
|
16
|
+
' The legacy client-invite flow was retired with the PHP backend.'
|
|
17
|
+
)
|
|
18
|
+
console.error(
|
|
19
|
+
' Invite clients to your foundation from the Uniweb app for now.'
|
|
20
|
+
)
|
|
21
|
+
process.exit(1)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default invite
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* uniweb org — the orgs you belong to, on the backend you are logged in to.
|
|
3
|
+
*
|
|
4
|
+
* uniweb org list Your personal scope, and the orgs you're a member of.
|
|
5
|
+
* uniweb org create <handle> Create an org; you become a member.
|
|
6
|
+
*
|
|
7
|
+
* ⭐ You need no org to register a foundation: `@<your handle>` is your personal scope
|
|
8
|
+
* (2026-09-23). An org is for a scope — and sites — you share, and it needs a handle
|
|
9
|
+
* of its own: a backend refuses one named after an account, yours included.
|
|
10
|
+
*
|
|
11
|
+
* Auth: the session of the backend you are logged in to (`uniweb login`) / UNIWEB_TOKEN.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { BackendClient } from '../backend/client.js'
|
|
15
|
+
import { validateHandle, bareHandle } from '../utils/registry-orgs.js'
|
|
16
|
+
|
|
17
|
+
const colors = {
|
|
18
|
+
reset: '\x1b[0m',
|
|
19
|
+
bright: '\x1b[1m',
|
|
20
|
+
dim: '\x1b[2m',
|
|
21
|
+
red: '\x1b[31m',
|
|
22
|
+
green: '\x1b[32m'
|
|
23
|
+
}
|
|
24
|
+
const error = (m) => console.error(`${colors.red}✗${colors.reset} ${m}`)
|
|
25
|
+
const success = (m) => console.log(`${colors.green}✓${colors.reset} ${m}`)
|
|
26
|
+
|
|
27
|
+
export async function org(args = []) {
|
|
28
|
+
const sub = args[0]
|
|
29
|
+
|
|
30
|
+
if (sub === 'list') {
|
|
31
|
+
const client = new BackendClient({ args, command: 'Listing orgs' })
|
|
32
|
+
const { account_handle: mine, orgs: all } = await client.fetchOrgs()
|
|
33
|
+
// An org named after you — made before 2026-09-23 — is your personal scope's `@`.
|
|
34
|
+
const orgs = all.filter((u) => u.handle !== mine)
|
|
35
|
+
if (mine) {
|
|
36
|
+
console.log(
|
|
37
|
+
`Your personal scope: ${colors.bright}@${mine}${colors.reset}${colors.dim} — register under it with no org${colors.reset}`
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
if (!orgs.length) {
|
|
41
|
+
console.log(
|
|
42
|
+
`You belong to no org. To share a scope and sites with others: uniweb org create <handle>`
|
|
43
|
+
)
|
|
44
|
+
return { exitCode: 0 }
|
|
45
|
+
}
|
|
46
|
+
console.log('Your orgs:')
|
|
47
|
+
for (const u of orgs) {
|
|
48
|
+
console.log(
|
|
49
|
+
` ${colors.bright}@${u.handle}${colors.reset}${u.is_primary ? `${colors.dim} (primary)${colors.reset}` : ''}`
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
return { exitCode: 0 }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (sub === 'create') {
|
|
56
|
+
const handle = bareHandle(args[1])
|
|
57
|
+
if (!handle) {
|
|
58
|
+
error('Usage: uniweb org create <handle>')
|
|
59
|
+
return { exitCode: 2 }
|
|
60
|
+
}
|
|
61
|
+
const invalid = validateHandle(handle)
|
|
62
|
+
if (invalid) {
|
|
63
|
+
error(invalid)
|
|
64
|
+
return { exitCode: 2 }
|
|
65
|
+
}
|
|
66
|
+
const client = new BackendClient({ args, command: 'Creating an org' })
|
|
67
|
+
try {
|
|
68
|
+
// Named after you, it is your personal scope already — and a backend refuses it.
|
|
69
|
+
const { account_handle: mine } = await client.fetchOrgs()
|
|
70
|
+
if (mine && handle === mine) {
|
|
71
|
+
error(
|
|
72
|
+
`@${handle} is your personal scope already — you can register under it with no org. An org needs a handle of its own.`
|
|
73
|
+
)
|
|
74
|
+
return { exitCode: 2 }
|
|
75
|
+
}
|
|
76
|
+
const org = await client.createOrg(handle)
|
|
77
|
+
success(
|
|
78
|
+
`Created ${colors.bright}@${org.handle}${colors.reset} — you're a member${org.is_primary ? ' (primary)' : ''}.`
|
|
79
|
+
)
|
|
80
|
+
console.log(
|
|
81
|
+
`${colors.dim}Register under it: uniweb register --scope @${org.handle}${colors.reset}`
|
|
82
|
+
)
|
|
83
|
+
return { exitCode: 0 }
|
|
84
|
+
} catch (err) {
|
|
85
|
+
error(err.message)
|
|
86
|
+
return { exitCode: 1 }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('uniweb org <command>')
|
|
91
|
+
console.log(' list Your personal scope, and the orgs you belong to')
|
|
92
|
+
console.log(' create <handle> Create an org (you become a member)')
|
|
93
|
+
return { exitCode: sub ? 2 : 0 }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export default org
|