uniweb 0.13.16 → 0.14.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 +1 -1
- package/package.json +7 -7
- package/partials/agents.md +51 -0
- package/src/backend/client.js +173 -40
- package/src/backend/data-bundle.js +15 -3
- package/src/backend/foundation-bring-along.js +76 -21
- package/src/backend/payment-handoff.js +28 -9
- package/src/backend/site-media.js +147 -19
- package/src/backend/site-sync.js +362 -40
- package/src/commands/add.js +575 -273
- package/src/commands/build.js +160 -57
- package/src/commands/clone.js +79 -26
- package/src/commands/content.js +11 -7
- package/src/commands/deploy.js +80 -32
- package/src/commands/dev.js +39 -17
- package/src/commands/docs.js +44 -16
- package/src/commands/doctor.js +338 -82
- package/src/commands/export.js +15 -7
- package/src/commands/handoff.js +6 -2
- package/src/commands/i18n.js +248 -99
- package/src/commands/inspect.js +48 -19
- package/src/commands/invite.js +9 -3
- package/src/commands/org.js +19 -5
- package/src/commands/publish.js +229 -60
- package/src/commands/pull.js +157 -48
- package/src/commands/push.js +144 -42
- package/src/commands/refresh.js +37 -10
- package/src/commands/register.js +235 -64
- package/src/commands/rename.js +126 -46
- package/src/commands/runtime.js +74 -24
- package/src/commands/status.js +48 -15
- package/src/commands/sync.js +7 -2
- package/src/commands/template.js +12 -4
- package/src/commands/update.js +263 -87
- package/src/commands/validate.js +115 -32
- package/src/framework-index.json +16 -14
- package/src/index.js +298 -145
- package/src/templates/fetchers/github.js +7 -7
- package/src/templates/fetchers/npm.js +3 -3
- package/src/templates/fetchers/release.js +21 -18
- package/src/templates/index.js +34 -12
- package/src/templates/processor.js +44 -12
- package/src/templates/resolver.js +42 -15
- package/src/templates/validator.js +14 -7
- package/src/utils/asset-upload.js +90 -22
- package/src/utils/code-upload.js +62 -37
- package/src/utils/config.js +31 -10
- package/src/utils/dep-survey.js +33 -7
- package/src/utils/destination-prompt.js +27 -17
- package/src/utils/git.js +66 -22
- package/src/utils/host-prompt.js +4 -3
- package/src/utils/install-integrity.js +8 -4
- package/src/utils/interactive.js +3 -3
- package/src/utils/json-file.js +6 -2
- package/src/utils/names.js +15 -6
- package/src/utils/placement.js +16 -8
- package/src/utils/registry-auth.js +185 -57
- package/src/utils/registry-orgs.js +142 -53
- package/src/utils/runtime-upload.js +52 -14
- package/src/utils/scaffold.js +55 -14
- package/src/utils/site-content-refs.js +3 -1
- package/src/utils/update-check.js +43 -17
- package/src/utils/workspace.js +13 -7
- package/src/versions.js +18 -7
- package/templates/site/_gitignore +5 -0
package/src/utils/git.js
CHANGED
|
@@ -32,11 +32,20 @@ import yaml from 'js-yaml'
|
|
|
32
32
|
* rather than assuming the defaults.
|
|
33
33
|
*/
|
|
34
34
|
export function siteContentRoots(siteDir) {
|
|
35
|
-
const roots = new Set([
|
|
35
|
+
const roots = new Set([
|
|
36
|
+
'site.yml',
|
|
37
|
+
'theme.yml',
|
|
38
|
+
'head.html',
|
|
39
|
+
'collections.yml',
|
|
40
|
+
'locales'
|
|
41
|
+
])
|
|
36
42
|
let paths = {}
|
|
37
43
|
try {
|
|
38
|
-
paths =
|
|
39
|
-
|
|
44
|
+
paths =
|
|
45
|
+
yaml.load(readFileSync(join(siteDir, 'site.yml'), 'utf8'))?.paths || {}
|
|
46
|
+
} catch {
|
|
47
|
+
/* no or unreadable site.yml — the defaults are right */
|
|
48
|
+
}
|
|
40
49
|
roots.add(paths.pages || 'pages')
|
|
41
50
|
roots.add(paths.layout || 'layout')
|
|
42
51
|
roots.add(paths.collections || 'collections')
|
|
@@ -50,7 +59,11 @@ export function hasUncommittedContent(siteDir) {
|
|
|
50
59
|
}
|
|
51
60
|
|
|
52
61
|
function git(args, cwd) {
|
|
53
|
-
return execFileSync('git', args, {
|
|
62
|
+
return execFileSync('git', args, {
|
|
63
|
+
cwd,
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
66
|
+
})
|
|
54
67
|
}
|
|
55
68
|
|
|
56
69
|
/**
|
|
@@ -78,14 +91,19 @@ export function isGitRepo(dir) {
|
|
|
78
91
|
export function uncommittedUnder(dir, relPaths) {
|
|
79
92
|
if (!isGitRepo(dir)) return null
|
|
80
93
|
try {
|
|
81
|
-
const out = git(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
const out = git(
|
|
95
|
+
['status', '--porcelain', '--untracked-files=all', '--', ...relPaths],
|
|
96
|
+
dir
|
|
97
|
+
)
|
|
98
|
+
return (
|
|
99
|
+
out
|
|
100
|
+
.split('\n')
|
|
101
|
+
.filter(Boolean)
|
|
102
|
+
// porcelain v1: XY<space>path, and a rename is "orig -> new".
|
|
103
|
+
.map((line) => line.slice(3).trim())
|
|
104
|
+
.map((p) => (p.includes(' -> ') ? p.split(' -> ')[1] : p))
|
|
105
|
+
.filter(Boolean)
|
|
106
|
+
)
|
|
89
107
|
} catch {
|
|
90
108
|
// A path git doesn't know (e.g. none of the roots exist yet) is not an error
|
|
91
109
|
// worth failing a pull over.
|
|
@@ -111,7 +129,7 @@ export function showAtHead(dir, relPath) {
|
|
|
111
129
|
return execFileSync('git', ['show', `HEAD:${relPath}`], {
|
|
112
130
|
cwd: dir,
|
|
113
131
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
114
|
-
maxBuffer: 64 * 1024 * 1024
|
|
132
|
+
maxBuffer: 64 * 1024 * 1024
|
|
115
133
|
})
|
|
116
134
|
} catch {
|
|
117
135
|
return null
|
|
@@ -135,10 +153,15 @@ export function mergeFile(dir, minePath, basePath, theirsPath, labels = {}) {
|
|
|
135
153
|
'git',
|
|
136
154
|
[
|
|
137
155
|
'merge-file',
|
|
138
|
-
'-L',
|
|
139
|
-
|
|
140
|
-
'-L',
|
|
141
|
-
|
|
156
|
+
'-L',
|
|
157
|
+
labels.mine || 'yours (local)',
|
|
158
|
+
'-L',
|
|
159
|
+
labels.base || 'common ancestor',
|
|
160
|
+
'-L',
|
|
161
|
+
labels.theirs || 'theirs (backend)',
|
|
162
|
+
minePath,
|
|
163
|
+
basePath,
|
|
164
|
+
theirsPath
|
|
142
165
|
],
|
|
143
166
|
{ cwd: dir, stdio: ['ignore', 'ignore', 'ignore'] }
|
|
144
167
|
)
|
|
@@ -174,15 +197,36 @@ export function hasRemote(dir) {
|
|
|
174
197
|
* @returns {{ ok: boolean, changed: boolean, message: string }}
|
|
175
198
|
*/
|
|
176
199
|
export function pullRemote(dir) {
|
|
177
|
-
const before = (() => {
|
|
200
|
+
const before = (() => {
|
|
201
|
+
try {
|
|
202
|
+
return git(['rev-parse', 'HEAD'], dir).trim()
|
|
203
|
+
} catch {
|
|
204
|
+
return null
|
|
205
|
+
}
|
|
206
|
+
})()
|
|
178
207
|
try {
|
|
179
208
|
const out = execFileSync('git', ['pull', '--ff-only'], {
|
|
180
|
-
cwd: dir,
|
|
209
|
+
cwd: dir,
|
|
210
|
+
encoding: 'utf8',
|
|
211
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
181
212
|
})
|
|
182
|
-
const after = (() => {
|
|
183
|
-
|
|
213
|
+
const after = (() => {
|
|
214
|
+
try {
|
|
215
|
+
return git(['rev-parse', 'HEAD'], dir).trim()
|
|
216
|
+
} catch {
|
|
217
|
+
return null
|
|
218
|
+
}
|
|
219
|
+
})()
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
changed: Boolean(before && after && before !== after),
|
|
223
|
+
message: out.trim()
|
|
224
|
+
}
|
|
184
225
|
} catch (err) {
|
|
185
|
-
const msg = [err?.stderr, err?.stdout]
|
|
226
|
+
const msg = [err?.stderr, err?.stdout]
|
|
227
|
+
.map((b) => (b ? String(b) : ''))
|
|
228
|
+
.join('\n')
|
|
229
|
+
.trim()
|
|
186
230
|
return { ok: false, changed: false, message: msg || 'git pull failed' }
|
|
187
231
|
}
|
|
188
232
|
}
|
package/src/utils/host-prompt.js
CHANGED
|
@@ -38,9 +38,10 @@ export async function promptForHost({ args, preselect = null } = {}) {
|
|
|
38
38
|
|
|
39
39
|
// promptSelect doesn't expose initial-index, so move the preselect to
|
|
40
40
|
// the top of the list — the menu still highlights index 0 by default.
|
|
41
|
-
const ordered =
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
const ordered =
|
|
42
|
+
preselect && adapters.includes(preselect)
|
|
43
|
+
? [preselect, ...adapters.filter((a) => a !== preselect)]
|
|
44
|
+
: adapters
|
|
44
45
|
|
|
45
46
|
const choice = await promptSelect('Pick a host adapter:', ordered)
|
|
46
47
|
if (!choice) {
|
|
@@ -117,7 +117,8 @@ export function checkSiteInstall(site, foundation, workspaceDir) {
|
|
|
117
117
|
` ${rel(foundation.path)} taken when it was installed. Edits you make will not\n` +
|
|
118
118
|
` reach the dev server. \`uniweb build\` resolves the foundation by path and reads\n` +
|
|
119
119
|
` your source, so builds stay correct — which is why this is easy to miss.`,
|
|
120
|
-
remedy:
|
|
120
|
+
remedy:
|
|
121
|
+
'Reinstall so it links to the workspace source (e.g. pnpm install)'
|
|
121
122
|
})
|
|
122
123
|
} else {
|
|
123
124
|
// Linked, but possibly at the wrong source.
|
|
@@ -130,7 +131,7 @@ export function checkSiteInstall(site, foundation, workspaceDir) {
|
|
|
130
131
|
site: site.name,
|
|
131
132
|
message: `In dev, this site resolves "${foundation.name}" somewhere other than your workspace source.`,
|
|
132
133
|
detail: ` resolves to: ${rel(target)}\n workspace source: ${rel(expected)}`,
|
|
133
|
-
remedy:
|
|
134
|
+
remedy: "Reinstall, and check the site's package.json file: reference"
|
|
134
135
|
})
|
|
135
136
|
}
|
|
136
137
|
}
|
|
@@ -138,7 +139,10 @@ export function checkSiteInstall(site, foundation, workspaceDir) {
|
|
|
138
139
|
// What the site actually reaches, versus what the foundation asks for. These
|
|
139
140
|
// agree through a link and drift through a copy — the drift is what runs.
|
|
140
141
|
const foundationPkg = readJson(join(foundation.path, 'package.json'))
|
|
141
|
-
const declared = {
|
|
142
|
+
const declared = {
|
|
143
|
+
...foundationPkg?.dependencies,
|
|
144
|
+
...foundationPkg?.peerDependencies
|
|
145
|
+
}
|
|
142
146
|
|
|
143
147
|
for (const [name, spec] of Object.entries(declared)) {
|
|
144
148
|
if (!name.startsWith('@uniweb/')) continue
|
|
@@ -160,7 +164,7 @@ export function checkSiteInstall(site, foundation, workspaceDir) {
|
|
|
160
164
|
` resolved from ${rel(linkPath)}: ${reached}\n` +
|
|
161
165
|
` ${name} is bundled into the foundation, so the dev server runs the version it\n` +
|
|
162
166
|
` reaches here. A build reads the workspace tree and may compile a different one.`,
|
|
163
|
-
remedy: 'Reinstall so the resolved tree matches what is declared'
|
|
167
|
+
remedy: 'Reinstall so the resolved tree matches what is declared'
|
|
164
168
|
})
|
|
165
169
|
}
|
|
166
170
|
}
|
package/src/utils/interactive.js
CHANGED
|
@@ -37,7 +37,7 @@ export function getCliPrefix() {
|
|
|
37
37
|
* @returns {string[]}
|
|
38
38
|
*/
|
|
39
39
|
export function stripNonInteractiveFlag(args) {
|
|
40
|
-
return args.filter(a => a !== '--non-interactive')
|
|
40
|
+
return args.filter((a) => a !== '--non-interactive')
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -46,8 +46,8 @@ export function stripNonInteractiveFlag(args) {
|
|
|
46
46
|
* @returns {string}
|
|
47
47
|
*/
|
|
48
48
|
export function formatOptions(options) {
|
|
49
|
-
const maxLen = Math.max(...options.map(o => o.label.length))
|
|
49
|
+
const maxLen = Math.max(...options.map((o) => o.label.length))
|
|
50
50
|
return options
|
|
51
|
-
.map(o => ` ${o.label.padEnd(maxLen + 3)}${o.description}`)
|
|
51
|
+
.map((o) => ` ${o.label.padEnd(maxLen + 3)}${o.description}`)
|
|
52
52
|
.join('\n')
|
|
53
53
|
}
|
package/src/utils/json-file.js
CHANGED
|
@@ -62,7 +62,11 @@ export function writeJsonPreservingStyle(filePath, obj, originalSrc = null) {
|
|
|
62
62
|
* @param {object} obj
|
|
63
63
|
* @param {string|null} [originalSrc]
|
|
64
64
|
*/
|
|
65
|
-
export async function writeJsonPreservingStyleAsync(
|
|
66
|
-
|
|
65
|
+
export async function writeJsonPreservingStyleAsync(
|
|
66
|
+
filePath,
|
|
67
|
+
obj,
|
|
68
|
+
originalSrc = null
|
|
69
|
+
) {
|
|
70
|
+
const src = originalSrc ?? (await readFile(filePath, 'utf8'))
|
|
67
71
|
await writeFile(filePath, stringifyJsonLike(obj, src))
|
|
68
72
|
}
|
package/src/utils/names.js
CHANGED
|
@@ -25,9 +25,15 @@ import { join } from 'node:path'
|
|
|
25
25
|
* that's a real constraint, not aesthetic.
|
|
26
26
|
*/
|
|
27
27
|
const RESERVED_NAMES = new Set([
|
|
28
|
-
'default',
|
|
29
|
-
'
|
|
30
|
-
'
|
|
28
|
+
'default',
|
|
29
|
+
'undefined',
|
|
30
|
+
'null',
|
|
31
|
+
'true',
|
|
32
|
+
'false',
|
|
33
|
+
'node_modules',
|
|
34
|
+
'package',
|
|
35
|
+
'dist',
|
|
36
|
+
'build'
|
|
31
37
|
])
|
|
32
38
|
|
|
33
39
|
/**
|
|
@@ -38,9 +44,12 @@ const RESERVED_NAMES = new Set([
|
|
|
38
44
|
*/
|
|
39
45
|
export function validatePackageName(name, existingNames) {
|
|
40
46
|
if (!name) return 'Name is required'
|
|
41
|
-
if (!/^[a-z0-9-]+$/.test(name))
|
|
42
|
-
|
|
43
|
-
if (
|
|
47
|
+
if (!/^[a-z0-9-]+$/.test(name))
|
|
48
|
+
return 'Use lowercase letters, numbers, and hyphens'
|
|
49
|
+
if (RESERVED_NAMES.has(name))
|
|
50
|
+
return `"${name}" is a reserved name — choose a different one`
|
|
51
|
+
if (existingNames?.has(name))
|
|
52
|
+
return `"${name}" already exists in this workspace`
|
|
44
53
|
return true
|
|
45
54
|
}
|
|
46
55
|
|
package/src/utils/placement.js
CHANGED
|
@@ -8,10 +8,18 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/** Foundation placement defaults (folder `src/`, package `src`). */
|
|
11
|
-
export const FOUNDATION_KIND = {
|
|
11
|
+
export const FOUNDATION_KIND = {
|
|
12
|
+
defaultDir: 'src',
|
|
13
|
+
defaultPkg: 'src',
|
|
14
|
+
projectSub: 'src'
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
/** Site placement defaults (folder `site/`, package `site`). */
|
|
14
|
-
export const SITE_KIND = {
|
|
18
|
+
export const SITE_KIND = {
|
|
19
|
+
defaultDir: 'site',
|
|
20
|
+
defaultPkg: 'site',
|
|
21
|
+
projectSub: 'site'
|
|
22
|
+
}
|
|
15
23
|
|
|
16
24
|
/**
|
|
17
25
|
* Resolve where a foundation or site should be placed, given the user's input.
|
|
@@ -56,13 +64,13 @@ export function resolvePlacement(rootDir, name, opts, kind) {
|
|
|
56
64
|
const last = name.split('/').filter(Boolean).pop()
|
|
57
65
|
return {
|
|
58
66
|
relativePath: `${parent}/${name}`.replace(/\/+/g, '/'),
|
|
59
|
-
packageName: last
|
|
67
|
+
packageName: last
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
const lastSegment = parent.split('/').filter(Boolean).pop() || parent
|
|
63
71
|
return {
|
|
64
72
|
relativePath: parent,
|
|
65
|
-
packageName: lastSegment
|
|
73
|
+
packageName: lastSegment
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
|
|
@@ -72,7 +80,7 @@ export function resolvePlacement(rootDir, name, opts, kind) {
|
|
|
72
80
|
const lastSegment = relativePath.split('/').filter(Boolean).pop()
|
|
73
81
|
return {
|
|
74
82
|
relativePath,
|
|
75
|
-
packageName: lastSegment
|
|
83
|
+
packageName: lastSegment
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
|
|
@@ -80,7 +88,7 @@ export function resolvePlacement(rootDir, name, opts, kind) {
|
|
|
80
88
|
if (name) {
|
|
81
89
|
return {
|
|
82
90
|
relativePath: name,
|
|
83
|
-
packageName: name
|
|
91
|
+
packageName: name
|
|
84
92
|
}
|
|
85
93
|
}
|
|
86
94
|
|
|
@@ -88,13 +96,13 @@ export function resolvePlacement(rootDir, name, opts, kind) {
|
|
|
88
96
|
if (opts.project) {
|
|
89
97
|
return {
|
|
90
98
|
relativePath: `${opts.project}/${kind.projectSub}`,
|
|
91
|
-
packageName: `${opts.project}-${kind.projectSub}
|
|
99
|
+
packageName: `${opts.project}-${kind.projectSub}`
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
// 5. Default placement.
|
|
96
104
|
return {
|
|
97
105
|
relativePath: kind.defaultDir,
|
|
98
|
-
packageName: kind.defaultPkg
|
|
106
|
+
packageName: kind.defaultPkg
|
|
99
107
|
}
|
|
100
108
|
}
|