uniweb 0.10.13 → 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/README.md +20 -18
- package/package.json +2 -2
- package/partials/agents.md +163 -39
- package/src/commands/add.js +152 -233
- package/src/commands/build.js +14 -42
- package/src/commands/deploy.js +262 -3
- package/src/commands/docs.js +5 -6
- package/src/commands/doctor.js +21 -22
- package/src/commands/publish.js +255 -34
- package/src/framework-index.json +3 -3
- package/src/index.js +27 -14
- package/src/utils/auth.js +82 -6
- package/src/utils/names.js +9 -2
- package/src/utils/registry.js +88 -16
- package/src/utils/scaffold.js +8 -2
- package/src/utils/workspace.js +8 -46
- package/starter/site/pages/home/1-welcome.md.hbs +1 -1
- package/templates/foundation/{src/foundation.js.hbs → main.js.hbs} +1 -1
- package/templates/foundation/package.json.hbs +6 -6
- package/templates/foundation/{src/styles.css → styles.css} +1 -1
- package/templates/site/index.html.hbs +1 -1
- package/templates/site/theme.yml +1 -1
- package/templates/workspace/README.md.hbs +9 -7
- /package/starter/foundation/{src/foundation.js → main.js} +0 -0
- /package/starter/foundation/{src/sections → sections}/Section/index.jsx +0 -0
- /package/starter/foundation/{src/sections → sections}/Section/meta.js +0 -0
- /package/templates/foundation/{src/components → components}/.gitkeep +0 -0
- /package/templates/foundation/{src/sections → sections}/.gitkeep +0 -0
- /package/templates/site/{main.js → entry.js} +0 -0
package/src/utils/registry.js
CHANGED
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Foundation Registry
|
|
3
3
|
*
|
|
4
|
-
* Manages published foundations in .unicloud/registry/.
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Manages published foundations in .unicloud/registry/. The on-disk
|
|
5
|
+
* shape mirrors uniweb-edge's registry index (versions as an array of
|
|
6
|
+
* { version, ... } objects, plus top-level namespace and latest) so
|
|
7
|
+
* `--local` exercises the same data shape that ships in production.
|
|
7
8
|
*
|
|
8
9
|
* Layout:
|
|
9
10
|
* .unicloud/
|
|
10
11
|
* registry/
|
|
11
|
-
* index.json #
|
|
12
|
+
* index.json # see "Index format" below
|
|
12
13
|
* packages/
|
|
13
14
|
* name/
|
|
14
15
|
* 1.0.0/
|
|
15
16
|
* foundation.js
|
|
16
17
|
* schema.json
|
|
17
18
|
* assets/...
|
|
19
|
+
*
|
|
20
|
+
* Index format:
|
|
21
|
+
* {
|
|
22
|
+
* "@ns/name": {
|
|
23
|
+
* namespace: "ns",
|
|
24
|
+
* versions: [
|
|
25
|
+
* { version: "1.0.0", publishedAt, publishedBy, ... },
|
|
26
|
+
* ...
|
|
27
|
+
* ],
|
|
28
|
+
* latest: "1.0.0"
|
|
29
|
+
* }
|
|
30
|
+
* }
|
|
31
|
+
*
|
|
32
|
+
* Legacy entries (versions as an object keyed by version) are migrated
|
|
33
|
+
* to this shape on read. The next write persists the new shape.
|
|
18
34
|
*/
|
|
19
35
|
|
|
20
36
|
import { existsSync } from 'node:fs'
|
|
@@ -37,7 +53,7 @@ export function getRegistryDir(startDir = process.cwd()) {
|
|
|
37
53
|
|
|
38
54
|
/**
|
|
39
55
|
* Sanitize a package name for filesystem use.
|
|
40
|
-
* '@org/pkg' → '
|
|
56
|
+
* '@org/pkg' → 'org/pkg'
|
|
41
57
|
* @param {string} name
|
|
42
58
|
* @returns {string}
|
|
43
59
|
*/
|
|
@@ -46,6 +62,38 @@ function sanitizeName(name) {
|
|
|
46
62
|
return name.startsWith('@') ? name.slice(1) : name
|
|
47
63
|
}
|
|
48
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Parse the namespace out of a scoped package name. '@org/pkg' → 'org';
|
|
67
|
+
* unscoped → ''.
|
|
68
|
+
* @param {string} name
|
|
69
|
+
* @returns {string}
|
|
70
|
+
*/
|
|
71
|
+
function parseNamespace(name) {
|
|
72
|
+
const m = /^@([a-z0-9_-]+)\//.exec(name)
|
|
73
|
+
return m ? m[1] : ''
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Migrate a legacy index entry (versions as object, no namespace/latest)
|
|
78
|
+
* to the current shape (versions as array, namespace + latest at top).
|
|
79
|
+
* Mutates and returns the entry.
|
|
80
|
+
*/
|
|
81
|
+
function normalizeEntry(name, entry) {
|
|
82
|
+
if (!entry) return entry
|
|
83
|
+
if (entry.versions && !Array.isArray(entry.versions) && typeof entry.versions === 'object') {
|
|
84
|
+
entry.versions = Object.entries(entry.versions).map(([version, data]) => ({
|
|
85
|
+
version,
|
|
86
|
+
...data,
|
|
87
|
+
}))
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(entry.versions)) entry.versions = []
|
|
90
|
+
if (!entry.namespace) entry.namespace = parseNamespace(name)
|
|
91
|
+
if (!entry.latest && entry.versions.length > 0) {
|
|
92
|
+
entry.latest = entry.versions[entry.versions.length - 1].version
|
|
93
|
+
}
|
|
94
|
+
return entry
|
|
95
|
+
}
|
|
96
|
+
|
|
49
97
|
/**
|
|
50
98
|
* Local registry — stores published foundations in .unicloud/registry/
|
|
51
99
|
*/
|
|
@@ -58,7 +106,11 @@ export class LocalRegistry {
|
|
|
58
106
|
|
|
59
107
|
async _readIndex() {
|
|
60
108
|
if (!existsSync(this.indexPath)) return {}
|
|
61
|
-
|
|
109
|
+
const raw = JSON.parse(await readFile(this.indexPath, 'utf8'))
|
|
110
|
+
for (const name of Object.keys(raw)) {
|
|
111
|
+
normalizeEntry(name, raw[name])
|
|
112
|
+
}
|
|
113
|
+
return raw
|
|
62
114
|
}
|
|
63
115
|
|
|
64
116
|
async _writeIndex(index) {
|
|
@@ -74,17 +126,20 @@ export class LocalRegistry {
|
|
|
74
126
|
*/
|
|
75
127
|
async exists(name, version) {
|
|
76
128
|
const index = await this._readIndex()
|
|
77
|
-
|
|
129
|
+
const versions = index[name]?.versions
|
|
130
|
+
if (!Array.isArray(versions)) return false
|
|
131
|
+
return versions.some(v => v.version === version)
|
|
78
132
|
}
|
|
79
133
|
|
|
80
134
|
/**
|
|
81
|
-
* Get all published versions for a package
|
|
135
|
+
* Get all published versions for a package as an array of
|
|
136
|
+
* `{ version, publishedAt, ... }` entries (matches uniweb-edge).
|
|
82
137
|
* @param {string} name
|
|
83
|
-
* @returns {Promise<
|
|
138
|
+
* @returns {Promise<Array>}
|
|
84
139
|
*/
|
|
85
140
|
async getVersions(name) {
|
|
86
141
|
const index = await this._readIndex()
|
|
87
|
-
return index[name]?.versions ||
|
|
142
|
+
return index[name]?.versions || []
|
|
88
143
|
}
|
|
89
144
|
|
|
90
145
|
/**
|
|
@@ -104,12 +159,26 @@ export class LocalRegistry {
|
|
|
104
159
|
|
|
105
160
|
const index = await this._readIndex()
|
|
106
161
|
if (!index[name]) {
|
|
107
|
-
index[name] = {
|
|
162
|
+
index[name] = {
|
|
163
|
+
namespace: parseNamespace(name),
|
|
164
|
+
versions: [],
|
|
165
|
+
latest: null,
|
|
166
|
+
}
|
|
108
167
|
}
|
|
109
|
-
|
|
168
|
+
|
|
169
|
+
const versionEntry = {
|
|
170
|
+
version,
|
|
110
171
|
publishedAt: new Date().toISOString(),
|
|
111
172
|
...metadata,
|
|
112
173
|
}
|
|
174
|
+
const existingIdx = index[name].versions.findIndex(v => v.version === version)
|
|
175
|
+
if (existingIdx >= 0) {
|
|
176
|
+
index[name].versions[existingIdx] = versionEntry
|
|
177
|
+
} else {
|
|
178
|
+
index[name].versions.push(versionEntry)
|
|
179
|
+
}
|
|
180
|
+
index[name].latest = version
|
|
181
|
+
|
|
113
182
|
await this._writeIndex(index)
|
|
114
183
|
}
|
|
115
184
|
|
|
@@ -165,20 +234,23 @@ export class RemoteRegistry {
|
|
|
165
234
|
async exists(name, version) {
|
|
166
235
|
try {
|
|
167
236
|
const index = await this._fetchIndex()
|
|
168
|
-
|
|
237
|
+
const versions = index[name]?.versions
|
|
238
|
+
if (!Array.isArray(versions)) return false
|
|
239
|
+
return versions.some(v => v.version === version)
|
|
169
240
|
} catch {
|
|
170
241
|
return false
|
|
171
242
|
}
|
|
172
243
|
}
|
|
173
244
|
|
|
174
245
|
/**
|
|
175
|
-
* Get all published versions for a package
|
|
246
|
+
* Get all published versions for a package as an array of
|
|
247
|
+
* `{ version, publishedAt, ... }` entries.
|
|
176
248
|
* @param {string} name
|
|
177
|
-
* @returns {Promise<
|
|
249
|
+
* @returns {Promise<Array>}
|
|
178
250
|
*/
|
|
179
251
|
async getVersions(name) {
|
|
180
252
|
const index = await this._fetchIndex()
|
|
181
|
-
return index[name]?.versions ||
|
|
253
|
+
return index[name]?.versions || []
|
|
182
254
|
}
|
|
183
255
|
|
|
184
256
|
/**
|
package/src/utils/scaffold.js
CHANGED
|
@@ -94,7 +94,13 @@ export async function scaffoldSite(targetDir, context, options = {}) {
|
|
|
94
94
|
* Apply content overlay from a content directory onto a target
|
|
95
95
|
*
|
|
96
96
|
* Content files overwrite scaffolded defaults. Structural files
|
|
97
|
-
* (package.json, vite.config.js,
|
|
97
|
+
* (package.json, vite.config.js, entry.js, index.html) are NOT overwritten.
|
|
98
|
+
*
|
|
99
|
+
* Note: a foundation's `main.js` (the user-authored declarations file) is
|
|
100
|
+
* NOT structural — templates legitimately provide their own `main.js` to
|
|
101
|
+
* override the empty scaffold default. The site's `entry.js` (formerly
|
|
102
|
+
* `main.js`) IS structural — the boilerplate `start({...})` is identical
|
|
103
|
+
* across all sites and shouldn't be overwritten.
|
|
98
104
|
*
|
|
99
105
|
* @param {string} contentDir - Source content directory (e.g., starter/foundation/)
|
|
100
106
|
* @param {string} targetDir - Target directory to overlay onto
|
|
@@ -110,7 +116,7 @@ export async function applyContent(contentDir, targetDir, context, options = {})
|
|
|
110
116
|
const STRUCTURAL_FILES = new Set([
|
|
111
117
|
'package.json',
|
|
112
118
|
'vite.config.js',
|
|
113
|
-
'
|
|
119
|
+
'entry.js',
|
|
114
120
|
'index.html',
|
|
115
121
|
'.gitignore',
|
|
116
122
|
])
|
package/src/utils/workspace.js
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
|
10
10
|
import { readFile } from 'node:fs/promises'
|
|
11
11
|
import { resolve, dirname, join } from 'node:path'
|
|
12
12
|
import yaml from 'js-yaml'
|
|
13
|
+
import { classifyPackage as classifyPackageSync } from '@uniweb/build'
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Check if a directory is a workspace root.
|
|
@@ -124,38 +125,17 @@ export async function getWorkspacePackages(workspaceRoot) {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
|
-
* Classify a package as foundation, site, or unknown
|
|
128
|
+
* Classify a package as foundation, site, or unknown.
|
|
128
129
|
*
|
|
129
|
-
*
|
|
130
|
-
* -
|
|
131
|
-
* -
|
|
132
|
-
*
|
|
133
|
-
* Note: Sites also have @uniweb/build for the Vite plugin, so we check
|
|
134
|
-
* for @uniweb/runtime first to distinguish them.
|
|
130
|
+
* Re-exports the canonical (sync) classifier from @uniweb/build, kept
|
|
131
|
+
* async-shaped here so existing call sites continue to work without an
|
|
132
|
+
* await-removal sweep. New code should import directly from @uniweb/build.
|
|
135
133
|
*
|
|
136
134
|
* @param {string} packagePath - Full path to package directory
|
|
137
135
|
* @returns {Promise<'foundation'|'site'|null>}
|
|
138
136
|
*/
|
|
139
137
|
export async function classifyPackage(packagePath) {
|
|
140
|
-
|
|
141
|
-
if (!existsSync(pkgJsonPath)) return null
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
const pkg = JSON.parse(await readFile(pkgJsonPath, 'utf-8'))
|
|
145
|
-
|
|
146
|
-
// Site: has @uniweb/runtime in dependencies (check first - more specific)
|
|
147
|
-
if (pkg.dependencies?.['@uniweb/runtime']) {
|
|
148
|
-
return 'site'
|
|
149
|
-
}
|
|
150
|
-
// Foundation: has @uniweb/build in devDependencies (and not a site)
|
|
151
|
-
if (pkg.devDependencies?.['@uniweb/build']) {
|
|
152
|
-
return 'foundation'
|
|
153
|
-
}
|
|
154
|
-
} catch {
|
|
155
|
-
// Ignore parse errors
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return null
|
|
138
|
+
return classifyPackageSync(packagePath)
|
|
159
139
|
}
|
|
160
140
|
|
|
161
141
|
/**
|
|
@@ -165,16 +145,7 @@ export async function classifyPackage(packagePath) {
|
|
|
165
145
|
*/
|
|
166
146
|
export async function findFoundations(workspaceRoot) {
|
|
167
147
|
const packages = await getWorkspacePackages(workspaceRoot)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
for (const pkg of packages) {
|
|
171
|
-
const fullPath = join(workspaceRoot, pkg)
|
|
172
|
-
if ((await classifyPackage(fullPath)) === 'foundation') {
|
|
173
|
-
foundations.push(pkg)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return foundations
|
|
148
|
+
return packages.filter(pkg => classifyPackageSync(join(workspaceRoot, pkg)) === 'foundation')
|
|
178
149
|
}
|
|
179
150
|
|
|
180
151
|
/**
|
|
@@ -184,16 +155,7 @@ export async function findFoundations(workspaceRoot) {
|
|
|
184
155
|
*/
|
|
185
156
|
export async function findSites(workspaceRoot) {
|
|
186
157
|
const packages = await getWorkspacePackages(workspaceRoot)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
for (const pkg of packages) {
|
|
190
|
-
const fullPath = join(workspaceRoot, pkg)
|
|
191
|
-
if ((await classifyPackage(fullPath)) === 'site') {
|
|
192
|
-
sites.push(pkg)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return sites
|
|
158
|
+
return packages.filter(pkg => classifyPackageSync(join(workspaceRoot, pkg)) === 'site')
|
|
197
159
|
}
|
|
198
160
|
|
|
199
161
|
/**
|
|
@@ -8,7 +8,7 @@ align: center
|
|
|
8
8
|
|
|
9
9
|
## Your Uniweb project is ready
|
|
10
10
|
|
|
11
|
-
This is a minimal starting point for your Uniweb project. Edit the content in `site/pages/` and build section types in `
|
|
11
|
+
This is a minimal starting point for your Uniweb project. Edit the content in `site/pages/` and build section types in `src/sections/`.
|
|
12
12
|
|
|
13
13
|
[About](/about)
|
|
14
14
|
[Documentation](https://github.com/uniweb)
|
|
@@ -19,7 +19,7 @@ export default {
|
|
|
19
19
|
{{else}}
|
|
20
20
|
export default {
|
|
21
21
|
// ─── Layout ─────────────────────────────────────────────────────────────────
|
|
22
|
-
// Create layouts in
|
|
22
|
+
// Create layouts in layouts/MyLayout/index.jsx (auto-discovered).
|
|
23
23
|
// defaultLayout: 'MyLayout',
|
|
24
24
|
|
|
25
25
|
// ─── Props ──────────────────────────────────────────────────────────────────
|
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"description": "{{projectName}} foundation",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "./
|
|
6
|
+
"main": "./_entry.generated.js",
|
|
7
7
|
"exports": {
|
|
8
|
-
".": "./
|
|
9
|
-
"./styles": "./
|
|
8
|
+
".": "./_entry.generated.js",
|
|
9
|
+
"./styles": "./styles.css",
|
|
10
10
|
"./dist": "./dist/foundation.js",
|
|
11
11
|
"./dist/styles": "./dist/assets/style.css"
|
|
12
12
|
},
|
|
13
13
|
"imports": {
|
|
14
|
-
"#components/*": "./
|
|
15
|
-
"#utils/*": "./
|
|
14
|
+
"#components/*": "./components/*",
|
|
15
|
+
"#utils/*": "./utils/*"
|
|
16
16
|
},
|
|
17
|
-
"files": ["dist", "
|
|
17
|
+
"files": ["dist", "sections", "components", "layouts", "utils", "main.js", "styles.css", "_entry.generated.js"],
|
|
18
18
|
"scripts": {
|
|
19
19
|
"dev": "vite",
|
|
20
20
|
"build": "uniweb build",
|
package/templates/site/theme.yml
CHANGED
|
@@ -93,7 +93,7 @@ colors:
|
|
|
93
93
|
|
|
94
94
|
# ─── Foundation Variables ──────────────────────────────────────────────────────
|
|
95
95
|
# Override variables declared by the foundation (e.g., header-height, max-width).
|
|
96
|
-
# Available variables depend on the foundation — check its
|
|
96
|
+
# Available variables depend on the foundation — check its main.js.
|
|
97
97
|
|
|
98
98
|
# vars:
|
|
99
99
|
# header-height: 5rem
|
|
@@ -8,10 +8,10 @@ A website built with [Uniweb](https://github.com/uniweb/cli) — a component web
|
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
{{projectName}}/
|
|
11
|
-
├──
|
|
12
|
-
│ ├──
|
|
13
|
-
│
|
|
14
|
-
│
|
|
11
|
+
├── src/ # Foundation package (the site's source code; pkg name: src)
|
|
12
|
+
│ ├── sections/ # Section types (selectable by content authors)
|
|
13
|
+
│ ├── styles.css # Tailwind CSS v4 theme
|
|
14
|
+
│ ├── main.js # Foundation declarations (vars, defaultLayout, props)
|
|
15
15
|
│ └── vite.config.js # defineFoundationConfig()
|
|
16
16
|
│
|
|
17
17
|
├── site/ # Content (markdown)
|
|
@@ -19,12 +19,14 @@ A website built with [Uniweb](https://github.com/uniweb/cli) — a component web
|
|
|
19
19
|
│ │ └── home/
|
|
20
20
|
│ │ ├── page.yml
|
|
21
21
|
│ │ └── 1-welcome.md
|
|
22
|
-
│ ├── site.yml # Site configuration
|
|
22
|
+
│ ├── site.yml # Site configuration (foundation: src)
|
|
23
23
|
│ └── vite.config.js # defineSiteConfig()
|
|
24
24
|
│
|
|
25
25
|
└── AGENTS.md # Developer guide (human + AI)
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
A site is pure content. A foundation is the site's source code — that's why it lives in `src/`.
|
|
29
|
+
|
|
28
30
|
## Content Authoring
|
|
29
31
|
|
|
30
32
|
Content lives in markdown files under `site/pages/`:
|
|
@@ -46,10 +48,10 @@ Your content here.
|
|
|
46
48
|
|
|
47
49
|
## Component Development
|
|
48
50
|
|
|
49
|
-
Section types live in `
|
|
51
|
+
Section types live in `src/sections/`. Each is a folder with an `index.jsx` and a `meta.js`:
|
|
50
52
|
|
|
51
53
|
```jsx
|
|
52
|
-
//
|
|
54
|
+
// src/sections/Hero/index.jsx
|
|
53
55
|
import { H1, P } from '@uniweb/kit'
|
|
54
56
|
|
|
55
57
|
export default function Hero({ content }) {
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|