fimo 0.2.3-staging.8 → 0.2.3-staging.9

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,245 +0,0 @@
1
- #!/usr/bin/env node
2
- // Builds the Fimo CLI, packs it into a tarball, and uploads the tarball to
3
- // a Cloudflare R2 bucket via `wrangler r2 object put`. Meant for internal
4
- // distribution ahead of a real npm publish.
5
- //
6
- // Usage:
7
- // pnpm -F cli publish:tarball
8
- // pnpm -F cli publish:tarball -- --bucket fimo-cli-test --remote
9
- // pnpm -F cli publish:tarball -- --staging
10
- // pnpm -F cli publish:tarball -- --api-url https://api.staging.fimo.team --web-url https://staging.fimo.team
11
- //
12
- // Env vars:
13
- // FIMO_CLI_R2_BUCKET override the target R2 bucket (default: fimo-cli-test)
14
- // FIMO_CLI_API_URL bake default apiUrl into the tarball (same as --api-url)
15
- // FIMO_CLI_WEB_URL bake default webUrl into the tarball (same as --web-url)
16
- // CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID - standard wrangler auth
17
- import { spawnSync } from 'node:child_process';
18
- import { randomBytes } from 'node:crypto';
19
- import { chmodSync, existsSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
20
- import { dirname, resolve } from 'node:path';
21
- import { fileURLToPath } from 'node:url';
22
-
23
- import { registerReleaseCleanup } from './lib/cleanup-release.mjs';
24
-
25
- const HERE = dirname(fileURLToPath(import.meta.url));
26
- const CLI_ROOT = resolve(HERE, '..');
27
-
28
- // Load apps/cli/.env if present, so CLOUDFLARE_* and FIMO_CLI_* vars don't have
29
- // to be exported in the shell. No-op when the file doesn't exist (CI, fresh
30
- // clones), so this stays safe for everyone.
31
- const ENV_FILE = resolve(CLI_ROOT, '.env');
32
- if (existsSync(ENV_FILE)) {
33
- process.loadEnvFile(ENV_FILE);
34
- }
35
-
36
- const STAGING_API_URL = 'https://api.staging.fimo.team';
37
- const STAGING_WEB_URL = 'https://staging.fimo.team';
38
-
39
- function parseArgs(argv) {
40
- const args = {
41
- bucket: process.env.FIMO_CLI_R2_BUCKET || 'fimo-cli-test',
42
- remote: true,
43
- apiUrl: process.env.FIMO_CLI_API_URL?.trim() || null,
44
- webUrl: process.env.FIMO_CLI_WEB_URL?.trim() || null,
45
- keepReleaseJson: false,
46
- };
47
- for (let i = 0; i < argv.length; i++) {
48
- const arg = argv[i];
49
- if (arg === '--bucket') {
50
- args.bucket = argv[++i];
51
- } else if (arg === '--local') {
52
- args.remote = false;
53
- } else if (arg === '--remote') {
54
- args.remote = true;
55
- } else if (arg === '--api-url') {
56
- args.apiUrl = argv[++i];
57
- } else if (arg === '--web-url') {
58
- args.webUrl = argv[++i];
59
- } else if (arg === '--staging') {
60
- args.apiUrl = args.apiUrl || STAGING_API_URL;
61
- args.webUrl = args.webUrl || STAGING_WEB_URL;
62
- } else if (arg === '--id') {
63
- args.id = argv[++i];
64
- } else if (arg === '--keep-release-json') {
65
- // Escape hatch for debugging the publish flow itself. Normally
66
- // we want release.json gone from the workspace so local fimo
67
- // invocations (and `fimovm link --path apps/cli`) fall back to
68
- // localhost rather than the URL we just baked into the tarball.
69
- args.keepReleaseJson = true;
70
- }
71
- }
72
- return args;
73
- }
74
-
75
- function run(command, args, opts = {}) {
76
- console.log(`[cli/publish] $ ${command} ${args.join(' ')}`);
77
- const result = spawnSync(command, args, { stdio: 'inherit', ...opts });
78
- if (result.status !== 0) {
79
- console.error(`[cli/publish] command failed (exit ${result.status}): ${command} ${args.join(' ')}`);
80
- process.exit(result.status ?? 1);
81
- }
82
- return result;
83
- }
84
-
85
- const { bucket, remote, apiUrl, webUrl, id: customId, keepReleaseJson } = parseArgs(process.argv.slice(2));
86
-
87
- const pkg = JSON.parse(readFileSync(resolve(CLI_ROOT, 'package.json'), 'utf8'));
88
-
89
- // Default: generate a random release id per publish so the URL changes each
90
- // time (sidesteps pnpm/CDN cache).
91
- //
92
- // `--id <name>` gives you a *channel* (e.g. `marc-dev`) but we ALWAYS append
93
- // a short random suffix to the actual upload id (`marc-dev-abc123`). Without
94
- // this, package managers (pnpm content store, npm tarball cache, Blaxel
95
- // sandbox cache) key by URL and never revalidate against the origin —
96
- // `Cache-Control: no-cache` only helps CF edge + HTTP intermediaries, not
97
- // the local package store. SHA-suffixed URLs sidestep every cache layer.
98
- //
99
- // The workspace release.json (written below) captures the SHA-suffixed id,
100
- // so `fimo create` pins new scaffolds to the fresh URL automatically.
101
- const channelSuffix = randomBytes(3).toString('hex');
102
- const releaseId = customId ? `${customId}-${channelSuffix}` : randomBytes(6).toString('hex');
103
- const releaseFile = resolve(CLI_ROOT, 'release.json');
104
- // `source` marks where the binary came from. R2/HTTP-hosted tarballs are
105
- // "url"; npm publishes will write "source: 'npm'" from their own flow
106
- // (P0-7 CI workflow). Dev / source runs have no `release.json` and resolve
107
- // to defaults at runtime.
108
- const releasePayload = { id: releaseId, source: 'url' };
109
- if (apiUrl) {
110
- releasePayload.apiUrl = apiUrl;
111
- }
112
- if (webUrl) {
113
- releasePayload.webUrl = webUrl;
114
- }
115
- writeFileSync(releaseFile, JSON.stringify(releasePayload, null, 2) + '\n');
116
- console.log(`[cli/publish] release id: ${releaseId}`);
117
- if (apiUrl) {
118
- console.log(`[cli/publish] baked apiUrl: ${apiUrl}`);
119
- }
120
- if (webUrl) {
121
- console.log(`[cli/publish] baked webUrl: ${webUrl}`);
122
- }
123
-
124
- // release.json is a publish artefact: written here, captured into the
125
- // tarball by `pnpm pack`, then removed from the workspace by default.
126
- // Without cleanup, the file lingers and every subsequent `fimo`
127
- // invocation from the workspace (including via `fimovm link
128
- // --path apps/cli`) reads the URL we just baked for someone else's
129
- // tarball.
130
- //
131
- // EXCEPTION: when `--id <name>` is set, we KEEP the file. Dev channels
132
- // want the workspace fimo to pin scaffolds to the freshly-published
133
- // SHA-suffixed URL — that's the whole point of the channel. Each
134
- // publish overwrites release.json with the new SHA-suffixed id, so
135
- // you don't have to manually edit it between iterations.
136
- //
137
- // `--keep-release-json` forces keep regardless (debugging escape hatch).
138
- // The cleanup runs on normal completion (explicit call at the bottom
139
- // of the script), on any `process.exit(...)` from a failing `run()`
140
- // step, and on SIGINT / SIGTERM. Behaviours covered by unit tests in
141
- // `lib/cleanup-release.test.ts`.
142
- const shouldKeep = keepReleaseJson || !!customId;
143
- const cleanupReleaseJson = registerReleaseCleanup(releaseFile, { skip: shouldKeep });
144
-
145
- const tarballName = `${pkg.name}-${pkg.version}.tgz`;
146
- const tarballPath = resolve(CLI_ROOT, tarballName);
147
-
148
- // 1. Clean previous outputs so `tsc -b` can't skip emit on stale tsbuildinfo
149
- // when `dist/` has been removed out from under it.
150
- run('pnpm', ['clean'], { cwd: CLI_ROOT });
151
-
152
- // 2. Build (prebuild copies the template).
153
- run('pnpm', ['build'], { cwd: CLI_ROOT });
154
-
155
- // 3. Bundle the CLI bin into a single self-contained `dist/cli/index.js`. The
156
- // subpath exports under `dist/runtime/` and `dist/build/` stay multi-file
157
- // so user apps can keep importing `fimo/ui`, `fimo/vite`, etc.
158
- run('pnpm', ['build:bundle'], { cwd: CLI_ROOT });
159
-
160
- // 4. Sanity check: the bundled bin must exist before we pack and upload.
161
- const binPath = resolve(CLI_ROOT, 'dist/cli/index.js');
162
- if (!existsSync(binPath)) {
163
- console.error(`[cli/publish] bundle did not produce ${binPath}; aborting`);
164
- process.exit(1);
165
- }
166
-
167
- // Mark the bin entry executable BEFORE packing. `tsc` emits 644; npm preserves
168
- // file modes in the tarball, and pnpm doesn't always chmod bin entries to 755
169
- // when installing from a tarball URL (only registry installs and named npm
170
- // packages get this treatment reliably). Without this step, `fimo` fails with
171
- // `zsh: permission denied` after `npm i -g <tarball-url>`.
172
- chmodSync(binPath, 0o755);
173
-
174
- // 4. Pack into a tarball, forced to the CLI package root. Remove any
175
- // previous `.tgz` first so `pnpm pack` never bails out on "file exists".
176
- rmSync(tarballPath, { force: true });
177
- run('pnpm', ['pack', '--pack-destination', CLI_ROOT], { cwd: CLI_ROOT });
178
- if (!existsSync(tarballPath)) {
179
- console.error(`[cli/publish] expected tarball not found at ${tarballPath}`);
180
- process.exit(1);
181
- }
182
-
183
- const releaseKey = `cli/${pkg.name}-${releaseId}.tgz`;
184
- const latestKey = `cli/${pkg.name}-latest.tgz`;
185
-
186
- console.log(`[cli/publish] tarball: ${tarballPath} (${(statSync(tarballPath).size / 1024).toFixed(1)} KB)`);
187
- console.log(`[cli/publish] uploading to r2://${bucket}/${releaseKey}`);
188
-
189
- // 5. Upload via wrangler. Always upload the release-keyed object. Skip the
190
- // `latest` alias when a custom `--id` is in play - dev channels shouldn't
191
- // overwrite the public install URL that real users pull from.
192
- //
193
- // Both the release-keyed URL and the `latest` alias are tagged with
194
- // `no-cache` so republishing propagates immediately. For random-hex
195
- // release ids (no `--id`), nothing else ever lives at that key, so the
196
- // only practical effect is freshness on the first install. For stable
197
- // `--id <name>` dev channels (e.g. `marc-dev`), no-cache is essential:
198
- // the URL is reused across publishes, and without it Cloudflare's edge
199
- // cache + pnpm's URL-keyed store will hand out stale tarballs after
200
- // a republish (Node sees fimo's `virtual:` imports because the old
201
- // code didn't bundle them into SSR, etc).
202
- const wranglerArgs = ['exec', 'wrangler', 'r2', 'object', 'put'];
203
- if (remote) {
204
- wranglerArgs.push('--remote');
205
- }
206
- run(
207
- 'pnpm',
208
- [...wranglerArgs, `${bucket}/${releaseKey}`, '--file', tarballPath, '--cache-control', 'no-cache, max-age=0'],
209
- { cwd: CLI_ROOT }
210
- );
211
- if (!customId) {
212
- run(
213
- 'pnpm',
214
- [...wranglerArgs, `${bucket}/${latestKey}`, '--file', tarballPath, '--cache-control', 'no-cache, max-age=0'],
215
- { cwd: CLI_ROOT }
216
- );
217
- }
218
-
219
- const PUBLIC_BASE_URL = 'https://pub-41cdea46386f4b238d8c528c4327dfc1.r2.dev';
220
-
221
- console.log('[cli/publish] done.');
222
- console.log(` release: ${PUBLIC_BASE_URL}/${releaseKey}`);
223
- if (!customId) {
224
- console.log(` latest: ${PUBLIC_BASE_URL}/${latestKey}`);
225
- } else {
226
- console.log(` latest: (skipped - custom --id ${customId} set, leaving fimo-latest.tgz untouched)`);
227
- }
228
- console.log('');
229
- console.log('Install on a test machine:');
230
- console.log(` npm install -g ${PUBLIC_BASE_URL}/${latestKey}`);
231
-
232
- // Explicit cleanup at the end of the happy path. The process.on('exit')
233
- // handler also covers this, but an explicit call lets us print a
234
- // confirmation line so users can see release.json went away.
235
- cleanupReleaseJson();
236
- console.log('');
237
- if (shouldKeep) {
238
- console.log(
239
- `[cli/publish] kept workspace release.json (id: ${releaseId}) - new \`fimo create\` scaffolds will pin to this URL.`
240
- );
241
- } else {
242
- console.log(
243
- `[cli/publish] cleaned up workspace release.json - local \`fimo\` invocations will resolve to localhost:3000 again.`
244
- );
245
- }