fimo 0.3.4 → 0.3.5-staging.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,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-07T11:27:20.207Z",
4
+ "bundledAt": "2026-07-07T12:27:33.763Z",
5
5
  "cliVersion": "0.3.4",
6
6
  "external": [
7
7
  "oxc-parser",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fimo",
3
- "version": "0.3.4",
3
+ "version": "0.3.5-staging.1",
4
4
  "description": "Fimo CLI - create, deploy, and manage Fimo projects",
5
5
  "bin": {
6
6
  "fimo": "dist/cli/index.js"
package/release.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "source": "npm",
3
- "apiUrl": "https://api.fimo.ai",
4
- "webUrl": "https://fimo.ai"
3
+ "apiUrl": "https://api.staging.fimo.team",
4
+ "webUrl": "https://staging.fimo.team"
5
5
  }
@@ -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",
24
+ "fimo": "0.3.5-staging.1",
25
25
  "input-otp": "^1.4.2",
26
26
  "isbot": "^5",
27
27
  "lucide-react": "^0.577.0",