fimo 0.6.0 → 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 (145) hide show
  1. package/README.md +1 -1
  2. package/assets/content-templates/hooks-template.eta +73 -27
  3. package/assets/content-templates/types-template.eta +4 -1
  4. package/assets/skills/fimo/references/content.md +46 -42
  5. package/assets/skills/fimo-cli/references/content.md +14 -14
  6. package/assets/skills/fimo-cli/references/domains.md +1 -1
  7. package/assets/skills/fimo-studio/references/custom-domains.md +1 -1
  8. package/dist/cli/bundle.json +3 -2
  9. package/dist/cli/index.js +1203 -110450
  10. package/dist/runtime/content/client/createContentClient.d.ts +5 -0
  11. package/dist/runtime/content/client/createContentClient.js +300 -0
  12. package/dist/runtime/content/client/createSingletonClient.d.ts +5 -0
  13. package/dist/runtime/content/client/createSingletonClient.js +108 -0
  14. package/dist/runtime/content/client/internal.d.ts +44 -0
  15. package/dist/runtime/content/client/internal.js +236 -0
  16. package/dist/runtime/content/client/types.d.ts +136 -0
  17. package/dist/runtime/content/client/useQueryLocale.d.ts +2 -0
  18. package/dist/runtime/content/client/useQueryLocale.js +6 -0
  19. package/dist/runtime/content/utils/retrieve-data-sources.d.ts +1 -0
  20. package/dist/runtime/content/utils/retrieve-data-sources.js +27 -4
  21. package/dist/runtime/iframe/runtime/overlay-hmr.js +3 -0
  22. package/dist/runtime/index.d.ts +3 -0
  23. package/dist/runtime/index.js +2 -0
  24. package/dist/runtime/primitives/lib/primitives.d.ts +2 -2
  25. package/dist/runtime/primitives/lib/query.d.ts +5 -3
  26. package/package.json +31 -13
  27. package/release.json +2 -2
  28. package/templates/react-router/package.json +1 -1
  29. package/templates/react-router/src/pages/Index.tsx +1 -1
  30. package/dist/build/vite/index.d.ts.map +0 -1
  31. package/dist/build/vite/plugins/content-sync.d.ts.map +0 -1
  32. package/dist/build/vite/plugins/content-sync.test.d.ts +0 -2
  33. package/dist/build/vite/plugins/content-sync.test.d.ts.map +0 -1
  34. package/dist/build/vite/plugins/content-sync.test.js +0 -64
  35. package/dist/build/vite/plugins/data-id.d.ts.map +0 -1
  36. package/dist/build/vite/plugins/fimo-config.d.ts.map +0 -1
  37. package/dist/build/vite/plugins/fimo-config.test.d.ts +0 -2
  38. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +0 -1
  39. package/dist/build/vite/plugins/fimo-config.test.js +0 -46
  40. package/dist/build/vite/plugins/overlay.d.ts.map +0 -1
  41. package/dist/build/vite/plugins/robots.d.ts.map +0 -1
  42. package/dist/build/vite/plugins/seo.d.ts.map +0 -1
  43. package/dist/build/vite/plugins/server-config.d.ts.map +0 -1
  44. package/dist/build/vite/plugins/sitemap.d.ts.map +0 -1
  45. package/dist/build/vite/plugins/translations.d.ts.map +0 -1
  46. package/dist/build/vite/plugins/translations.test.d.ts +0 -2
  47. package/dist/build/vite/plugins/translations.test.d.ts.map +0 -1
  48. package/dist/build/vite/plugins/translations.test.js +0 -266
  49. package/dist/runtime/app/FimoProviders.d.ts.map +0 -1
  50. package/dist/runtime/app/FimoScripts.d.ts.map +0 -1
  51. package/dist/runtime/app/index.d.ts.map +0 -1
  52. package/dist/runtime/app/prefetch.d.ts.map +0 -1
  53. package/dist/runtime/content/DataSourcesProvider.d.ts.map +0 -1
  54. package/dist/runtime/content/RefreshContentProvider.d.ts.map +0 -1
  55. package/dist/runtime/content/RefreshContentProvider.test.d.ts +0 -2
  56. package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +0 -1
  57. package/dist/runtime/content/RefreshContentProvider.test.js +0 -33
  58. package/dist/runtime/content/parseCollectionResponse.d.ts.map +0 -1
  59. package/dist/runtime/content/parseCollectionResponse.test.d.ts +0 -2
  60. package/dist/runtime/content/parseCollectionResponse.test.d.ts.map +0 -1
  61. package/dist/runtime/content/parseCollectionResponse.test.js +0 -20
  62. package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +0 -1
  63. package/dist/runtime/core/AppErrorBoundary.d.ts.map +0 -1
  64. package/dist/runtime/core/ViteLifecycles.d.ts.map +0 -1
  65. package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +0 -1
  66. package/dist/runtime/i18n/locale.d.ts.map +0 -1
  67. package/dist/runtime/i18n/locale.test.d.ts +0 -2
  68. package/dist/runtime/i18n/locale.test.d.ts.map +0 -1
  69. package/dist/runtime/i18n/locale.test.js +0 -58
  70. package/dist/runtime/iframe/ConsoleErrorProvider.d.ts.map +0 -1
  71. package/dist/runtime/iframe/ScreenshotProvider.d.ts.map +0 -1
  72. package/dist/runtime/iframe/runtime/overlay-hmr.d.ts.map +0 -1
  73. package/dist/runtime/iframe/runtime/overlay-runtime.d.ts.map +0 -1
  74. package/dist/runtime/index.d.ts.map +0 -1
  75. package/dist/runtime/paths/get-fimo-paths.d.ts.map +0 -1
  76. package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
  77. package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
  78. package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
  79. package/dist/runtime/paths/index.d.ts.map +0 -1
  80. package/dist/runtime/paths/types.d.ts.map +0 -1
  81. package/dist/runtime/primitives/components/Boolean.d.ts.map +0 -1
  82. package/dist/runtime/primitives/components/Date.d.ts.map +0 -1
  83. package/dist/runtime/primitives/components/Image.d.ts.map +0 -1
  84. package/dist/runtime/primitives/components/RichText.d.ts.map +0 -1
  85. package/dist/runtime/primitives/components/StaticImage.d.ts.map +0 -1
  86. package/dist/runtime/primitives/components/Text.d.ts.map +0 -1
  87. package/dist/runtime/primitives/components/Video.d.ts.map +0 -1
  88. package/dist/runtime/primitives/components/index.d.ts.map +0 -1
  89. package/dist/runtime/primitives/index.d.ts.map +0 -1
  90. package/dist/runtime/primitives/lib/image-optimization.d.ts.map +0 -1
  91. package/dist/runtime/primitives/lib/index.d.ts.map +0 -1
  92. package/dist/runtime/primitives/lib/primitives.d.ts.map +0 -1
  93. package/dist/runtime/primitives/lib/query.d.ts.map +0 -1
  94. package/dist/runtime/primitives/lib/template.d.ts.map +0 -1
  95. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +0 -1
  96. package/dist/runtime/primitives/public-surface.test.d.ts +0 -2
  97. package/dist/runtime/primitives/public-surface.test.d.ts.map +0 -1
  98. package/dist/runtime/primitives/public-surface.test.js +0 -45
  99. package/dist/runtime/primitives/translations.d.ts.map +0 -1
  100. package/dist/runtime/react-router/index.d.ts.map +0 -1
  101. package/dist/runtime/react-router/navigation-utils.d.ts.map +0 -1
  102. package/dist/runtime/react-router/navigation.d.ts.map +0 -1
  103. package/dist/runtime/react-router/navigation.test.d.ts +0 -2
  104. package/dist/runtime/react-router/navigation.test.d.ts.map +0 -1
  105. package/dist/runtime/react-router/navigation.test.js +0 -35
  106. package/dist/runtime/react-router/routes.d.ts.map +0 -1
  107. package/dist/runtime/react-router/routes.test.d.ts +0 -2
  108. package/dist/runtime/react-router/routes.test.d.ts.map +0 -1
  109. package/dist/runtime/react-router/routes.test.js +0 -67
  110. package/dist/runtime/routing/AppResetScrollOnNavigate.d.ts.map +0 -1
  111. package/dist/runtime/routing/AppRoutesProvider.d.ts.map +0 -1
  112. package/dist/runtime/seo/buildSeoForPath.d.ts.map +0 -1
  113. package/dist/runtime/seo/htmlProps.d.ts.map +0 -1
  114. package/dist/runtime/seo/index.d.ts.map +0 -1
  115. package/dist/runtime/seo/resolver.d.ts.map +0 -1
  116. package/dist/runtime/shared/fimo-config.server.d.ts.map +0 -1
  117. package/dist/runtime/shared/fimo-config.types.d.ts.map +0 -1
  118. package/dist/runtime/t/OptionalTracking.d.ts.map +0 -1
  119. package/dist/runtime/t/OptionalTracking.test.d.ts +0 -2
  120. package/dist/runtime/t/OptionalTracking.test.d.ts.map +0 -1
  121. package/dist/runtime/t/OptionalTracking.test.js +0 -11
  122. package/dist/runtime/t/Pageview.d.ts.map +0 -1
  123. package/dist/runtime/templates.d.ts.map +0 -1
  124. package/dist/scripts/generated-types.test.js +0 -212
  125. package/dist/scripts/lint-translation-keys.test.d.ts +0 -1
  126. package/dist/scripts/lint-translation-keys.test.js +0 -16
  127. package/dist/scripts/sync-schemas.test.d.ts +0 -1
  128. package/dist/scripts/sync-schemas.test.js +0 -124
  129. package/dist/scripts/validate-route-metadata.test.d.ts +0 -1
  130. package/dist/scripts/validate-route-metadata.test.js +0 -116
  131. package/dist/scripts/validate-schemas.test.d.ts +0 -1
  132. package/dist/scripts/validate-schemas.test.js +0 -263
  133. package/scripts/add-ext.mjs +0 -47
  134. package/scripts/bump-version.mjs +0 -58
  135. package/scripts/bundle-cli.mjs +0 -123
  136. package/scripts/install-cli.mjs +0 -36
  137. package/scripts/lib/channels.mjs +0 -70
  138. package/scripts/lib/dev-release-state.mjs +0 -44
  139. package/scripts/lib/dev-release-state.test.ts +0 -36
  140. package/scripts/lib/postinstall-onboarding.test.ts +0 -82
  141. package/scripts/lib/release-core.mjs +0 -225
  142. package/scripts/lib/release-core.test.ts +0 -147
  143. package/scripts/migration-qa-gate.test.ts +0 -155
  144. package/scripts/publish-npm.mjs +0 -180
  145. /package/dist/{scripts/generated-types.test.d.ts → runtime/content/client/types.js} +0 -0
@@ -1,263 +0,0 @@
1
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterEach, describe, expect, it } from 'vitest';
5
- import { validateSchemas } from './validate-schemas';
6
- describe('validateSchemas', () => {
7
- let scratch = null;
8
- afterEach(() => {
9
- if (scratch) {
10
- rmSync(scratch, { recursive: true, force: true });
11
- scratch = null;
12
- }
13
- });
14
- function writeProject(schemas) {
15
- scratch = mkdtempSync(join(tmpdir(), 'validate-schemas-'));
16
- mkdirSync(join(scratch, 'src/schemas'), { recursive: true });
17
- for (const [fileName, schema] of Object.entries(schemas)) {
18
- writeFileSync(join(scratch, 'src/schemas', fileName), typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2));
19
- }
20
- return scratch;
21
- }
22
- it('accepts a valid schema', () => {
23
- const cwd = writeProject({
24
- 'Author.json': {
25
- uid: 'Author',
26
- name: 'Authors',
27
- fields: {
28
- name: { type: 'string' },
29
- },
30
- },
31
- 'BlogPost.json': {
32
- uid: 'BlogPost',
33
- name: 'Blog Posts',
34
- fields: {
35
- title: { type: 'string' },
36
- body: { type: 'richtext' },
37
- coverImage: { type: 'media' },
38
- featured: { type: 'boolean' },
39
- author: { type: 'reference', target: 'Author' },
40
- },
41
- },
42
- });
43
- expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 2 });
44
- });
45
- it('accepts self-referential reference fields', () => {
46
- const cwd = writeProject({
47
- 'Item.json': {
48
- uid: 'Item',
49
- name: 'Items',
50
- fields: {
51
- title: { type: 'string' },
52
- parent: { type: 'reference', target: 'Item' },
53
- },
54
- },
55
- });
56
- expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 1 });
57
- });
58
- it('accepts a non-localized schema policy', () => {
59
- const cwd = writeProject({
60
- 'Author.json': {
61
- uid: 'Author',
62
- name: 'Authors',
63
- i18n: { enabled: false },
64
- fields: { name: { type: 'string' } },
65
- },
66
- });
67
- expect(validateSchemas({ cwd })).toEqual({ errors: [], schemasChecked: 1 });
68
- });
69
- it('rejects a non-boolean singleton policy', () => {
70
- const cwd = writeProject({
71
- 'Settings.json': {
72
- uid: 'Settings',
73
- name: 'Settings',
74
- isSingleton: 'yes',
75
- fields: {},
76
- },
77
- });
78
- const { errors } = validateSchemas({ cwd });
79
- expect(errors).toEqual([expect.objectContaining({ rule: 'schemas/singleton' })]);
80
- });
81
- it.each([true, { enabled: 'no' }, { enabled: false, fallback: 'en' }, { fallback: 'en' }])('rejects an invalid schema-level i18n policy %#', (i18n) => {
82
- const cwd = writeProject({
83
- 'Author.json': {
84
- uid: 'Author',
85
- name: 'Authors',
86
- i18n,
87
- fields: { name: { type: 'string' } },
88
- },
89
- });
90
- const { errors } = validateSchemas({ cwd });
91
- expect(errors).toHaveLength(1);
92
- expect(errors[0]).toMatchObject({ rule: 'schemas/i18n' });
93
- });
94
- it('returns no errors when the schemas directory does not exist', () => {
95
- scratch = mkdtempSync(join(tmpdir(), 'validate-schemas-'));
96
- expect(validateSchemas({ cwd: scratch })).toEqual({ errors: [], schemasChecked: 0 });
97
- });
98
- it('rejects reserved field names like slug', () => {
99
- const cwd = writeProject({
100
- 'TeamMember.json': {
101
- uid: 'TeamMember',
102
- name: 'Team Members',
103
- fields: {
104
- name: { type: 'string' },
105
- slug: { type: 'string' },
106
- },
107
- },
108
- });
109
- const { errors } = validateSchemas({ cwd });
110
- expect(errors).toHaveLength(1);
111
- expect(errors[0]).toMatchObject({ rule: 'schemas/reserved-field', file: 'src/schemas/TeamMember.json' });
112
- expect(errors[0].message).toContain('"slug" is reserved');
113
- expect(errors[0].message).toContain('entry data');
114
- });
115
- it('rejects every reserved metadata name', () => {
116
- const cwd = writeProject({
117
- 'Bad.json': {
118
- uid: 'Bad',
119
- name: 'Bad',
120
- fields: Object.fromEntries(['id', 'documentId', 'slug', 'locale', 'createdAt', 'updatedAt', '__fimo'].map((name) => [
121
- name,
122
- { type: 'string' },
123
- ])),
124
- },
125
- });
126
- const { errors } = validateSchemas({ cwd });
127
- expect(errors).toHaveLength(7);
128
- expect(errors.every((err) => err.rule === 'schemas/reserved-field')).toBe(true);
129
- });
130
- it('rejects unsupported field types', () => {
131
- const cwd = writeProject({
132
- 'Product.json': {
133
- uid: 'Product',
134
- name: 'Products',
135
- fields: { price: { type: 'decimal' } },
136
- },
137
- });
138
- const { errors } = validateSchemas({ cwd });
139
- expect(errors).toHaveLength(1);
140
- expect(errors[0]).toMatchObject({ rule: 'schemas/field-type' });
141
- expect(errors[0].message).toContain('"decimal"');
142
- expect(errors[0].message).toContain('string, richtext, media');
143
- });
144
- it('rejects reference fields without a valid target', () => {
145
- const cwd = writeProject({
146
- 'Post.json': {
147
- uid: 'Post',
148
- name: 'Posts',
149
- fields: { author: { type: 'reference' } },
150
- },
151
- });
152
- const { errors } = validateSchemas({ cwd });
153
- expect(errors).toHaveLength(1);
154
- expect(errors[0]).toMatchObject({ rule: 'schemas/reference-target' });
155
- });
156
- it('rejects reference fields targeting a missing local schema', () => {
157
- const cwd = writeProject({
158
- 'Post.json': {
159
- uid: 'Post',
160
- name: 'Posts',
161
- fields: { author: { type: 'reference', target: 'Author' } },
162
- },
163
- });
164
- const { errors } = validateSchemas({ cwd });
165
- expect(errors).toHaveLength(1);
166
- expect(errors[0]).toMatchObject({ rule: 'schemas/reference-target' });
167
- expect(errors[0].message).toContain('unknown schema "Author"');
168
- });
169
- it('rejects reference-only properties on scalar fields', () => {
170
- const cwd = writeProject({
171
- 'Post.json': {
172
- uid: 'Post',
173
- name: 'Posts',
174
- fields: { author: { type: 'string', target: 'Author' } },
175
- },
176
- });
177
- const { errors } = validateSchemas({ cwd });
178
- expect(errors).toHaveLength(1);
179
- expect(errors[0]).toMatchObject({ rule: 'schemas/reference-shape' });
180
- });
181
- it('rejects schema uid names that cannot be used as content type identifiers', () => {
182
- const cwd = writeProject({
183
- 'blog-post.json': {
184
- uid: 'blog-post',
185
- name: 'Blog Posts',
186
- fields: { title: { type: 'string' } },
187
- },
188
- });
189
- const { errors } = validateSchemas({ cwd });
190
- expect(errors).toHaveLength(1);
191
- expect(errors[0]).toMatchObject({ rule: 'schemas/type-name' });
192
- });
193
- it('rejects field names that cannot be used in generated source paths', () => {
194
- const cwd = writeProject({
195
- 'Page.json': {
196
- uid: 'Page',
197
- name: 'Pages',
198
- fields: { 'hero-title': { type: 'string' } },
199
- },
200
- });
201
- const { errors } = validateSchemas({ cwd });
202
- expect(errors).toHaveLength(1);
203
- expect(errors[0]).toMatchObject({ rule: 'schemas/field-name' });
204
- });
205
- it('rejects field-level i18n settings', () => {
206
- const cwd = writeProject({
207
- 'Page.json': {
208
- uid: 'Page',
209
- name: 'Pages',
210
- fields: { title: { type: 'string', i18n: true } },
211
- },
212
- });
213
- const { errors } = validateSchemas({ cwd });
214
- expect(errors).toHaveLength(1);
215
- expect(errors[0]).toMatchObject({ rule: 'schemas/field-i18n' });
216
- });
217
- it('rejects reference-level locale settings', () => {
218
- const cwd = writeProject({
219
- 'Page.json': {
220
- uid: 'Page',
221
- name: 'Pages',
222
- fields: { parent: { type: 'reference', target: 'Page', localized: true } },
223
- },
224
- });
225
- const { errors } = validateSchemas({ cwd });
226
- expect(errors).toHaveLength(1);
227
- expect(errors[0]).toMatchObject({ rule: 'schemas/field-i18n' });
228
- expect(errors[0].message).toContain('"localized"');
229
- });
230
- it('rejects a uid that does not match the file name', () => {
231
- const cwd = writeProject({
232
- 'TeamMember.json': {
233
- uid: 'Member',
234
- name: 'Team Members',
235
- fields: { name: { type: 'string' } },
236
- },
237
- });
238
- const { errors } = validateSchemas({ cwd });
239
- expect(errors).toHaveLength(1);
240
- expect(errors[0]).toMatchObject({ rule: 'schemas/uid' });
241
- });
242
- it('rejects missing uid, name, and fields', () => {
243
- const cwd = writeProject({ 'Empty.json': {} });
244
- const { errors } = validateSchemas({ cwd });
245
- expect(errors.map((err) => err.rule)).toEqual(['schemas/uid', 'schemas/name', 'schemas/fields']);
246
- });
247
- it('reports invalid JSON', () => {
248
- const cwd = writeProject({ 'Broken.json': '{ not json' });
249
- const { errors } = validateSchemas({ cwd });
250
- expect(errors).toHaveLength(1);
251
- expect(errors[0]).toMatchObject({ rule: 'schemas/parse' });
252
- });
253
- it('validates multiple files and counts them', () => {
254
- const cwd = writeProject({
255
- 'Good.json': { uid: 'Good', name: 'Good', fields: { title: { type: 'string' } } },
256
- 'Bad.json': { uid: 'Bad', name: 'Bad', fields: { slug: { type: 'string' } } },
257
- });
258
- const result = validateSchemas({ cwd });
259
- expect(result.schemasChecked).toBe(2);
260
- expect(result.errors).toHaveLength(1);
261
- expect(result.errors[0].file).toBe('src/schemas/Bad.json');
262
- });
263
- });
@@ -1,47 +0,0 @@
1
- #!/usr/bin/env node
2
- // Rewrite relative imports to add `.js` extensions (required by Node ESM).
3
- // Idempotent. Preserves all other code.
4
- import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from 'node:fs';
5
- import { extname, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
-
8
- const HERE = resolve(fileURLToPath(import.meta.url), '..');
9
- const ROOTS = process.argv.slice(2).map((p) => resolve(p));
10
-
11
- const IMPORT_RE = /(from\s+|import\s*\(\s*)(['"])(\.\.?\/[^'"\s]+?)\2/g;
12
-
13
- function walk(dir, out) {
14
- for (const name of readdirSync(dir)) {
15
- if (['node_modules', 'dist', '.git'].includes(name)) continue;
16
- const p = `${dir}/${name}`;
17
- const st = statSync(p);
18
- if (st.isDirectory()) walk(p, out);
19
- else if (/\.(ts|tsx|mts|mjs)$/.test(name) && !name.endsWith('.d.ts')) out.push(p);
20
- }
21
- }
22
-
23
- function rewriteOne(filePath) {
24
- const src = readFileSync(filePath, 'utf-8');
25
- const dir = filePath.split('/').slice(0, -1).join('/');
26
- let mutated = false;
27
- const out = src.replace(IMPORT_RE, (match, lead, quote, spec) => {
28
- const ext = extname(spec);
29
- if (ext) return match;
30
- const candidate = resolve(dir, spec);
31
- if (existsSync(candidate) && statSync(candidate).isDirectory()) {
32
- mutated = true;
33
- return `${lead}${quote}${spec}/index.js${quote}`;
34
- }
35
- mutated = true;
36
- return `${lead}${quote}${spec}.js${quote}`;
37
- });
38
- if (mutated) {
39
- writeFileSync(filePath, out);
40
- console.log(`updated ${filePath.slice(HERE.length - 7)}`);
41
- }
42
- }
43
-
44
- const files = [];
45
- for (const r of ROOTS) walk(r, files);
46
- for (const f of files) rewriteOne(f);
47
- console.log(`[add-ext] processed ${files.length} files`);
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env node
2
- // Bumps the Fimo CLI version in apps/cli/package.json — NO git tag, NO commit.
3
- //
4
- // Under the branch-driven release model the version lives in
5
- // apps/cli/package.json and you bump it in a PR. CI creates the
6
- // `cli@<version>` git tag when it publishes `@latest` on a `production`
7
- // merge, so this script must NOT create a tag — that would collide with
8
- // CI's tag-guard (the publish would skip, thinking the version already
9
- // shipped).
10
- //
11
- // Usage:
12
- // pnpm -F fimo release:bump patch
13
- // pnpm -F fimo release:bump minor
14
- // pnpm -F fimo release:bump major
15
- // pnpm -F fimo release:bump 1.2.3 # explicit version
16
- //
17
- // After bumping, commit the change in your PR. Merging it through to
18
- // `production` is what publishes `@latest` and creates the tag.
19
-
20
- import { spawnSync } from 'node:child_process';
21
- import { dirname, resolve } from 'node:path';
22
- import { fileURLToPath } from 'node:url';
23
-
24
- const HERE = dirname(fileURLToPath(import.meta.url));
25
- const CLI_ROOT = resolve(HERE, '..');
26
-
27
- const VALID_INCREMENTS = new Set(['major', 'minor', 'patch', 'premajor', 'preminor', 'prepatch', 'prerelease']);
28
-
29
- const args = process.argv.slice(2);
30
- if (args.length === 0) {
31
- console.error(
32
- '[cli/bump] usage: pnpm -F fimo release:bump <patch|minor|major|premajor|preminor|prepatch|prerelease|X.Y.Z> [--preid <id>]'
33
- );
34
- process.exit(1);
35
- }
36
-
37
- const increment = args[0];
38
- const isExplicitVersion = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$/.test(increment);
39
- if (!VALID_INCREMENTS.has(increment) && !isExplicitVersion) {
40
- console.error(
41
- `[cli/bump] invalid increment "${increment}". expected one of ${[...VALID_INCREMENTS].join('|')} or an explicit X.Y.Z.`
42
- );
43
- process.exit(1);
44
- }
45
-
46
- // Bump package.json ONLY — `--no-git-tag-version` skips both the commit and
47
- // the tag. CI owns the `cli@<version>` tag (created on the @latest publish).
48
- // Use pnpm (not npm) — npm chokes on the `workspace:*` devDeps.
49
- const versionArgs = ['version', '--no-git-tag-version', ...args];
50
- console.log(`[cli/bump] $ pnpm ${versionArgs.join(' ')}`);
51
- const result = spawnSync('pnpm', versionArgs, { cwd: CLI_ROOT, stdio: 'inherit' });
52
- if (result.status !== 0) {
53
- console.error(`[cli/bump] pnpm version failed (exit ${result.status}).`);
54
- process.exit(result.status ?? 1);
55
- }
56
-
57
- console.log('[cli/bump] bumped apps/cli/package.json (no tag, no commit).');
58
- console.log('[cli/bump] commit it in your PR — merging to `production` publishes @latest + tags it.');
@@ -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
- });