fimo 0.7.0-experimental.1 → 0.7.0-experimental.2

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.
Files changed (136) hide show
  1. package/README.md +1 -1
  2. package/assets/skills/fimo-cli/references/domains.md +1 -1
  3. package/assets/skills/fimo-studio/references/custom-domains.md +1 -1
  4. package/dist/cli/bundle.json +3 -2
  5. package/dist/cli/index.js +1203 -110671
  6. package/dist/runtime/content/client/createContentClient.d.ts +5 -0
  7. package/dist/runtime/content/client/createContentClient.js +300 -0
  8. package/dist/runtime/content/client/createSingletonClient.d.ts +5 -0
  9. package/dist/runtime/content/client/createSingletonClient.js +108 -0
  10. package/dist/runtime/content/client/internal.d.ts +44 -0
  11. package/dist/runtime/content/client/internal.js +236 -0
  12. package/dist/runtime/content/client/types.d.ts +136 -0
  13. package/dist/runtime/content/client/useQueryLocale.d.ts +2 -0
  14. package/dist/runtime/content/client/useQueryLocale.js +6 -0
  15. package/dist/runtime/iframe/runtime/overlay-hmr.js +3 -0
  16. package/dist/runtime/index.d.ts +3 -0
  17. package/dist/runtime/index.js +2 -0
  18. package/package.json +31 -13
  19. package/templates/react-router/package.json +1 -1
  20. package/templates/react-router/src/pages/Index.tsx +1 -1
  21. package/dist/build/vite/index.d.ts.map +0 -1
  22. package/dist/build/vite/plugins/content-sync.d.ts.map +0 -1
  23. package/dist/build/vite/plugins/content-sync.test.d.ts +0 -2
  24. package/dist/build/vite/plugins/content-sync.test.d.ts.map +0 -1
  25. package/dist/build/vite/plugins/content-sync.test.js +0 -64
  26. package/dist/build/vite/plugins/data-id.d.ts.map +0 -1
  27. package/dist/build/vite/plugins/fimo-config.d.ts.map +0 -1
  28. package/dist/build/vite/plugins/fimo-config.test.d.ts +0 -2
  29. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +0 -1
  30. package/dist/build/vite/plugins/fimo-config.test.js +0 -46
  31. package/dist/build/vite/plugins/overlay.d.ts.map +0 -1
  32. package/dist/build/vite/plugins/robots.d.ts.map +0 -1
  33. package/dist/build/vite/plugins/seo.d.ts.map +0 -1
  34. package/dist/build/vite/plugins/server-config.d.ts.map +0 -1
  35. package/dist/build/vite/plugins/sitemap.d.ts.map +0 -1
  36. package/dist/build/vite/plugins/translations.d.ts.map +0 -1
  37. package/dist/build/vite/plugins/translations.test.d.ts +0 -2
  38. package/dist/build/vite/plugins/translations.test.d.ts.map +0 -1
  39. package/dist/build/vite/plugins/translations.test.js +0 -266
  40. package/dist/runtime/app/FimoProviders.d.ts.map +0 -1
  41. package/dist/runtime/app/FimoScripts.d.ts.map +0 -1
  42. package/dist/runtime/app/index.d.ts.map +0 -1
  43. package/dist/runtime/app/prefetch.d.ts.map +0 -1
  44. package/dist/runtime/content/DataSourcesProvider.d.ts.map +0 -1
  45. package/dist/runtime/content/RefreshContentProvider.d.ts.map +0 -1
  46. package/dist/runtime/content/RefreshContentProvider.test.d.ts +0 -2
  47. package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +0 -1
  48. package/dist/runtime/content/RefreshContentProvider.test.js +0 -33
  49. package/dist/runtime/content/parseCollectionResponse.d.ts.map +0 -1
  50. package/dist/runtime/content/parseCollectionResponse.test.d.ts +0 -2
  51. package/dist/runtime/content/parseCollectionResponse.test.d.ts.map +0 -1
  52. package/dist/runtime/content/parseCollectionResponse.test.js +0 -20
  53. package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +0 -1
  54. package/dist/runtime/core/AppErrorBoundary.d.ts.map +0 -1
  55. package/dist/runtime/core/ViteLifecycles.d.ts.map +0 -1
  56. package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +0 -1
  57. package/dist/runtime/i18n/locale.d.ts.map +0 -1
  58. package/dist/runtime/i18n/locale.test.d.ts +0 -2
  59. package/dist/runtime/i18n/locale.test.d.ts.map +0 -1
  60. package/dist/runtime/i18n/locale.test.js +0 -58
  61. package/dist/runtime/iframe/ConsoleErrorProvider.d.ts.map +0 -1
  62. package/dist/runtime/iframe/ScreenshotProvider.d.ts.map +0 -1
  63. package/dist/runtime/iframe/runtime/overlay-hmr.d.ts.map +0 -1
  64. package/dist/runtime/iframe/runtime/overlay-runtime.d.ts.map +0 -1
  65. package/dist/runtime/index.d.ts.map +0 -1
  66. package/dist/runtime/paths/get-fimo-paths.d.ts.map +0 -1
  67. package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
  68. package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
  69. package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
  70. package/dist/runtime/paths/index.d.ts.map +0 -1
  71. package/dist/runtime/paths/types.d.ts.map +0 -1
  72. package/dist/runtime/primitives/components/Boolean.d.ts.map +0 -1
  73. package/dist/runtime/primitives/components/Date.d.ts.map +0 -1
  74. package/dist/runtime/primitives/components/Image.d.ts.map +0 -1
  75. package/dist/runtime/primitives/components/RichText.d.ts.map +0 -1
  76. package/dist/runtime/primitives/components/StaticImage.d.ts.map +0 -1
  77. package/dist/runtime/primitives/components/Text.d.ts.map +0 -1
  78. package/dist/runtime/primitives/components/Video.d.ts.map +0 -1
  79. package/dist/runtime/primitives/components/index.d.ts.map +0 -1
  80. package/dist/runtime/primitives/index.d.ts.map +0 -1
  81. package/dist/runtime/primitives/lib/image-optimization.d.ts.map +0 -1
  82. package/dist/runtime/primitives/lib/index.d.ts.map +0 -1
  83. package/dist/runtime/primitives/lib/primitives.d.ts.map +0 -1
  84. package/dist/runtime/primitives/lib/query.d.ts.map +0 -1
  85. package/dist/runtime/primitives/lib/template.d.ts.map +0 -1
  86. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +0 -1
  87. package/dist/runtime/primitives/public-surface.test.d.ts +0 -2
  88. package/dist/runtime/primitives/public-surface.test.d.ts.map +0 -1
  89. package/dist/runtime/primitives/public-surface.test.js +0 -49
  90. package/dist/runtime/primitives/translations.d.ts.map +0 -1
  91. package/dist/runtime/react-router/index.d.ts.map +0 -1
  92. package/dist/runtime/react-router/navigation-utils.d.ts.map +0 -1
  93. package/dist/runtime/react-router/navigation.d.ts.map +0 -1
  94. package/dist/runtime/react-router/navigation.test.d.ts +0 -2
  95. package/dist/runtime/react-router/navigation.test.d.ts.map +0 -1
  96. package/dist/runtime/react-router/navigation.test.js +0 -35
  97. package/dist/runtime/react-router/routes.d.ts.map +0 -1
  98. package/dist/runtime/react-router/routes.test.d.ts +0 -2
  99. package/dist/runtime/react-router/routes.test.d.ts.map +0 -1
  100. package/dist/runtime/react-router/routes.test.js +0 -67
  101. package/dist/runtime/routing/AppResetScrollOnNavigate.d.ts.map +0 -1
  102. package/dist/runtime/routing/AppRoutesProvider.d.ts.map +0 -1
  103. package/dist/runtime/seo/buildSeoForPath.d.ts.map +0 -1
  104. package/dist/runtime/seo/htmlProps.d.ts.map +0 -1
  105. package/dist/runtime/seo/index.d.ts.map +0 -1
  106. package/dist/runtime/seo/resolver.d.ts.map +0 -1
  107. package/dist/runtime/shared/fimo-config.server.d.ts.map +0 -1
  108. package/dist/runtime/shared/fimo-config.types.d.ts.map +0 -1
  109. package/dist/runtime/t/OptionalTracking.d.ts.map +0 -1
  110. package/dist/runtime/t/OptionalTracking.test.d.ts +0 -2
  111. package/dist/runtime/t/OptionalTracking.test.d.ts.map +0 -1
  112. package/dist/runtime/t/OptionalTracking.test.js +0 -11
  113. package/dist/runtime/t/Pageview.d.ts.map +0 -1
  114. package/dist/runtime/templates.d.ts.map +0 -1
  115. package/dist/scripts/generated-types.test.js +0 -212
  116. package/dist/scripts/lint-translation-keys.test.d.ts +0 -1
  117. package/dist/scripts/lint-translation-keys.test.js +0 -16
  118. package/dist/scripts/sync-schemas.test.d.ts +0 -1
  119. package/dist/scripts/sync-schemas.test.js +0 -144
  120. package/dist/scripts/validate-route-metadata.test.d.ts +0 -1
  121. package/dist/scripts/validate-route-metadata.test.js +0 -116
  122. package/dist/scripts/validate-schemas.test.d.ts +0 -1
  123. package/dist/scripts/validate-schemas.test.js +0 -263
  124. package/scripts/add-ext.mjs +0 -47
  125. package/scripts/bump-version.mjs +0 -58
  126. package/scripts/bundle-cli.mjs +0 -123
  127. package/scripts/install-cli.mjs +0 -36
  128. package/scripts/lib/channels.mjs +0 -70
  129. package/scripts/lib/dev-release-state.mjs +0 -44
  130. package/scripts/lib/dev-release-state.test.ts +0 -36
  131. package/scripts/lib/postinstall-onboarding.test.ts +0 -82
  132. package/scripts/lib/release-core.mjs +0 -225
  133. package/scripts/lib/release-core.test.ts +0 -147
  134. package/scripts/migration-qa-gate.test.ts +0 -155
  135. package/scripts/publish-npm.mjs +0 -180
  136. /package/dist/{scripts/generated-types.test.d.ts → runtime/content/client/types.js} +0 -0
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env node
2
- // Bundles the Fimo CLI binary into a single self-contained ESM file at
3
- // `dist/cli/index.js`, overwriting the per-file tsc output.
4
- //
5
- // Scope:
6
- // - Only the `bin` entry (`src/cli/index.ts`) is bundled. The subpath
7
- // exports (`fimo/ui`, `fimo/seo`, `fimo/app`, `fimo/vite`,
8
- // `fimo/config/react-router`) keep their multi-file tsc output under
9
- // `dist/runtime/` and `dist/build/`.
10
- // - Native deps are kept external (`oxc-parser` has a Rust addon) so they
11
- // resolve at runtime from the published `dependencies`.
12
- // - All other deps reachable from `src/cli/index.ts` are inlined.
13
- //
14
- // This script assumes `tsc -b` has already produced the rest of `dist/`. Run
15
- // `pnpm -F fimo build` first, or use the `prepublishOnly` chain.
16
- //
17
- // Usage:
18
- // node scripts/bundle-cli.mjs
19
-
20
- import { readFileSync, rmSync, writeFileSync, statSync } from 'node:fs';
21
- import { dirname, resolve } from 'node:path';
22
- import { fileURLToPath } from 'node:url';
23
-
24
- import { build } from 'esbuild';
25
-
26
- const HERE = dirname(fileURLToPath(import.meta.url));
27
- const CLI_ROOT = resolve(HERE, '..');
28
- const ENTRY = resolve(CLI_ROOT, 'src/cli/index.ts');
29
- const OUTFILE = resolve(CLI_ROOT, 'dist/cli/index.js');
30
-
31
- // Native or otherwise non-bundlable runtime deps. Keep them in the published
32
- // `dependencies` so `npm install -g fimo` pulls them in transitively.
33
- const EXTERNAL = [
34
- 'oxc-parser', // Rust-backed native parser
35
- 'fsevents', // macOS-only native .node addon; chokidar require()s it behind a try/catch
36
- ];
37
-
38
- const pkg = JSON.parse(readFileSync(resolve(CLI_ROOT, 'package.json'), 'utf8'));
39
-
40
- console.log(`[cli/bundle] entry: ${ENTRY}`);
41
- console.log(`[cli/bundle] outfile: ${OUTFILE}`);
42
- console.log(`[cli/bundle] external: ${EXTERNAL.join(', ')}`);
43
-
44
- const result = await build({
45
- entryPoints: [ENTRY],
46
- outfile: OUTFILE,
47
- bundle: true,
48
- platform: 'node',
49
- target: 'node20',
50
- format: 'esm',
51
- // Esbuild promotes the source's `#!/usr/bin/env node` shebang to line 1 of
52
- // the output, then prepends this banner. Some bundled deps (notably
53
- // `commander`) are CJS and call `require('node:events')` etc. internally;
54
- // esbuild wraps those with its own `__require` helper which doesn't know
55
- // about Node built-ins. Injecting `createRequire` here gives the bundled
56
- // CJS a working `require()` against the real Node resolver.
57
- banner: {
58
- js: "import { createRequire as __fimoCreateRequire } from 'node:module';\nconst require = __fimoCreateRequire(import.meta.url);",
59
- },
60
- // Mark native runtime deps as external; everything else inlines.
61
- external: EXTERNAL,
62
- // Inline node_modules deps reachable from the entry. Built-ins
63
- // (node:fs, etc.) are externalized automatically by `platform=node`.
64
- packages: 'bundle',
65
- // Resolve .ts / .tsx during the bundling pass (we're reading source, not
66
- // the tsc output).
67
- resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'],
68
- // ESM emit requires ESM-style imports for built-ins.
69
- mainFields: ['module', 'main'],
70
- conditions: ['node', 'import', 'default'],
71
- // Keep file names stable in the bundled output for clearer stack traces.
72
- keepNames: true,
73
- // Source map is overkill in the published tarball; skip for v1.
74
- sourcemap: false,
75
- // Minification trades readability for ~30% size; skip - the bundle stays
76
- // human-grep-able which helps debugging field issues. Revisit if size
77
- // becomes a problem.
78
- minify: false,
79
- // Surface every warning so we don't silently produce a broken bundle.
80
- logLevel: 'info',
81
- metafile: true,
82
- });
83
-
84
- // Stamp the version + bundled marker into a sidecar so `release.json` /
85
- // future tooling can detect bundled artifacts without re-parsing the bundle.
86
- const meta = {
87
- bundled: true,
88
- bundler: 'esbuild',
89
- bundledAt: new Date().toISOString(),
90
- cliVersion: pkg.version,
91
- external: EXTERNAL,
92
- };
93
- writeFileSync(resolve(CLI_ROOT, 'dist/cli/bundle.json'), JSON.stringify(meta, null, 2) + '\n');
94
-
95
- // Clean up the per-file tsc artifacts under `dist/cli/` that the bundle has
96
- // now subsumed - anything except `index.js` and the sidecar. Subpath exports
97
- // live under `dist/runtime/` and `dist/build/`, which stay untouched.
98
- const inputs = Object.keys(result.metafile.inputs);
99
- console.log(`[cli/bundle] inlined ${inputs.length} modules`);
100
-
101
- const distCli = resolve(CLI_ROOT, 'dist/cli');
102
- const { readdirSync } = await import('node:fs');
103
- function rmExceptIndex(dir) {
104
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
105
- const full = resolve(dir, entry.name);
106
- if (dir === distCli && (entry.name === 'index.js' || entry.name === 'bundle.json')) {
107
- continue;
108
- }
109
- rmSync(full, { recursive: true, force: true });
110
- }
111
- }
112
- rmExceptIndex(distCli);
113
-
114
- // Make the bin executable. npm chmods bin files on `npm install -g`
115
- // regardless, but a properly-permissioned tarball is the right hygiene
116
- // AND it makes the local-link flow (where the user's checkout is
117
- // symlinked into ~/.fimovm/linked/<alias>) work without a follow-up
118
- // chmod step.
119
- import { chmodSync } from 'node:fs';
120
- chmodSync(OUTFILE, 0o755);
121
-
122
- const bytes = statSync(OUTFILE).size;
123
- console.log(`[cli/bundle] done. ${(bytes / 1024).toFixed(1)} KB written to ${OUTFILE}`);
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- // Creates a `fimo` symlink in ~/.local/bin/ pointing at the built CLI entry
3
- // so `fimo <command>` works from any directory on the machine.
4
- import { chmodSync, existsSync, mkdirSync, symlinkSync, unlinkSync } from 'node:fs';
5
- import { homedir } from 'node:os';
6
- import { dirname, resolve } from 'node:path';
7
- import { fileURLToPath } from 'node:url';
8
-
9
- const HERE = dirname(fileURLToPath(import.meta.url));
10
- const CLI_ROOT = resolve(HERE, '..');
11
- const ENTRY = resolve(CLI_ROOT, 'dist', 'cli', 'index.js');
12
-
13
- if (!existsSync(ENTRY)) {
14
- console.error(`[cli/install] Built entry not found at ${ENTRY}. Run \`pnpm -F cli build\` first.`);
15
- process.exit(1);
16
- }
17
-
18
- const BIN_DIR = resolve(homedir(), '.local', 'bin');
19
- const LINK = resolve(BIN_DIR, 'fimo');
20
-
21
- mkdirSync(BIN_DIR, { recursive: true });
22
- chmodSync(ENTRY, 0o755);
23
- if (existsSync(LINK)) {
24
- unlinkSync(LINK);
25
- }
26
- symlinkSync(ENTRY, LINK);
27
-
28
- console.log(`[cli/install] Linked ${LINK} → ${ENTRY}`);
29
-
30
- const pathDirs = (process.env.PATH ?? '').split(':');
31
- if (!pathDirs.includes(BIN_DIR)) {
32
- console.log('');
33
- console.log(`⚠ ${BIN_DIR} is not on your PATH.`);
34
- console.log(' Add this to your shell rc (~/.zshrc, ~/.bashrc, ...):');
35
- console.log(` export PATH="$HOME/.local/bin:$PATH"`);
36
- }
@@ -1,70 +0,0 @@
1
- // Single source of truth for fimo release channels.
2
- //
3
- // channel → { source, apiUrl, webUrl, distTag, prerelease }
4
- //
5
- // Read by the npm publish path (scripts/publish-npm.mjs) and the CI
6
- // release workflow. Defined ONCE so the npm and (legacy) R2 paths can
7
- // never drift on "what does staging mean" — the bug class behind a
8
- // published build accidentally pointing at the wrong API.
9
- //
10
- // Hard rule (D5): a published package bakes ONLY staging or prod. Never a
11
- // tunnel or localhost — that would leak a private URL to the public
12
- // registry. Tunnels/localhost are runtime FIMO_API_URL overrides, never
13
- // baked. `ALLOWED_API_URLS` enforces this at publish time.
14
-
15
- // Staging lives on the .team domain; production is fimo.ai.
16
- export const STAGING_API_URL = 'https://api.staging.fimo.team';
17
- export const STAGING_WEB_URL = 'https://staging.fimo.team';
18
- export const PROD_API_URL = 'https://api.fimo.ai';
19
- export const PROD_WEB_URL = 'https://fimo.ai';
20
-
21
- /**
22
- * @typedef {Object} Channel
23
- * @property {'npm'} source - marks the published build's origin
24
- * @property {string} apiUrl - cloud API base baked into release.json
25
- * @property {string} webUrl - web URL baked into release.json
26
- * @property {string} distTag - npm dist-tag to publish under
27
- * @property {boolean} prerelease - true → synthesize a disposable X.Y.Z-<tag>.N version
28
- */
29
-
30
- /** @type {Record<string, Channel>} */
31
- export const CHANNELS = {
32
- experimental: {
33
- source: 'npm',
34
- apiUrl: STAGING_API_URL,
35
- webUrl: STAGING_WEB_URL,
36
- distTag: 'experimental',
37
- prerelease: true,
38
- },
39
- staging: {
40
- source: 'npm',
41
- apiUrl: STAGING_API_URL,
42
- webUrl: STAGING_WEB_URL,
43
- distTag: 'staging',
44
- prerelease: true,
45
- },
46
- latest: {
47
- source: 'npm',
48
- apiUrl: PROD_API_URL,
49
- webUrl: PROD_WEB_URL,
50
- distTag: 'latest',
51
- prerelease: false,
52
- },
53
- };
54
-
55
- // A published package may bake ONLY these api URLs. Everything else
56
- // (localhost, tunnels) is refused at publish time.
57
- export const ALLOWED_API_URLS = new Set([STAGING_API_URL, PROD_API_URL]);
58
-
59
- /**
60
- * Resolve a channel by name, throwing on an unknown name.
61
- * @param {string} name
62
- * @returns {Channel}
63
- */
64
- export function resolveChannel(name) {
65
- const channel = CHANNELS[name];
66
- if (!channel) {
67
- throw new Error(`Unknown channel "${name}". Valid channels: ${Object.keys(CHANNELS).join(', ')}.`);
68
- }
69
- return channel;
70
- }
@@ -1,44 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
3
- import { relative, resolve } from 'node:path';
4
-
5
- /**
6
- * Record the latest locally-published experimental CLI version for this
7
- * checkout. `fimo deploy` uses this to swap a project-local `file:` dependency
8
- * to an immutable npm version before pushing to a remote sandbox.
9
- *
10
- * @param {string} cliRoot - apps/cli root
11
- * @param {{ version: string, channel: string }} release
12
- * @returns {string} the file written
13
- */
14
- export function writeDevReleaseState(cliRoot, release) {
15
- const repoRoot = resolve(cliRoot, '..', '..');
16
- const dir = resolve(repoRoot, '.fimo');
17
- mkdirSync(dir, { recursive: true });
18
-
19
- const payload = {
20
- version: release.version,
21
- channel: release.channel,
22
- packageRoot: relative(repoRoot, cliRoot),
23
- gitSha: readGitSha(repoRoot),
24
- publishedAt: new Date().toISOString(),
25
- };
26
-
27
- const file = resolve(dir, 'dev.json');
28
- writeFileSync(file, JSON.stringify(payload, null, 2) + '\n');
29
- return file;
30
- }
31
-
32
- function readGitSha(repoRoot) {
33
- const result = spawnSync('git', ['rev-parse', 'HEAD'], {
34
- cwd: repoRoot,
35
- encoding: 'utf8',
36
- });
37
- if (result.status === 0) {
38
- const sha = result.stdout.trim();
39
- if (sha.length > 0) {
40
- return sha;
41
- }
42
- }
43
- return null;
44
- }
@@ -1,36 +0,0 @@
1
- import fs from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
-
5
- import { afterEach, describe, expect, it } from 'vitest';
6
-
7
- import { writeDevReleaseState } from './dev-release-state.mjs';
8
-
9
- const tmpRoots: string[] = [];
10
-
11
- afterEach(() => {
12
- for (const root of tmpRoots.splice(0)) {
13
- fs.rmSync(root, { recursive: true, force: true });
14
- }
15
- });
16
-
17
- describe('writeDevReleaseState', () => {
18
- it('writes repo-root .fimo/dev.json for the checkout', () => {
19
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'fimo-dev-state-'));
20
- tmpRoots.push(root);
21
- const cliRoot = path.join(root, 'apps', 'cli');
22
- fs.mkdirSync(cliRoot, { recursive: true });
23
-
24
- const file = writeDevReleaseState(cliRoot, {
25
- version: '0.2.3-experimental.42',
26
- channel: 'experimental',
27
- });
28
-
29
- expect(file).toBe(path.join(root, '.fimo', 'dev.json'));
30
- expect(JSON.parse(fs.readFileSync(file, 'utf8'))).toMatchObject({
31
- version: '0.2.3-experimental.42',
32
- channel: 'experimental',
33
- packageRoot: 'apps/cli',
34
- });
35
- });
36
- });
@@ -1,82 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { isGlobalInstall, parseYesNo, shouldPromptForGlobalSkills } from './postinstall-onboarding.mjs';
4
-
5
- describe('isGlobalInstall', () => {
6
- it('detects npm global installs', () => {
7
- expect(isGlobalInstall({ npm_config_global: 'true' })).toBe(true);
8
- });
9
-
10
- it('detects package-manager location=global installs', () => {
11
- expect(isGlobalInstall({ npm_config_location: 'global' })).toBe(true);
12
- });
13
-
14
- it('ignores local installs', () => {
15
- expect(isGlobalInstall({})).toBe(false);
16
- });
17
- });
18
-
19
- describe('shouldPromptForGlobalSkills', () => {
20
- it('prompts only for interactive global installs', () => {
21
- expect(
22
- shouldPromptForGlobalSkills({
23
- env: { npm_config_global: 'true' },
24
- stdinIsTTY: true,
25
- stdoutIsTTY: true,
26
- })
27
- ).toEqual({ ok: true });
28
- });
29
-
30
- it('skips CI and non-interactive installs', () => {
31
- expect(
32
- shouldPromptForGlobalSkills({
33
- env: { npm_config_global: 'true', CI: '1' },
34
- stdinIsTTY: true,
35
- stdoutIsTTY: true,
36
- })
37
- ).toMatchObject({ ok: false, reason: 'ci' });
38
- expect(
39
- shouldPromptForGlobalSkills({
40
- env: { npm_config_global: 'true' },
41
- stdinIsTTY: false,
42
- stdoutIsTTY: true,
43
- })
44
- ).toMatchObject({ ok: false, reason: 'non-interactive' });
45
- });
46
-
47
- it('honors the explicit skip env var', () => {
48
- expect(
49
- shouldPromptForGlobalSkills({
50
- env: { npm_config_global: 'true', FIMO_SKIP_POSTINSTALL_PROMPT: '1' },
51
- stdinIsTTY: true,
52
- stdoutIsTTY: true,
53
- })
54
- ).toMatchObject({ ok: false, reason: 'disabled' });
55
- });
56
-
57
- it('skips package-manager assume-yes installs', () => {
58
- expect(
59
- shouldPromptForGlobalSkills({
60
- env: { npm_config_global: 'true', npm_config_yes: 'true' },
61
- stdinIsTTY: true,
62
- stdoutIsTTY: true,
63
- })
64
- ).toMatchObject({ ok: false, reason: 'assume-yes-install' });
65
- expect(
66
- shouldPromptForGlobalSkills({
67
- env: { npm_config_global: 'true', npm_config_force: 'true' },
68
- stdinIsTTY: true,
69
- stdoutIsTTY: true,
70
- })
71
- ).toMatchObject({ ok: false, reason: 'assume-yes-install' });
72
- });
73
- });
74
-
75
- describe('parseYesNo', () => {
76
- it('parses yes, no, and empty defaults', () => {
77
- expect(parseYesNo('y')).toBe(true);
78
- expect(parseYesNo('no')).toBe(false);
79
- expect(parseYesNo('', true)).toBe(true);
80
- expect(parseYesNo('', false)).toBe(false);
81
- });
82
- });
@@ -1,225 +0,0 @@
1
- // Shared release-core: the channel-aware release.json bake.
2
- //
3
- // This is the single place that stamps which environment a published
4
- // build talks to. Both the npm publish path and any future CI job call
5
- // it, so the "which URL goes in which build" decision can't drift.
6
- //
7
- // `release.json` is a publish artifact: it is gitignored (apps/cli/.gitignore)
8
- // and only exists transiently while packing. The CLI reads it at runtime as
9
- // `FIMO_API_URL env || baked release.json || localhost` — so the bake only
10
- // governs a bare `npm i -g fimo` with no env set. The sandbox always
11
- // overrides it via the injected `.env`.
12
-
13
- import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
14
- import { join, resolve } from 'node:path';
15
-
16
- import { ALLOWED_API_URLS } from './channels.mjs';
17
-
18
- const TEMPLATE_PKG_REL = 'templates/react-router/package.json';
19
- const TEMPLATE_LOCK_REL = 'templates/react-router/pnpm-lock.yaml';
20
-
21
- /**
22
- * Write a `release.json` for a published (`source: 'npm'`) build using the
23
- * channel's baked URLs. Refuses any apiUrl not on the allowlist so a
24
- * tunnel/localhost can never reach the public registry (D5).
25
- *
26
- * @param {string} cliRoot - apps/cli root
27
- * @param {import('./channels.mjs').Channel} channel
28
- * @returns {string} the path written
29
- */
30
- export function writeReleaseJson(cliRoot, channel) {
31
- if (!ALLOWED_API_URLS.has(channel.apiUrl)) {
32
- throw new Error(
33
- `Refusing to bake non-allowlisted apiUrl "${channel.apiUrl}" into a published package. ` +
34
- `Published channels may bake only staging or prod (D5).`
35
- );
36
- }
37
- const payload = {
38
- source: channel.source,
39
- apiUrl: channel.apiUrl,
40
- webUrl: channel.webUrl,
41
- };
42
- const file = resolve(cliRoot, 'release.json');
43
- writeFileSync(file, JSON.stringify(payload, null, 2) + '\n');
44
- return file;
45
- }
46
-
47
- /**
48
- * Pin the bundled template's `fimo` dependency to an exact version at pack
49
- * time, so a project scaffolded from `fimo@X` depends on exactly `fimo@X`
50
- * (immutable, reproducible — no mutable URL, no caret drift). The committed
51
- * template keeps a dev-friendly value; this overwrites it only for the
52
- * tarball and the returned function restores it afterwards.
53
- *
54
- * @param {string} cliRoot
55
- * @param {string} version - exact version to pin (e.g. "0.3.0" or "0.3.0-experimental.4")
56
- * @returns {() => void} restore - rewrites the original file contents back
57
- */
58
- export function pinTemplateFimo(cliRoot, version) {
59
- const file = resolve(cliRoot, TEMPLATE_PKG_REL);
60
- if (!existsSync(file)) {
61
- return () => {};
62
- }
63
- const original = readFileSync(file, 'utf8');
64
- const pkg = JSON.parse(original);
65
- let changed = false;
66
- for (const field of ['dependencies', 'devDependencies']) {
67
- if (pkg[field] && typeof pkg[field].fimo === 'string' && pkg[field].fimo !== version) {
68
- pkg[field].fimo = version;
69
- changed = true;
70
- }
71
- }
72
- if (changed) {
73
- writeFileSync(file, JSON.stringify(pkg, null, 2) + '\n');
74
- }
75
- return () => writeFileSync(file, original);
76
- }
77
-
78
- /**
79
- * Remove the template's pnpm-lock.yaml from the tarball at pack time.
80
- *
81
- * We can't ship a correct lockfile: it would have to pin the exact version
82
- * we're about to publish, which isn't on the registry yet (chicken-and-egg).
83
- * A stale lockfile pinning a different spec than package.json breaks frozen
84
- * installs (the 2026-06-11 outage class). With an exact, immutable version in
85
- * package.json, `fimo create` + a fresh `pnpm install` resolves
86
- * deterministically — so shipping no lockfile is the correct choice.
87
- * `scaffold.ts` already deletes the lockfile whenever it rewrites the dep, so
88
- * this only matters for the exact-npm-match path, but it's always safe.
89
- *
90
- * @param {string} cliRoot
91
- * @returns {() => void} restore - rewrites the original lockfile back, if any
92
- */
93
- export function stripTemplateLockfile(cliRoot) {
94
- const file = resolve(cliRoot, TEMPLATE_LOCK_REL);
95
- if (!existsSync(file)) {
96
- return () => {};
97
- }
98
- const original = readFileSync(file, 'utf8');
99
- rmSync(file, { force: true });
100
- return () => writeFileSync(file, original);
101
- }
102
-
103
- /**
104
- * Parse `npm view <name> versions --json` output into a flat list of version
105
- * strings, tolerating every shape npm has shipped: an array of strings
106
- * (npm <= 11), a bare string when exactly one version is published
107
- * (npm <= 11), and the npm 12 wrapper — "npm view --json now always returns
108
- * an array", one entry per matched version, i.e. `[[...versions]]` for a bare
109
- * package name. Returns null when nothing version-shaped survives (error
110
- * envelopes, empty output), so callers fall back to the run-number/timestamp
111
- * counter instead of trusting garbage.
112
- *
113
- * @param {string} stdout - raw stdout of `npm view <name> versions --json`
114
- * @returns {string[] | null}
115
- */
116
- export function parsePublishedVersions(stdout) {
117
- let parsed;
118
- try {
119
- parsed = JSON.parse(stdout);
120
- } catch {
121
- return null;
122
- }
123
- const versions = (Array.isArray(parsed) ? parsed : [parsed]).flat().filter((version) => typeof version === 'string');
124
- return versions.length > 0 ? versions : null;
125
- }
126
-
127
- /**
128
- * Next per-version prerelease counter: one past the highest already-published
129
- * `<base>-<distTag>.<n>` on npm. Counting from what's actually published
130
- * (instead of resetting blindly to 1) keeps the counter collision-proof even
131
- * when the projected base flaps back to a value that already has prereleases —
132
- * npm versions are immutable, so a blind reset would make the publish fail.
133
- *
134
- * @param {readonly string[]} existingVersions - all published versions of the package
135
- * @param {string} base - X.Y.Z base (no prerelease suffix)
136
- * @param {string} distTag
137
- * @returns {number}
138
- */
139
- export function nextPrereleaseCounter(existingVersions, base, distTag) {
140
- const prefix = `${base}-${distTag}.`;
141
- let max = 0;
142
- for (const version of existingVersions) {
143
- if (!version.startsWith(prefix)) {
144
- continue;
145
- }
146
- const n = Number(version.slice(prefix.length));
147
- if (Number.isInteger(n) && n > max) {
148
- max = n;
149
- }
150
- }
151
- return max + 1;
152
- }
153
-
154
- /**
155
- * Compute the version to publish for a channel.
156
- * - Stable (`latest`) publishes the exact package.json version.
157
- * - Prerelease channels synthesize a disposable `X.Y.Z-<distTag>.<n>`.
158
- * `n` counts per base version (`0.3.5-staging.1`, `.2`, …) when the
159
- * published versions could be fetched from npm; otherwise it falls back
160
- * to the CI run number, then a timestamp.
161
- *
162
- * @param {string} baseVersion - package.json version
163
- * @param {import('./channels.mjs').Channel} channel
164
- * @param {{ runId?: string, existingVersions?: readonly string[] }} [opts]
165
- * @returns {string}
166
- */
167
- export function computePublishVersion(baseVersion, channel, opts = {}) {
168
- if (!channel.prerelease) {
169
- return baseVersion;
170
- }
171
- // Strip any existing prerelease suffix from the base so we don't stack them.
172
- const base = baseVersion.split('-')[0];
173
- const n = opts.existingVersions
174
- ? String(nextPrereleaseCounter(opts.existingVersions, base, channel.distTag))
175
- : (opts.runId ?? String(Date.now()));
176
- return `${base}-${channel.distTag}.${n}`;
177
- }
178
-
179
- const BUMP_RANK = { patch: 1, minor: 2, major: 3 };
180
-
181
- /**
182
- * Peek at pending changeset files and compute the version the next
183
- * `changeset version` run will produce for `fimo`. Prerelease channels use
184
- * this as their base so a staging build advertises the version prod will
185
- * actually ship (`0.3.4-staging.26` while 0.3.4 is pending) instead of the
186
- * already-released base. No pending changesets (or no .changeset dir) →
187
- * base unchanged.
188
- *
189
- * @param {string} baseVersion - package.json version
190
- * @param {string} changesetDir - absolute path to the repo's .changeset dir
191
- * @returns {string}
192
- */
193
- export function nextVersionFromChangesets(baseVersion, changesetDir) {
194
- let entries;
195
- try {
196
- entries = readdirSync(changesetDir);
197
- } catch {
198
- return baseVersion;
199
- }
200
-
201
- let maxBump = null;
202
- for (const name of entries) {
203
- if (!name.endsWith('.md') || name === 'README.md') {
204
- continue;
205
- }
206
- const text = readFileSync(join(changesetDir, name), 'utf8');
207
- const frontmatter = text.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
208
- const bump = frontmatter.match(/['"]?fimo['"]?\s*:\s*(patch|minor|major)/)?.[1];
209
- if (bump && (!maxBump || BUMP_RANK[bump] > BUMP_RANK[maxBump])) {
210
- maxBump = bump;
211
- }
212
- }
213
-
214
- if (!maxBump) {
215
- return baseVersion;
216
- }
217
- const [major, minor, patch] = baseVersion.split('-')[0].split('.').map(Number);
218
- if (maxBump === 'major') {
219
- return `${major + 1}.0.0`;
220
- }
221
- if (maxBump === 'minor') {
222
- return `${major}.${minor + 1}.0`;
223
- }
224
- return `${major}.${minor}.${patch + 1}`;
225
- }