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.
Files changed (129) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +532 -3
  3. package/package.json +60 -11
  4. package/partials/adding-pages.hbs +77 -0
  5. package/partials/agents.md +3000 -0
  6. package/partials/ai-assistance.hbs +28 -0
  7. package/partials/components-docs.hbs +13 -0
  8. package/partials/config-reference.hbs +66 -0
  9. package/partials/deployment.hbs +13 -0
  10. package/partials/learn-more.hbs +6 -0
  11. package/partials/quick-start.hbs +8 -0
  12. package/partials/search-docs.hbs +110 -0
  13. package/src/backend/asset-download.js +222 -0
  14. package/src/backend/client.js +986 -0
  15. package/src/backend/foundation-bring-along.js +692 -0
  16. package/src/backend/payment-handoff.js +362 -0
  17. package/src/backend/service-request.js +210 -0
  18. package/src/backend/site-media.js +255 -0
  19. package/src/backend/site-preview.js +77 -0
  20. package/src/backend/site-sync.js +2515 -0
  21. package/src/backend/workspace.js +234 -0
  22. package/src/commands/add.js +1900 -0
  23. package/src/commands/build.js +1214 -0
  24. package/src/commands/clone.js +466 -0
  25. package/src/commands/content.js +202 -0
  26. package/src/commands/deploy.js +537 -0
  27. package/src/commands/dev.js +202 -0
  28. package/src/commands/docs.js +530 -0
  29. package/src/commands/doctor.js +1982 -0
  30. package/src/commands/export.js +116 -0
  31. package/src/commands/families.js +82 -0
  32. package/src/commands/forget.js +197 -0
  33. package/src/commands/handoff.js +21 -0
  34. package/src/commands/i18n.js +1707 -0
  35. package/src/commands/inspect.js +375 -0
  36. package/src/commands/invite.js +24 -0
  37. package/src/commands/org.js +96 -0
  38. package/src/commands/publish.js +1087 -0
  39. package/src/commands/pull.js +1257 -0
  40. package/src/commands/push.js +663 -0
  41. package/src/commands/refresh.js +212 -0
  42. package/src/commands/register.js +1092 -0
  43. package/src/commands/rename.js +647 -0
  44. package/src/commands/site.js +358 -0
  45. package/src/commands/snapshot.js +461 -0
  46. package/src/commands/starter.js +206 -0
  47. package/src/commands/status.js +302 -0
  48. package/src/commands/sync.js +93 -0
  49. package/src/commands/template.js +107 -0
  50. package/src/commands/update.js +887 -0
  51. package/src/commands/validate.js +376 -0
  52. package/src/framework-index.json +248 -0
  53. package/src/index.js +2241 -0
  54. package/src/templates/fetchers/github.js +113 -0
  55. package/src/templates/fetchers/npm.js +85 -0
  56. package/src/templates/fetchers/release.js +92 -0
  57. package/src/templates/index.js +190 -0
  58. package/src/templates/processor.js +333 -0
  59. package/src/templates/resolver.js +232 -0
  60. package/src/templates/validator.js +155 -0
  61. package/src/utils/agents-stamp.js +110 -0
  62. package/src/utils/args.js +126 -0
  63. package/src/utils/asset-upload.js +270 -0
  64. package/src/utils/bytes.js +30 -0
  65. package/src/utils/code-upload.js +394 -0
  66. package/src/utils/config.js +417 -0
  67. package/src/utils/conformance.js +274 -0
  68. package/src/utils/dep-survey.js +176 -0
  69. package/src/utils/destination-prompt.js +146 -0
  70. package/src/utils/discover.js +75 -0
  71. package/src/utils/env.js +22 -0
  72. package/src/utils/exit.js +30 -0
  73. package/src/utils/fetch-retry.js +109 -0
  74. package/src/utils/flag-guard.js +246 -0
  75. package/src/utils/foundation-name.js +179 -0
  76. package/src/utils/git.js +342 -0
  77. package/src/utils/host-prompt.js +51 -0
  78. package/src/utils/install-integrity.js +210 -0
  79. package/src/utils/interactive.js +118 -0
  80. package/src/utils/json-file.js +72 -0
  81. package/src/utils/names.js +91 -0
  82. package/src/utils/placement.js +151 -0
  83. package/src/utils/pm.js +165 -0
  84. package/src/utils/preview.js +20 -0
  85. package/src/utils/pull-written.js +132 -0
  86. package/src/utils/records-guard.js +88 -0
  87. package/src/utils/registry-auth.js +994 -0
  88. package/src/utils/registry-orgs.js +290 -0
  89. package/src/utils/scaffold.js +483 -0
  90. package/src/utils/schemaless-report.js +30 -0
  91. package/src/utils/semver-precedence.js +80 -0
  92. package/src/utils/session-file.js +95 -0
  93. package/src/utils/site-content-refs.js +23 -0
  94. package/src/utils/site-copies.js +123 -0
  95. package/src/utils/site-data-upload.js +302 -0
  96. package/src/utils/site-identity.js +137 -0
  97. package/src/utils/snapshot-settings.js +112 -0
  98. package/src/utils/update-check.js +302 -0
  99. package/src/utils/uwx-read.js +157 -0
  100. package/src/utils/workspace.js +223 -0
  101. package/src/versions.js +467 -0
  102. package/starter/foundation/main.js +50 -0
  103. package/starter/foundation/sections/Section/index.jsx +95 -0
  104. package/starter/foundation/sections/Section/meta.js +44 -0
  105. package/starter/site/pages/about/1-about.md.hbs +13 -0
  106. package/starter/site/pages/about/page.yml +3 -0
  107. package/starter/site/pages/home/1-welcome.md.hbs +14 -0
  108. package/starter/site/pages/home/page.yml +3 -0
  109. package/templates/foundation/_gitignore +5 -0
  110. package/templates/foundation/components/.gitkeep +0 -0
  111. package/templates/foundation/main.js.hbs +33 -0
  112. package/templates/foundation/package.json.hbs +51 -0
  113. package/templates/foundation/sections/.gitkeep +0 -0
  114. package/templates/foundation/styles.css +6 -0
  115. package/templates/foundation/vite.config.js +3 -0
  116. package/templates/site/_gitignore +12 -0
  117. package/templates/site/entry.js +7 -0
  118. package/templates/site/index.html.hbs +13 -0
  119. package/templates/site/package.json.hbs +27 -0
  120. package/templates/site/public/favicon.svg +7 -0
  121. package/templates/site/site.yml.hbs +91 -0
  122. package/templates/site/theme.yml +99 -0
  123. package/templates/site/vite.config.js +7 -0
  124. package/templates/workspace/AGENTS.md.hbs +2 -0
  125. package/templates/workspace/README.md.hbs +87 -0
  126. package/templates/workspace/_gitignore +29 -0
  127. package/templates/workspace/_vscode/settings.json +6 -0
  128. package/templates/workspace/package.json.hbs +20 -0
  129. package/templates/workspace/pnpm-workspace.yaml.hbs +26 -0
@@ -0,0 +1,212 @@
1
+ /**
2
+ * uniweb refresh — catch up with everything outside your working copy.
3
+ *
4
+ * A developer on a synced site has TWO independent external sources, and nothing
5
+ * in the tool used to connect them:
6
+ *
7
+ * - the git remote — teammates' commits;
8
+ * - the backend — content authors' edits, made in the app.
9
+ *
10
+ * Someone who runs only `git pull` silently misses every app edit; someone who
11
+ * runs only `uniweb pull` misses their teammates. The person most likely to be
12
+ * caught out is exactly the one starting their day believing they are current.
13
+ * This is the one command for that: run it in the morning, or before a milestone.
14
+ *
15
+ * READ-ONLY, and that is the load-bearing decision. It never pushes. Because it
16
+ * cannot ship anything, it can be run reflexively without weighing consequences —
17
+ * which is the whole point of a start-of-day command. A version that also pushed
18
+ * would be one you had to think about first, and so one people would stop running.
19
+ *
20
+ * Order is deliberate: git first, then the backend. The three-way merge's common
21
+ * ancestor is the COMMITTED version of each file, so taking teammates' commits
22
+ * first makes that ancestor fresher and the merge more accurate. And if git itself
23
+ * conflicts, refresh stops there — one source of conflict at a time.
24
+ *
25
+ * Exits NON-ZERO when a merge left conflicts, the way a conflicted `git merge`
26
+ * does. That is what makes `uniweb refresh && uniweb push` correct by
27
+ * construction: without it, the obvious one-liner ships conflict markers into live
28
+ * content. If a `sync` verb is ever wanted, it is that chain.
29
+ *
30
+ * Usage:
31
+ * uniweb refresh git pull, then merge the backend's content
32
+ * uniweb refresh --no-git skip the git remote; backend only
33
+ * uniweb refresh --no-backend skip the backend; git only
34
+ *
35
+ * The backend is the one you are logged in to, as for every backend verb (no flag picks it).
36
+ */
37
+
38
+ import { readFileSync } from 'node:fs'
39
+ import { join } from 'node:path'
40
+ import yaml from 'js-yaml'
41
+
42
+ import { resolveSiteDir } from './deploy.js'
43
+ import {
44
+ isGitRepo,
45
+ hasRemote,
46
+ pullRemote,
47
+ headProvenance
48
+ } from '../utils/git.js'
49
+ import { probeUnpushed } from '../backend/site-sync.js'
50
+ import { resolveBackendOrigin } from '../backend/client.js'
51
+ import { readBackendState } from '@uniweb/build/uwx'
52
+ import { checkFlags } from '../utils/flag-guard.js'
53
+
54
+ const c = {
55
+ reset: '\x1b[0m',
56
+ bold: '\x1b[1m',
57
+ dim: '\x1b[2m',
58
+ cyan: '\x1b[36m',
59
+ green: '\x1b[32m',
60
+ yellow: '\x1b[33m',
61
+ red: '\x1b[31m'
62
+ }
63
+ const say = {
64
+ ok: (m) => console.log(`${c.green}✓${c.reset} ${m}`),
65
+ info: (m) => console.log(`${c.cyan}→${c.reset} ${m}`),
66
+ warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
67
+ err: (m) => console.error(`${c.red}✗${c.reset} ${m}`),
68
+ dim: (m) => console.log(` ${c.dim}${m}${c.reset}`)
69
+ }
70
+
71
+ // A site is backend-synced once it has an identity to pull by — on THIS backend,
72
+ // from sync.json. It read `site.yml::$uuid`, so after step 4 refresh treated every
73
+ // project as unsynced and skipped the backend half of its check.
74
+ //
75
+ // ⛔ `backend` is the one the delegated pull will use: the backend the user is logged in
76
+ // to. (For part of 2026-09-21, `refresh --backend X` checked one backend's sync state and
77
+ // pulled from another; the flag is gone from the backend verbs since.)
78
+ function siteContentUuid(siteDir, backend) {
79
+ try {
80
+ return readBackendState(siteDir, backend).site?.uuid || null
81
+ } catch {
82
+ return null
83
+ }
84
+ }
85
+
86
+ /**
87
+ * @param {string[]} args
88
+ * @param {object} [deps] - injectable seams for testing, mirroring pull.js:
89
+ * `resolveSiteDir`, and `pull` (so the backend step can be driven without a
90
+ * network or a real backend).
91
+ */
92
+ export async function refresh(args = [], deps = {}) {
93
+ // An unrecognized flag is invisible to a literal scan, so it silently keeps the
94
+ // default — and for `--backend` the default can be production. This verb never
95
+ // sent data anywhere, which is why it went unguarded; but it DELEGATES to `pull`,
96
+ // and a mistyped `--backend` is not forwarded, so the pull runs against whatever
97
+ // the origin ladder resolves instead of the backend the user named.
98
+ //
99
+ // `sync` validates the UNION of both halves and forwards raw argv, so a legal
100
+ // `uniweb sync --force` would be rejected here. It passes `skipFlagCheck`.
101
+ if (!deps.skipFlagCheck) {
102
+ const bad = checkFlags('refresh', args)
103
+ if (bad) {
104
+ say.err(bad.message)
105
+ return { exitCode: 2 }
106
+ }
107
+ }
108
+ const skipGit = args.includes('--no-git')
109
+ const skipBackend = args.includes('--no-backend')
110
+ const resolveSite = deps.resolveSiteDir || resolveSiteDir
111
+ const siteDir = await resolveSite(args, 'refresh')
112
+
113
+ // Which sources this run actually consulted. Reported at the end, because
114
+ // "up to date" is a different claim from "up to date with the two things I
115
+ // happened to be able to check" — and the second is all we can ever honestly
116
+ // say. Silently skipping a source is how someone concludes they are current
117
+ // when they are not.
118
+ const consulted = []
119
+ const skipped = []
120
+ let conflicts = 0
121
+
122
+ // ── 1. the git remote ─────────────────────────────────────────────────────
123
+ if (skipGit) {
124
+ skipped.push('git (--no-git)')
125
+ } else if (!isGitRepo(siteDir)) {
126
+ skipped.push('git (not a repository)')
127
+ } else if (!hasRemote(siteDir)) {
128
+ skipped.push('git (no remote configured)')
129
+ } else {
130
+ say.info('Pulling from the git remote…')
131
+ const r = pullRemote(siteDir)
132
+ if (!r.ok) {
133
+ say.err('git pull failed — resolve this before continuing.')
134
+ for (const line of r.message.split('\n').slice(0, 6)) say.dim(line)
135
+ // Deliberately stop. Layering the backend's content on top of an unresolved
136
+ // git state gives the user two independent conflicts at once, and no clear
137
+ // order to address them in.
138
+ return { exitCode: 1 }
139
+ }
140
+ say.dim(
141
+ r.changed
142
+ ? 'Took new commits from the remote.'
143
+ : 'Already up to date with the remote.'
144
+ )
145
+ consulted.push('git')
146
+ }
147
+
148
+ // ── 2. the backend ────────────────────────────────────────────────────────
149
+ // The backend the delegated pull talks to — the one you are logged in to
150
+ // (resolveBackendOrigin). Everything below asks about THAT one.
151
+ const backend = resolveBackendOrigin()
152
+ if (skipBackend) {
153
+ skipped.push('backend (--no-backend)')
154
+ } else if (!siteContentUuid(siteDir, backend)) {
155
+ skipped.push('backend (this site has never been synced)')
156
+ } else {
157
+ say.info("Merging the backend's content…")
158
+ const pull = deps.pull || (await import('./pull.js')).pull
159
+ // `--merge` rather than a plain pull: an author editing a different part of the
160
+ // same section is not a conflict, and should not be presented as one.
161
+ // Nothing of refresh's own argv is forwarded: the pull goes to the backend you are
162
+ // logged in to, as refresh does, and authenticates the same way.
163
+ const res = await pull(['--merge'])
164
+ conflicts = res?.merge?.conflicted?.length ?? 0
165
+ if (res?.exitCode && !conflicts) {
166
+ // Failed for a reason other than conflicts — say so plainly rather than
167
+ // reporting a clean refresh over a lane that never ran.
168
+ say.err('Could not merge the backend content.')
169
+ return { exitCode: res.exitCode }
170
+ }
171
+ consulted.push('backend')
172
+ }
173
+
174
+ // ── 3. where that leaves you ──────────────────────────────────────────────
175
+ console.log('')
176
+ if (consulted.length) say.dim(`Checked: ${consulted.join(', ')}`)
177
+ for (const s of skipped) say.dim(`Skipped: ${s}`)
178
+
179
+ const git = headProvenance(siteDir)
180
+ if (git)
181
+ say.dim(
182
+ `Commit : ${git.sha.slice(0, 8)}${git.dirty ? ' (working tree has changes)' : ''}`
183
+ )
184
+
185
+ // What is still yours to send. The point of a milestone check is knowing both
186
+ // directions, not just that you took what was waiting.
187
+ if (!skipBackend && siteContentUuid(siteDir, backend)) {
188
+ try {
189
+ // Whose asset ids the comparison reads — see status.js. Offline, so the
190
+ // origin is resolved rather than taken from a client.
191
+ const probe = await probeUnpushed(siteDir, { backend })
192
+ if (probe.changed)
193
+ say.dim(
194
+ `Unpushed: ${probe.changed} entit${probe.changed === 1 ? 'y' : 'ies'} changed locally`
195
+ )
196
+ } catch {
197
+ /* the probe is a courtesy; never fail a refresh over it */
198
+ }
199
+ }
200
+
201
+ console.log('')
202
+ if (conflicts) {
203
+ say.err(
204
+ `${conflicts} file(s) need you to resolve conflicts before pushing.`
205
+ )
206
+ return { exitCode: 1 }
207
+ }
208
+ say.ok('Up to date.')
209
+ return { exitCode: 0 }
210
+ }
211
+
212
+ export default refresh