@timekast/factory 1.11.0 → 1.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.
@@ -18,6 +18,7 @@ import { execa } from 'execa';
18
18
  import { FACTORY_REPO } from '../lib/constants.js';
19
19
  import { hasLockfile, readLockfile } from '../lib/lockfile.js';
20
20
  import { compare } from '../lib/semver.js';
21
+ import { isKitReleaseTag } from '../lib/unpack.js';
21
22
  /**
22
23
  * The BETA channel banner (BETA-005). Emitted by `status` and `doctor` when the
23
24
  * lockfile is on the beta channel (`lockfile.beta === true`) so the dev always
@@ -47,7 +48,9 @@ async function fetchLatestRelease() {
47
48
  '30',
48
49
  ]);
49
50
  const releases = JSON.parse(stdout);
50
- const stable = releases.find((r) => !r.isPrerelease);
51
+ // Only consider the KIT release line (`v*`): the repo also hosts `launcher-v*`
52
+ // releases, which must never be mistaken for the latest kit version.
53
+ const stable = releases.find((r) => !r.isPrerelease && isKitReleaseTag(r.tagName));
51
54
  if (!stable)
52
55
  return null;
53
56
  return stripVersionPrefix(stable.tagName);
@@ -22,10 +22,31 @@ import { FACTORY_REPO, MANIFEST_FILE, TIMEKAST_DIR } from './constants.js';
22
22
  import { compare } from './semver.js';
23
23
  /** Download the profile tarball into `tmpDir`; returns the tarball path. */
24
24
  export async function downloadProfileTarball(profile, tmpDir) {
25
+ // List releases and resolve the latest KIT release by tag, then download BY TAG.
26
+ // We do NOT let `gh` resolve repo-wide "latest": the repo also hosts the launcher
27
+ // release line (`launcher-v*`), whose assets are not kit tarballs, so "latest"
28
+ // can point at a launcher release. Filtering to `v*` keeps the kit line isolated.
29
+ const { stdout } = await execa('gh', [
30
+ 'release',
31
+ 'list',
32
+ '--repo',
33
+ FACTORY_REPO,
34
+ '--json',
35
+ 'tagName,isPrerelease',
36
+ '--limit',
37
+ '30',
38
+ ]);
39
+ const releases = JSON.parse(stdout);
40
+ const latestKit = resolveLatestKitRelease(releases);
41
+ if (!latestKit) {
42
+ throw new CLIError(`No se encontró un release del kit (\`v*\`) publicado en \`${FACTORY_REPO}\`.\n` +
43
+ 'Verifica que exista un release con los assets de distribución.');
44
+ }
25
45
  try {
26
46
  await execa('gh', [
27
47
  'release',
28
48
  'download',
49
+ latestKit.tagName,
29
50
  '--repo',
30
51
  FACTORY_REPO,
31
52
  '--pattern',
@@ -35,12 +56,12 @@ export async function downloadProfileTarball(profile, tmpDir) {
35
56
  ]);
36
57
  }
37
58
  catch {
38
- throw new CLIError(`No se encontró un release publicado con el perfil \`${profile}\` en \`${FACTORY_REPO}\`.\n` +
39
- 'Verifica que exista un release con los assets de distribución.');
59
+ throw new CLIError(`No se encontró el asset \`${profile}\` en el release del kit \`${latestKit.tagName}\` de \`${FACTORY_REPO}\`.\n` +
60
+ 'Verifica que el release contenga los assets de distribución.');
40
61
  }
41
62
  const downloaded = readdirSync(tmpDir).filter((f) => f.endsWith('.tgz'));
42
63
  if (downloaded.length === 0) {
43
- throw new CLIError(`El release no contiene un asset para el perfil \`${profile}\`. ` +
64
+ throw new CLIError(`El release del kit \`${latestKit.tagName}\` no contiene un asset para el perfil \`${profile}\`. ` +
44
65
  'Verifica el release del Factory.');
45
66
  }
46
67
  return path.join(tmpDir, downloaded[0]);
@@ -81,6 +102,28 @@ export function resolveLatestBeta(releases) {
81
102
  // semver-max: `compare` strips the leading `v` itself, so tags pass verbatim.
82
103
  return betas.reduce((max, r) => (compare(r.tagName, max.tagName) > 0 ? r : max));
83
104
  }
105
+ /**
106
+ * True when a release tag belongs to the KIT's release line (`v<semver>`, e.g.
107
+ * `v10.9.0`). The same repo hosts the launcher line (`launcher-v*`) and — via npm,
108
+ * not GitHub Releases — the CLI line (`cli-v*`); neither starts with `v<digit>`,
109
+ * so this predicate isolates the kit from every other release line in the repo.
110
+ * Without it, `gh`'s repo-wide "latest" / a non-prerelease `launcher-v*` could be
111
+ * mistaken for the latest kit version and break `status` / `update`.
112
+ */
113
+ export function isKitReleaseTag(tagName) {
114
+ return /^v\d/.test(tagName);
115
+ }
116
+ /**
117
+ * Pick the latest STABLE kit release from a list: filter to kit tags (excluding
118
+ * `launcher-v*` / any non-kit line) AND non-prerelease, then the highest by semver.
119
+ * Returns null when no stable kit release exists.
120
+ */
121
+ export function resolveLatestKitRelease(releases) {
122
+ const kit = releases.filter((r) => !r.isPrerelease && isKitReleaseTag(r.tagName));
123
+ if (kit.length === 0)
124
+ return null;
125
+ return kit.reduce((max, r) => (compare(r.tagName, max.tagName) > 0 ? r : max));
126
+ }
84
127
  /**
85
128
  * Download the latest beta profile tarball into `tmpDir`; returns the tarball
86
129
  * path. Beta counterpart of {@link downloadProfileTarball}: instead of letting
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",