create-stator 1.3.0 → 1.5.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/index.js +57 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -42,13 +42,18 @@ const TEMPLATES = [
|
|
|
42
42
|
label: 'With auth',
|
|
43
43
|
hint: 'accounts, guarded login, roles, session rotation (Node 24+)',
|
|
44
44
|
},
|
|
45
|
+
{
|
|
46
|
+
value: 'weather',
|
|
47
|
+
label: 'Weather',
|
|
48
|
+
hint: 'live forecasts over SSE, entry-effect data loading, a canvas island',
|
|
49
|
+
},
|
|
45
50
|
]
|
|
46
51
|
|
|
47
52
|
/** First-party templates resolve into the monorepo's examples/. */
|
|
48
53
|
const FIRST_PARTY = 'gh:statorjs/stator/examples'
|
|
49
54
|
/** Scaffolded apps get a real semver for the framework (the examples
|
|
50
55
|
* themselves use workspace linking in-repo). Bumped with releases. */
|
|
51
|
-
const STATOR_RANGE = '^1.
|
|
56
|
+
const STATOR_RANGE = '^1.4.0'
|
|
52
57
|
|
|
53
58
|
const { values: flags, positionals } = parseArgs({
|
|
54
59
|
allowPositionals: true,
|
|
@@ -142,8 +147,18 @@ if (!template) {
|
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
// --- fetch ---
|
|
145
|
-
|
|
146
|
-
|
|
150
|
+
// Source and ref are held separately so provenance can record them apart:
|
|
151
|
+
// community sources may embed a ref (`github:user/repo/path#branch`), while
|
|
152
|
+
// first-party templates take it from --ref.
|
|
153
|
+
const sourceBase = (isRemoteSource(template) ? template : `${FIRST_PARTY}/${template}`).split(
|
|
154
|
+
'#',
|
|
155
|
+
)[0]
|
|
156
|
+
const refName = isRemoteSource(template)
|
|
157
|
+
? template.includes('#')
|
|
158
|
+
? template.slice(template.indexOf('#') + 1)
|
|
159
|
+
: null
|
|
160
|
+
: (flags.ref ?? null)
|
|
161
|
+
const source = refName ? `${sourceBase}#${refName}` : sourceBase
|
|
147
162
|
const s = p.spinner()
|
|
148
163
|
s.start(`Fetching ${template}`)
|
|
149
164
|
try {
|
|
@@ -170,6 +185,18 @@ try {
|
|
|
170
185
|
pkg.dependencies['@statorjs/stator'] = STATOR_RANGE
|
|
171
186
|
}
|
|
172
187
|
delete pkg.private
|
|
188
|
+
// Record where this scaffold came from. The commit is the merge base for a
|
|
189
|
+
// later template update (docs: recipes/updating-from-a-template) — the one
|
|
190
|
+
// fact that can only be captured now. Best-effort: null when offline.
|
|
191
|
+
pkg.stator = {
|
|
192
|
+
template: {
|
|
193
|
+
v: 1,
|
|
194
|
+
source: sourceBase,
|
|
195
|
+
ref: refName,
|
|
196
|
+
commit: resolveTemplateCommit(sourceBase, refName),
|
|
197
|
+
scaffoldedAt: new Date().toISOString(),
|
|
198
|
+
},
|
|
199
|
+
}
|
|
173
200
|
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`)
|
|
174
201
|
s.stop(`Scaffolded ${name} (${template})`)
|
|
175
202
|
} catch {
|
|
@@ -231,3 +258,30 @@ function bailIfCancelled(v) {
|
|
|
231
258
|
process.exit(0)
|
|
232
259
|
}
|
|
233
260
|
}
|
|
261
|
+
|
|
262
|
+
/** Resolve the template ref to a commit sha for provenance. Best-effort by
|
|
263
|
+
* design: no git, no network, or an unknown provider all return null rather
|
|
264
|
+
* than slow down or fail the scaffold. Annotated tags resolve to the peeled
|
|
265
|
+
* commit (`^{}`) when the host lists one. */
|
|
266
|
+
function resolveTemplateCommit(src, refName) {
|
|
267
|
+
if (refName && /^[0-9a-f]{40}$/.test(refName)) return refName
|
|
268
|
+
// giget providers that are also plain git hosts, for `git ls-remote`.
|
|
269
|
+
const hosts = {
|
|
270
|
+
gh: 'github.com',
|
|
271
|
+
github: 'github.com',
|
|
272
|
+
gitlab: 'gitlab.com',
|
|
273
|
+
bitbucket: 'bitbucket.org',
|
|
274
|
+
}
|
|
275
|
+
const m = src.match(/^([a-z]+):([^/]+\/[^/#]+)/)
|
|
276
|
+
const host = m && hosts[m[1]]
|
|
277
|
+
if (!host) return null
|
|
278
|
+
const out = spawnSync('git', ['ls-remote', `https://${host}/${m[2]}`, refName ?? 'HEAD'], {
|
|
279
|
+
encoding: 'utf8',
|
|
280
|
+
timeout: 15_000,
|
|
281
|
+
})
|
|
282
|
+
if (out.status !== 0 || !out.stdout) return null
|
|
283
|
+
const lines = out.stdout.trim().split('\n')
|
|
284
|
+
const line = lines.find((l) => l.includes('^{}')) ?? lines[0]
|
|
285
|
+
const sha = line?.split(/\s+/)[0]
|
|
286
|
+
return sha && /^[0-9a-f]{40}$/.test(sha) ? sha : null
|
|
287
|
+
}
|