fimo 0.3.4-staging.27 → 0.3.5-experimental.1

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-07T11:04:00.742Z",
5
- "cliVersion": "0.3.3",
4
+ "bundledAt": "2026-07-07T12:46:54.057Z",
5
+ "cliVersion": "0.3.4",
6
6
  "external": [
7
7
  "oxc-parser",
8
8
  "fsevents"
package/dist/cli/index.js CHANGED
@@ -103822,7 +103822,8 @@ async function loadPublicTenantLabels(cwd, locale) {
103822
103822
  try {
103823
103823
  const url2 = new URL("/labels", apiUrl);
103824
103824
  url2.searchParams.set("locale", locale);
103825
- const response = await fetchWithTimeout(url2, 4e3);
103825
+ const headers = env2.VITE_FIMO_BRANCH ? { "X-Fimo-Branch": env2.VITE_FIMO_BRANCH } : {};
103826
+ const response = await fetchWithTimeout(url2, 4e3, headers);
103826
103827
  if (!response.ok) {
103827
103828
  return null;
103828
103829
  }
@@ -103830,17 +103831,17 @@ async function loadPublicTenantLabels(cwd, locale) {
103830
103831
  if (Array.isArray(body.data)) {
103831
103832
  return labelsToRecord(body.data);
103832
103833
  }
103833
- return body.data?.labels ?? {};
103834
+ return body.data?.labels ?? null;
103834
103835
  } catch {
103835
103836
  return null;
103836
103837
  }
103837
103838
  }
103838
103839
  __name(loadPublicTenantLabels, "loadPublicTenantLabels");
103839
- async function fetchWithTimeout(url2, timeoutMs) {
103840
+ async function fetchWithTimeout(url2, timeoutMs, headers) {
103840
103841
  const controller = new AbortController();
103841
103842
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
103842
103843
  try {
103843
- return await fetch(url2, { signal: controller.signal });
103844
+ return await fetch(url2, { signal: controller.signal, headers });
103844
103845
  } finally {
103845
103846
  clearTimeout(timeout);
103846
103847
  }
@@ -103854,6 +103855,9 @@ async function readProjectEnv(cwd) {
103854
103855
  if (process.env.VITE_API_URL) {
103855
103856
  values.VITE_API_URL = process.env.VITE_API_URL;
103856
103857
  }
103858
+ if (process.env.VITE_FIMO_BRANCH) {
103859
+ values.VITE_FIMO_BRANCH = process.env.VITE_FIMO_BRANCH;
103860
+ }
103857
103861
  return values;
103858
103862
  }
103859
103863
  __name(readProjectEnv, "readProjectEnv");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.3.4-staging.27",
3
+ "version": "0.3.5-experimental.1",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
@@ -100,15 +100,44 @@ export function stripTemplateLockfile(cliRoot) {
100
100
  return () => writeFileSync(file, original);
101
101
  }
102
102
 
103
+ /**
104
+ * Next per-version prerelease counter: one past the highest already-published
105
+ * `<base>-<distTag>.<n>` on npm. Counting from what's actually published
106
+ * (instead of resetting blindly to 1) keeps the counter collision-proof even
107
+ * when the projected base flaps back to a value that already has prereleases —
108
+ * npm versions are immutable, so a blind reset would make the publish fail.
109
+ *
110
+ * @param {readonly string[]} existingVersions - all published versions of the package
111
+ * @param {string} base - X.Y.Z base (no prerelease suffix)
112
+ * @param {string} distTag
113
+ * @returns {number}
114
+ */
115
+ export function nextPrereleaseCounter(existingVersions, base, distTag) {
116
+ const prefix = `${base}-${distTag}.`;
117
+ let max = 0;
118
+ for (const version of existingVersions) {
119
+ if (!version.startsWith(prefix)) {
120
+ continue;
121
+ }
122
+ const n = Number(version.slice(prefix.length));
123
+ if (Number.isInteger(n) && n > max) {
124
+ max = n;
125
+ }
126
+ }
127
+ return max + 1;
128
+ }
129
+
103
130
  /**
104
131
  * Compute the version to publish for a channel.
105
132
  * - Stable (`latest`) publishes the exact package.json version.
106
- * - Prerelease channels synthesize a disposable `X.Y.Z-<distTag>.<n>`,
107
- * where `n` is the CI run number when available, else a timestamp.
133
+ * - Prerelease channels synthesize a disposable `X.Y.Z-<distTag>.<n>`.
134
+ * `n` counts per base version (`0.3.5-staging.1`, `.2`, …) when the
135
+ * published versions could be fetched from npm; otherwise it falls back
136
+ * to the CI run number, then a timestamp.
108
137
  *
109
138
  * @param {string} baseVersion - package.json version
110
139
  * @param {import('./channels.mjs').Channel} channel
111
- * @param {{ runId?: string }} [opts]
140
+ * @param {{ runId?: string, existingVersions?: readonly string[] }} [opts]
112
141
  * @returns {string}
113
142
  */
114
143
  export function computePublishVersion(baseVersion, channel, opts = {}) {
@@ -117,7 +146,9 @@ export function computePublishVersion(baseVersion, channel, opts = {}) {
117
146
  }
118
147
  // Strip any existing prerelease suffix from the base so we don't stack them.
119
148
  const base = baseVersion.split('-')[0];
120
- const n = opts.runId ?? String(Date.now());
149
+ const n = opts.existingVersions
150
+ ? String(nextPrereleaseCounter(opts.existingVersions, base, channel.distTag))
151
+ : (opts.runId ?? String(Date.now()));
121
152
  return `${base}-${channel.distTag}.${n}`;
122
153
  }
123
154
 
@@ -5,7 +5,7 @@ import { join } from 'node:path';
5
5
  import { afterEach, describe, expect, it } from 'vitest';
6
6
 
7
7
  // @ts-expect-error plain mjs module without types
8
- import { computePublishVersion, nextVersionFromChangesets } from './release-core.mjs';
8
+ import { computePublishVersion, nextPrereleaseCounter, nextVersionFromChangesets } from './release-core.mjs';
9
9
 
10
10
  const stagingChannel = { prerelease: true, distTag: 'staging' };
11
11
  const latestChannel = { prerelease: false, distTag: 'latest' };
@@ -22,6 +22,37 @@ describe('computePublishVersion', () => {
22
22
  it('strips an existing prerelease suffix before synthesizing', () => {
23
23
  expect(computePublishVersion('0.3.3-staging.9', stagingChannel, { runId: '26' })).toBe('0.3.3-staging.26');
24
24
  });
25
+
26
+ it('counts per base version when published versions are provided', () => {
27
+ const existingVersions = ['0.3.4', '0.3.5-staging.1', '0.3.5-staging.2'];
28
+ expect(computePublishVersion('0.3.5', stagingChannel, { runId: '30', existingVersions })).toBe('0.3.5-staging.3');
29
+ });
30
+
31
+ it('starts at 1 for a base with no published prereleases', () => {
32
+ const existingVersions = ['0.3.4', '0.3.4-staging.26', '0.3.4-staging.27'];
33
+ expect(computePublishVersion('0.3.5', stagingChannel, { runId: '30', existingVersions })).toBe('0.3.5-staging.1');
34
+ });
35
+ });
36
+
37
+ describe('nextPrereleaseCounter', () => {
38
+ it('returns 1 when nothing matches the base + tag', () => {
39
+ expect(nextPrereleaseCounter([], '0.3.5', 'staging')).toBe(1);
40
+ expect(nextPrereleaseCounter(['0.3.5', '0.3.5-experimental.4'], '0.3.5', 'staging')).toBe(1);
41
+ });
42
+
43
+ it('continues past the highest published counter for the base', () => {
44
+ const versions = ['0.3.4-staging.26', '0.3.4-staging.27', '0.3.3-staging.25'];
45
+ expect(nextPrereleaseCounter(versions, '0.3.4', 'staging')).toBe(28);
46
+ });
47
+
48
+ it('does not confuse prefix-sharing bases (0.3.4 vs 0.3.40)', () => {
49
+ expect(nextPrereleaseCounter(['0.3.40-staging.9'], '0.3.4', 'staging')).toBe(1);
50
+ });
51
+
52
+ it('ignores non-integer counters (timestamps still count, garbage does not)', () => {
53
+ expect(nextPrereleaseCounter(['0.3.4-staging.abc', '0.3.4-staging.2.5'], '0.3.4', 'staging')).toBe(1);
54
+ expect(nextPrereleaseCounter(['0.3.3-experimental.1783410168478'], '0.3.3', 'experimental')).toBe(1783410168479);
55
+ });
25
56
  });
26
57
 
27
58
  describe('nextVersionFromChangesets', () => {
@@ -71,6 +71,23 @@ function run(cmd, args, cwd) {
71
71
  }
72
72
  }
73
73
 
74
+ // All published versions of the package, or null when the registry can't be
75
+ // reached (offline, first-ever publish) — callers fall back to the
76
+ // run-number/timestamp counter.
77
+ function fetchPublishedVersions(name) {
78
+ const result = spawnSync('npm', ['view', name, 'versions', '--json'], { encoding: 'utf8' });
79
+ if (result.status !== 0) {
80
+ return null;
81
+ }
82
+ try {
83
+ const parsed = JSON.parse(result.stdout);
84
+ // npm view returns a bare string when exactly one version is published.
85
+ return Array.isArray(parsed) ? parsed : [parsed];
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
74
91
  const { channel: channelName, dryRun, bootstrap, plan } = parseArgs(process.argv.slice(2));
75
92
 
76
93
  if (!channelName) {
@@ -98,7 +115,14 @@ const baseVersion = channel.prerelease
98
115
  ? nextVersionFromChangesets(pkg.version, resolve(CLI_ROOT, '../../.changeset'))
99
116
  : pkg.version;
100
117
  const runId = process.env.GITHUB_RUN_NUMBER;
101
- const publishVersion = computePublishVersion(baseVersion, channel, { runId });
118
+ let existingVersions;
119
+ if (channel.prerelease) {
120
+ existingVersions = fetchPublishedVersions(pkg.name) ?? undefined;
121
+ if (!existingVersions) {
122
+ console.warn('[publish-npm] could not fetch published versions from npm — using run/timestamp counter.');
123
+ }
124
+ }
125
+ const publishVersion = computePublishVersion(baseVersion, channel, { runId, existingVersions });
102
126
 
103
127
  console.log(`[publish-npm] channel: ${channelName} (dist-tag: ${channel.distTag})`);
104
128
  console.log(
@@ -21,7 +21,7 @@
21
21
  "cmdk": "^1.1.1",
22
22
  "date-fns": "^4.1.0",
23
23
  "embla-carousel-react": "^8.6.0",
24
- "fimo": "0.3.4-staging.27",
24
+ "fimo": "0.3.5-experimental.1",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",