@sealcode/sealgen 0.18.2 → 0.18.5

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 (209) hide show
  1. package/@types/add-collection.d.ts +1 -0
  2. package/@types/add-crud.d.ts +3 -0
  3. package/@types/add-jdd-component.d.ts +1 -0
  4. package/@types/add-list-route.test.d.ts +1 -0
  5. package/@types/add-route.d.ts +3 -0
  6. package/@types/add-route.test.d.ts +1 -0
  7. package/@types/add-verbose-crud.d.ts +3 -0
  8. package/@types/build.d.ts +3 -0
  9. package/@types/builders/backend-ts-builder.d.ts +7 -0
  10. package/@types/builders/builder.d.ts +11 -0
  11. package/@types/builders/colors-builder.d.ts +10 -0
  12. package/@types/builders/css-builder.d.ts +19 -0
  13. package/@types/builders/fonts-builder.d.ts +7 -0
  14. package/@types/builders/frontend-ts-builder.d.ts +11 -0
  15. package/@types/cli.d.ts +2 -0
  16. package/@types/components/components.d.ts +2 -0
  17. package/@types/components/tabs.d.ts +2 -0
  18. package/@types/components/toggle-modal.d.ts +6 -0
  19. package/@types/crud-field-snippets.d.ts +3 -0
  20. package/@types/embeddable-file-extensions.d.ts +1 -0
  21. package/@types/esbuild-plugins/load-assets.d.ts +5 -0
  22. package/@types/esbuild-plugins/rewrite-asset-imports.d.ts +5 -0
  23. package/@types/example.test.d.ts +1 -0
  24. package/@types/find-css-includes.d.ts +1 -0
  25. package/@types/forms/controls/array.d.ts +35 -0
  26. package/@types/forms/controls/autocomplete.d.ts +22 -0
  27. package/@types/forms/controls/big-radio-group.d.ts +14 -0
  28. package/@types/forms/controls/checkbox-group.d.ts +21 -0
  29. package/@types/forms/controls/checkbox.d.ts +17 -0
  30. package/@types/forms/controls/checkboxed-list-input.d.ts +21 -0
  31. package/@types/forms/controls/checkboxed-list-input.test.d.ts +1 -0
  32. package/@types/forms/controls/collapsible-checkbox-group.d.ts +11 -0
  33. package/@types/forms/controls/collapsible-field-group.d.ts +21 -0
  34. package/@types/forms/controls/controls.d.ts +31 -0
  35. package/@types/forms/controls/derived-frame.d.ts +21 -0
  36. package/@types/forms/controls/derived.d.ts +13 -0
  37. package/@types/forms/controls/dropdown.d.ts +30 -0
  38. package/@types/forms/controls/editable-collection-subset.d.ts +20 -0
  39. package/@types/forms/controls/field-group.d.ts +23 -0
  40. package/@types/forms/controls/file.d.ts +13 -0
  41. package/@types/forms/controls/file.test.d.ts +1 -0
  42. package/@types/forms/controls/form-control.d.ts +20 -0
  43. package/@types/forms/controls/form-field-control.d.ts +13 -0
  44. package/@types/forms/controls/form-header.d.ts +8 -0
  45. package/@types/forms/controls/form-paragraph.d.ts +7 -0
  46. package/@types/forms/controls/frame.d.ts +17 -0
  47. package/@types/forms/controls/hidden-input.d.ts +9 -0
  48. package/@types/forms/controls/html.d.ts +11 -0
  49. package/@types/forms/controls/multiple-files.d.ts +40 -0
  50. package/@types/forms/controls/multiple-photos.d.ts +19 -0
  51. package/@types/forms/controls/number.d.ts +13 -0
  52. package/@types/forms/controls/photo.d.ts +21 -0
  53. package/@types/forms/controls/photo.test.d.ts +1 -0
  54. package/@types/forms/controls/radio.d.ts +16 -0
  55. package/@types/forms/controls/reverse-single-reference.d.ts +1 -0
  56. package/@types/forms/controls/simple-input.d.ts +33 -0
  57. package/@types/forms/controls/single-reference.d.ts +13 -0
  58. package/@types/forms/controls/single-reference.test.d.ts +1 -0
  59. package/@types/forms/controls/table.d.ts +34 -0
  60. package/@types/forms/controls/textarea.d.ts +25 -0
  61. package/@types/forms/controls/tickable.d.ts +25 -0
  62. package/@types/forms/fields/boolean.d.ts +17 -0
  63. package/@types/forms/fields/boolean.test.d.ts +1 -0
  64. package/@types/forms/fields/checkboxed-list.d.ts +21 -0
  65. package/@types/forms/fields/collection-field.d.ts +15 -0
  66. package/@types/forms/fields/email.d.ts +7 -0
  67. package/@types/forms/fields/email.test.d.ts +1 -0
  68. package/@types/forms/fields/enum-multiple.d.ts +18 -0
  69. package/@types/forms/fields/enum-multiple.test.d.ts +1 -0
  70. package/@types/forms/fields/enum.d.ts +18 -0
  71. package/@types/forms/fields/enum.test.d.ts +1 -0
  72. package/@types/forms/fields/existing-sealious-value.d.ts +6 -0
  73. package/@types/forms/fields/field.d.ts +48 -0
  74. package/@types/forms/fields/field.test.d.ts +1 -0
  75. package/@types/forms/fields/fields.d.ts +16 -0
  76. package/@types/forms/fields/file.d.ts +19 -0
  77. package/@types/forms/fields/file.test.d.ts +1 -0
  78. package/@types/forms/fields/multiple-files.d.ts +14 -0
  79. package/@types/forms/fields/number.d.ts +23 -0
  80. package/@types/forms/fields/number.test.d.ts +1 -0
  81. package/@types/forms/fields/pick-from-list.d.ts +14 -0
  82. package/@types/forms/fields/pick-from-list.test.d.ts +1 -0
  83. package/@types/forms/fields/proxy-field.d.ts +13 -0
  84. package/@types/forms/fields/regexp.d.ts +16 -0
  85. package/@types/forms/fields/regexp.test.d.ts +1 -0
  86. package/@types/forms/fields/reverse-single-reference.d.ts +13 -0
  87. package/@types/forms/fields/simple-form-field.d.ts +9 -0
  88. package/@types/forms/fields/structured-array.d.ts +14 -0
  89. package/@types/forms/fields/table.d.ts +13 -0
  90. package/@types/forms/form-types.d.ts +26 -0
  91. package/@types/forms/form.d.ts +31 -0
  92. package/@types/forms/form.test.d.ts +6 -0
  93. package/@types/forms/multiform.d.ts +24 -0
  94. package/@types/forms/multiform.test.d.ts +1 -0
  95. package/@types/generate-collections.d.ts +2 -0
  96. package/@types/generate-components.d.ts +1 -0
  97. package/@types/generate-routes.d.ts +5 -0
  98. package/@types/generate-routes.test.d.ts +1 -0
  99. package/@types/generate-stimulus.d.ts +1 -0
  100. package/@types/get-fonts.d.ts +2 -0
  101. package/@types/index.d.ts +46 -0
  102. package/@types/make-env.d.ts +1 -0
  103. package/@types/make-hidden-inputs.d.ts +5 -0
  104. package/@types/mount.d.ts +7 -0
  105. package/@types/notifier.d.ts +2 -0
  106. package/@types/page/access-control.d.ts +6 -0
  107. package/@types/page/default-list-filters.d.ts +18 -0
  108. package/@types/page/list.d.ts +48 -0
  109. package/@types/page/mountable-with-fields.d.ts +42 -0
  110. package/@types/page/mountable-with-fields.test.d.ts +1 -0
  111. package/@types/page/mountable.d.ts +11 -0
  112. package/@types/page/page.d.ts +19 -0
  113. package/@types/page/props-parser.d.ts +16 -0
  114. package/@types/page/sealious-list.d.ts +20 -0
  115. package/@types/page/sealious-list.test.d.ts +1 -0
  116. package/@types/page/stateful-page.d.ts +63 -0
  117. package/@types/page/stateful-page.test.d.ts +1 -0
  118. package/@types/register-external.d.ts +2 -0
  119. package/@types/register-external.test.d.ts +1 -0
  120. package/@types/run.d.ts +1 -0
  121. package/@types/sanitize.d.ts +1 -0
  122. package/@types/src/builder.d.ts +9 -7
  123. package/@types/src/crud-field-snippets.d.ts +3 -0
  124. package/@types/src/page/stateful-page.d.ts +1 -1
  125. package/@types/src/register-external.d.ts +2 -0
  126. package/@types/src/register-external.test.d.ts +1 -0
  127. package/@types/src/templates/shared/handlers/settable-by.d.ts +2 -0
  128. package/@types/src/templates/shared/shared-crud-form-fields.d.ts +1 -1
  129. package/@types/target-locreq.d.ts +4 -0
  130. package/@types/templates/collection.d.ts +1 -0
  131. package/@types/templates/form.d.ts +16 -0
  132. package/@types/templates/jdd-component.d.ts +1 -0
  133. package/@types/templates/jdd-editor.d.ts +1 -0
  134. package/@types/templates/list.d.ts +1 -0
  135. package/@types/templates/long-running-process.d.ts +1 -0
  136. package/@types/templates/multiform.d.ts +1 -0
  137. package/@types/templates/page.d.ts +1 -0
  138. package/@types/templates/post.d.ts +1 -0
  139. package/@types/templates/redirect.d.ts +1 -0
  140. package/@types/templates/route-test.d.ts +1 -0
  141. package/@types/templates/shared/collection-create-form.d.ts +1 -0
  142. package/@types/templates/shared/collection-edit-form.d.ts +1 -0
  143. package/@types/templates/shared/collection-list.d.ts +16 -0
  144. package/@types/templates/shared/handlers/boolean.d.ts +2 -0
  145. package/@types/templates/shared/handlers/date.d.ts +2 -0
  146. package/@types/templates/shared/handlers/deep-reverse-single-reference.d.ts +2 -0
  147. package/@types/templates/shared/handlers/default.d.ts +2 -0
  148. package/@types/templates/shared/handlers/enum.d.ts +2 -0
  149. package/@types/templates/shared/handlers/file.d.ts +2 -0
  150. package/@types/templates/shared/handlers/image.d.ts +2 -0
  151. package/@types/templates/shared/handlers/int.d.ts +2 -0
  152. package/@types/templates/shared/handlers/jdd.d.ts +2 -0
  153. package/@types/templates/shared/handlers/single-reference.d.ts +2 -0
  154. package/@types/templates/shared/handlers/structured-array.d.ts +2 -0
  155. package/@types/templates/shared/item-delete.d.ts +1 -0
  156. package/@types/templates/shared/shared-crud-form-fields.d.ts +68 -0
  157. package/@types/templates/stateful-page.d.ts +1 -0
  158. package/@types/templates/stimulus-controller.d.ts +1 -0
  159. package/@types/templates/templates.d.ts +21 -0
  160. package/@types/test_utils/exec_async.d.ts +1 -0
  161. package/@types/test_utils/locator_is_visible.d.ts +2 -0
  162. package/@types/test_utils/test-on-real-app.d.ts +27 -0
  163. package/@types/utils/assert-throws.d.ts +1 -0
  164. package/@types/utils/base64.d.ts +2 -0
  165. package/@types/utils/browser-creator.d.ts +1 -0
  166. package/@types/utils/escape-url-params.d.ts +2 -0
  167. package/@types/utils/exec.d.ts +5 -0
  168. package/@types/utils/extract-fields-from-collection.d.ts +14 -0
  169. package/@types/utils/format-external-url.d.ts +1 -0
  170. package/@types/utils/import-path.d.ts +2 -0
  171. package/@types/utils/input-wrapper.d.ts +3 -0
  172. package/@types/utils/list-collections.d.ts +1 -0
  173. package/@types/utils/make-slug.d.ts +1 -0
  174. package/@types/utils/module-dirname.d.ts +1 -0
  175. package/@types/utils/natural-numbers.d.ts +1 -0
  176. package/@types/utils/prettier.d.ts +3 -0
  177. package/@types/utils/question.d.ts +1 -0
  178. package/@types/utils/render-attributes.d.ts +1 -0
  179. package/@types/utils/sleep.d.ts +1 -0
  180. package/@types/utils/translate.d.ts +3 -0
  181. package/@types/utils/url-with-new-params.d.ts +2 -0
  182. package/@types/utils/utils.d.ts +17 -0
  183. package/@types/utils/walk.d.ts +1 -0
  184. package/@types/utils/with-fallback.d.ts +2 -0
  185. package/@types/utils/write-file.d.ts +1 -0
  186. package/README.md +57 -0
  187. package/lib/cli.js +13 -5
  188. package/lib/crud-field-snippets.js +52 -0
  189. package/lib/esbuild-plugins/rewrite-asset-imports.js +0 -2
  190. package/lib/page/list.js +6 -2
  191. package/lib/page/stateful-page.js +1 -1
  192. package/lib/register-external.js +105 -0
  193. package/lib/register-external.test.js +340 -0
  194. package/lib/templates/shared/handlers/default.js +1 -1
  195. package/lib/templates/shared/shared-crud-form-fields.js +2 -0
  196. package/lib/utils/extract-fields-from-collection.js +3 -3
  197. package/package.json +1 -1
  198. package/src/cli.ts +12 -4
  199. package/src/crud-field-snippets.ts +57 -0
  200. package/src/esbuild-plugins/rewrite-asset-imports.ts +0 -2
  201. package/src/find-css-includes.ts +0 -3
  202. package/src/page/list.ts +16 -3
  203. package/src/page/stateful-page.ts +3 -1
  204. package/src/register-external.test.ts +469 -0
  205. package/src/register-external.ts +148 -0
  206. package/src/templates/shared/handlers/default.ts +1 -0
  207. package/src/templates/shared/shared-crud-form-fields.ts +8 -0
  208. package/src/utils/extract-fields-from-collection.ts +3 -3
  209. package/remove-named-imports.js +0 -68
@@ -0,0 +1,469 @@
1
+ import assert from "assert";
2
+ import { promises as fs } from "fs";
3
+ import path from "path";
4
+ import { RealAppTest } from "./test_utils/test-on-real-app.js";
5
+
6
+ // Helper function to ensure sealgen configuration exists
7
+ async function ensureSealgenConfig(test_app: RealAppTest): Promise<void> {
8
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
9
+ let packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
10
+ let packageJson = JSON.parse(packageJsonContent);
11
+
12
+ if (!packageJson.sealgen) {
13
+ packageJson.sealgen = {};
14
+ }
15
+
16
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
17
+ }
18
+
19
+ // Helper function to create mock external module
20
+ async function createMockExternalModule(
21
+ test_app: RealAppTest,
22
+ moduleName: string,
23
+ subdirectory: string,
24
+ files: Array<{ name: string; content: string }>
25
+ ): Promise<void> {
26
+ const externalModulePath = path.join(test_app.app_path, "node_modules", moduleName);
27
+ const targetPath = path.join(externalModulePath, subdirectory);
28
+
29
+ await fs.mkdir(targetPath, { recursive: true });
30
+
31
+ for (const file of files) {
32
+ await fs.writeFile(path.join(targetPath, file.name), file.content);
33
+ // If the file is a .ts controller, also create a .js file for build import
34
+ if (file.name.endsWith(".ts") && file.name.includes("stimulus")) {
35
+ const jsName = file.name.replace(/\.ts$/, ".js");
36
+ const exportName = file.name.replace(/\.stimulus\.ts$/, "");
37
+ await fs.writeFile(
38
+ path.join(targetPath, jsName),
39
+ `export default { connect() { console.log('Dummy JS for ${exportName}'); } }`
40
+ );
41
+ }
42
+ }
43
+ }
44
+
45
+ describe("register-external CLI commands", () => {
46
+ describe("package.json configuration", () => {
47
+ it("registers external controllers and verifies package.json is updated", async function () {
48
+ const test_app = await RealAppTest.init();
49
+ await ensureSealgenConfig(test_app);
50
+
51
+ const moduleName = "test-controller-module";
52
+ const subdirectory = "controllers";
53
+
54
+ await test_app.runSealgenCommand(
55
+ `register-external-controllers ${moduleName} ${subdirectory}`
56
+ );
57
+
58
+ // Verify package.json was updated correctly
59
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
60
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
61
+ const packageJson = JSON.parse(packageJsonContent);
62
+
63
+ assert(packageJson.sealgen, "sealgen configuration should exist");
64
+ assert(packageJson.sealgen.controllerDirs, "controllerDirs should exist");
65
+ assert(
66
+ Array.isArray(packageJson.sealgen.controllerDirs),
67
+ "controllerDirs should be an array"
68
+ );
69
+
70
+ const expectedPath = `node_modules/${moduleName}/${subdirectory}`;
71
+ assert(
72
+ packageJson.sealgen.controllerDirs.includes(expectedPath),
73
+ `controllerDirs should contain ${expectedPath}`
74
+ );
75
+
76
+ await test_app.close();
77
+ }).timeout(100 * 1000);
78
+
79
+ it("registers external styles and verifies package.json is updated", async function () {
80
+ const test_app = await RealAppTest.init();
81
+ await ensureSealgenConfig(test_app);
82
+
83
+ const moduleName = "test-style-module";
84
+ const subdirectory = "styles";
85
+
86
+ await test_app.runSealgenCommand(
87
+ `register-external-styles ${moduleName} ${subdirectory}`
88
+ );
89
+
90
+ // Verify package.json was updated correctly
91
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
92
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
93
+ const packageJson = JSON.parse(packageJsonContent);
94
+
95
+ assert(packageJson.sealgen, "sealgen configuration should exist");
96
+ assert(packageJson.sealgen.styleDirs, "styleDirs should exist");
97
+ assert(Array.isArray(packageJson.sealgen.styleDirs), "styleDirs should be an array");
98
+
99
+ const expectedPath = `node_modules/${moduleName}/${subdirectory}`;
100
+ assert(
101
+ packageJson.sealgen.styleDirs.includes(expectedPath),
102
+ `styleDirs should contain ${expectedPath}`
103
+ );
104
+
105
+ await test_app.close();
106
+ }).timeout(100 * 1000);
107
+
108
+ it("registers both controllers and styles in the same project", async function () {
109
+ const test_app = await RealAppTest.init();
110
+ await ensureSealgenConfig(test_app);
111
+
112
+ const controllerModule = "test-controller-module";
113
+ const controllerSubdir = "controllers";
114
+ const styleModule = "test-style-module";
115
+ const styleSubdir = "styles";
116
+
117
+ await test_app.runSealgenCommand(
118
+ `register-external-controllers ${controllerModule} ${controllerSubdir}`
119
+ );
120
+ await test_app.runSealgenCommand(
121
+ `register-external-styles ${styleModule} ${styleSubdir}`
122
+ );
123
+
124
+ // Verify package.json was updated correctly for both
125
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
126
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
127
+ const packageJson = JSON.parse(packageJsonContent);
128
+
129
+ assert(packageJson.sealgen, "sealgen configuration should exist");
130
+ assert(packageJson.sealgen.controllerDirs, "controllerDirs should exist");
131
+ assert(packageJson.sealgen.styleDirs, "styleDirs should exist");
132
+
133
+ const expectedControllerPath = `node_modules/${controllerModule}/${controllerSubdir}`;
134
+ const expectedStylePath = `node_modules/${styleModule}/${styleSubdir}`;
135
+
136
+ assert(
137
+ packageJson.sealgen.controllerDirs.includes(expectedControllerPath),
138
+ `controllerDirs should contain ${expectedControllerPath}`
139
+ );
140
+ assert(
141
+ packageJson.sealgen.styleDirs.includes(expectedStylePath),
142
+ `styleDirs should contain ${expectedStylePath}`
143
+ );
144
+
145
+ await test_app.close();
146
+ }).timeout(100 * 1000);
147
+
148
+ it("handles duplicate registrations gracefully", async function () {
149
+ const test_app = await RealAppTest.init();
150
+ await ensureSealgenConfig(test_app);
151
+
152
+ const moduleName = "test-duplicate-module";
153
+ const subdirectory = "controllers";
154
+
155
+ // Register the same module twice
156
+ await test_app.runSealgenCommand(
157
+ `register-external-controllers ${moduleName} ${subdirectory}`
158
+ );
159
+ await test_app.runSealgenCommand(
160
+ `register-external-controllers ${moduleName} ${subdirectory}`
161
+ );
162
+
163
+ // Verify package.json was updated correctly (should not have duplicates)
164
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
165
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
166
+ const packageJson = JSON.parse(packageJsonContent);
167
+
168
+ assert(packageJson.sealgen, "sealgen configuration should exist");
169
+ assert(packageJson.sealgen.controllerDirs, "controllerDirs should exist");
170
+
171
+ const expectedPath = `node_modules/${moduleName}/${subdirectory}`;
172
+ const controllerDirs = packageJson.sealgen.controllerDirs as string[];
173
+
174
+ // Should only appear once
175
+ const occurrences = controllerDirs.filter((dir) => dir === expectedPath).length;
176
+ assert(
177
+ occurrences === 1,
178
+ `controllerDirs should contain ${expectedPath} exactly once, found ${occurrences} times`
179
+ );
180
+
181
+ await test_app.close();
182
+ }).timeout(100 * 1000);
183
+
184
+ it("preserves existing sealgen configuration when adding new entries", async function () {
185
+ const test_app = await RealAppTest.init();
186
+
187
+ // First, manually add some existing configuration to package.json
188
+ const packageJsonPath = path.join(test_app.app_path, "package.json");
189
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
190
+ const packageJson = JSON.parse(packageJsonContent);
191
+
192
+ // Add existing sealgen configuration
193
+ packageJson.sealgen = {
194
+ controllerDirs: ["node_modules/existing-controller/controllers"],
195
+ styleDirs: ["node_modules/existing-style/styles"],
196
+ };
197
+
198
+ await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
199
+
200
+ // Now register new external modules
201
+ const newControllerModule = "new-controller-module";
202
+ const newStyleModule = "new-style-module";
203
+
204
+ await test_app.runSealgenCommand(
205
+ `register-external-controllers ${newControllerModule} controllers`
206
+ );
207
+ await test_app.runSealgenCommand(`register-external-styles ${newStyleModule} styles`);
208
+
209
+ // Verify both old and new configurations are preserved
210
+ const updatedPackageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
211
+ const updatedPackageJson = JSON.parse(updatedPackageJsonContent);
212
+
213
+ assert(updatedPackageJson.sealgen, "sealgen configuration should exist");
214
+ assert(updatedPackageJson.sealgen.controllerDirs, "controllerDirs should exist");
215
+ assert(updatedPackageJson.sealgen.styleDirs, "styleDirs should exist");
216
+
217
+ // Check that existing entries are preserved
218
+ assert(
219
+ updatedPackageJson.sealgen.controllerDirs.includes(
220
+ "node_modules/existing-controller/controllers"
221
+ ),
222
+ "Existing controller directory should be preserved"
223
+ );
224
+ assert(
225
+ updatedPackageJson.sealgen.styleDirs.includes("node_modules/existing-style/styles"),
226
+ "Existing style directory should be preserved"
227
+ );
228
+
229
+ // Check that new entries are added
230
+ assert(
231
+ updatedPackageJson.sealgen.controllerDirs.includes(
232
+ `node_modules/${newControllerModule}/controllers`
233
+ ),
234
+ "New controller directory should be added"
235
+ );
236
+ assert(
237
+ updatedPackageJson.sealgen.styleDirs.includes(
238
+ `node_modules/${newStyleModule}/styles`
239
+ ),
240
+ "New style directory should be added"
241
+ );
242
+
243
+ await test_app.close();
244
+ }).timeout(100 * 1000);
245
+ });
246
+
247
+ describe("build integration", () => {
248
+ it("registers external controllers and verifies they are included in build", async function () {
249
+ const test_app = await RealAppTest.init();
250
+ await ensureSealgenConfig(test_app);
251
+
252
+ // Create a mock external module with controllers
253
+ const externalModuleName = "test-external-controllers";
254
+ await createMockExternalModule(test_app, externalModuleName, "controllers", [
255
+ {
256
+ name: "test-external.stimulus.ts",
257
+ content: `
258
+ import { Controller } from "stimulus";
259
+
260
+ export default class TestExternalController extends Controller {
261
+ static targets = ["output"];
262
+
263
+ connect() {
264
+ console.log("External controller connected!");
265
+ }
266
+ }
267
+ `,
268
+ },
269
+ ]);
270
+
271
+ // Register the external controllers
272
+ await test_app.runSealgenCommand(
273
+ `register-external-controllers ${externalModuleName} controllers`
274
+ );
275
+
276
+ // Run the build process
277
+ await test_app.runSealgenCommand("build");
278
+
279
+ // Verify the controllers are included in the build
280
+ const controllersFilePath = path.join(test_app.app_path, "src/front/controllers.ts");
281
+ const controllersContent = await fs.readFile(controllersFilePath, "utf-8");
282
+
283
+ // Check that the external controller is imported
284
+ assert(
285
+ controllersContent.includes(
286
+ `import { default as TestExternal } from "./../../node_modules/${externalModuleName}/controllers/test-external.stimulus.js"`
287
+ ),
288
+ "External controller should be imported in generated controllers file"
289
+ );
290
+
291
+ // Check that the controller is registered
292
+ assert(
293
+ controllersContent.includes(`application.register("test-external", TestExternal);`),
294
+ "External controller should be registered in generated controllers file"
295
+ );
296
+
297
+ // Verify the build output exists
298
+ const bundlePath = path.join(test_app.app_path, "public/dist/bundle.js");
299
+ assert(
300
+ await fs
301
+ .stat(bundlePath)
302
+ .then(() => true)
303
+ .catch(() => false),
304
+ "Bundle should be generated"
305
+ );
306
+
307
+ await test_app.close();
308
+ }).timeout(100 * 1000);
309
+
310
+ it("registers external styles and verifies they are included in build", async function () {
311
+ const test_app = await RealAppTest.init();
312
+ await ensureSealgenConfig(test_app);
313
+
314
+ // Create a mock external module with styles
315
+ const externalModuleName = "test-external-styles";
316
+ await createMockExternalModule(test_app, externalModuleName, "styles", [
317
+ {
318
+ name: "external.css",
319
+ content: `
320
+ .external-style {
321
+ color: red;
322
+ background: blue;
323
+ }
324
+ `,
325
+ },
326
+ ]);
327
+
328
+ // Register the external styles
329
+ await test_app.runSealgenCommand(
330
+ `register-external-styles ${externalModuleName} styles`
331
+ );
332
+
333
+ // Run the build process
334
+ await test_app.runSealgenCommand("build");
335
+
336
+ // Verify the styles are included in the build
337
+ const styleEntrypointPath = path.join(
338
+ test_app.app_path,
339
+ "src/style-entrypoints/default.entrypoint.css"
340
+ );
341
+ const styleEntrypointContent = await fs.readFile(styleEntrypointPath, "utf-8");
342
+
343
+ // Check that the external CSS is imported
344
+ assert(
345
+ styleEntrypointContent.includes(
346
+ `@import "../../node_modules/${externalModuleName}/styles/external.css"`
347
+ ),
348
+ "External CSS should be imported in generated style entrypoint"
349
+ );
350
+
351
+ // Verify the CSS build output exists
352
+ const cssOutputPath = path.join(
353
+ test_app.app_path,
354
+ "public/dist/default.entrypoint.css"
355
+ );
356
+ assert(
357
+ await fs
358
+ .stat(cssOutputPath)
359
+ .then(() => true)
360
+ .catch(() => false),
361
+ "CSS should be generated"
362
+ );
363
+
364
+ await test_app.close();
365
+ }).timeout(100 * 1000);
366
+
367
+ it("registers both external controllers and styles and verifies complete build integration", async function () {
368
+ const test_app = await RealAppTest.init();
369
+ await ensureSealgenConfig(test_app);
370
+
371
+ // Create mock external modules
372
+ const controllerModuleName = "test-external-controllers";
373
+ const styleModuleName = "test-external-styles";
374
+
375
+ // Create controller module
376
+ await createMockExternalModule(test_app, controllerModuleName, "controllers", [
377
+ {
378
+ name: "combined-test.stimulus.ts",
379
+ content: `
380
+ import { Controller } from "stimulus";
381
+
382
+ export default class CombinedTestController extends Controller {
383
+ static targets = ["output"];
384
+
385
+ connect() {
386
+ console.log("Combined test controller connected!");
387
+ }
388
+ }
389
+ `,
390
+ },
391
+ ]);
392
+
393
+ // Create style module
394
+ await createMockExternalModule(test_app, styleModuleName, "styles", [
395
+ {
396
+ name: "combined-external.css",
397
+ content: `
398
+ .combined-external-style {
399
+ color: green;
400
+ background: yellow;
401
+ }
402
+ `,
403
+ },
404
+ ]);
405
+
406
+ // Register both external modules
407
+ await test_app.runSealgenCommand(
408
+ `register-external-controllers ${controllerModuleName} controllers`
409
+ );
410
+ await test_app.runSealgenCommand(`register-external-styles ${styleModuleName} styles`);
411
+
412
+ // Run the build process
413
+ await test_app.runSealgenCommand("build");
414
+
415
+ // Verify controllers are included
416
+ const controllersFilePath = path.join(test_app.app_path, "src/front/controllers.ts");
417
+ const controllersContent = await fs.readFile(controllersFilePath, "utf-8");
418
+
419
+ assert(
420
+ controllersContent.includes(
421
+ `import { default as CombinedTest } from "./../../node_modules/${controllerModuleName}/controllers/combined-test.stimulus.js"`
422
+ ),
423
+ "Combined test controller should be imported"
424
+ );
425
+ assert(
426
+ controllersContent.includes(`application.register("combined-test", CombinedTest);`),
427
+ "Combined test controller should be registered"
428
+ );
429
+
430
+ // Verify styles are included
431
+ const styleEntrypointPath = path.join(
432
+ test_app.app_path,
433
+ "src/style-entrypoints/default.entrypoint.css"
434
+ );
435
+ const styleEntrypointContent = await fs.readFile(styleEntrypointPath, "utf-8");
436
+
437
+ assert(
438
+ styleEntrypointContent.includes(
439
+ `@import "../../node_modules/${styleModuleName}/styles/combined-external.css"`
440
+ ),
441
+ "Combined external CSS should be imported"
442
+ );
443
+
444
+ // Verify build outputs exist
445
+ const bundlePath = path.join(test_app.app_path, "public/dist/bundle.js");
446
+ const cssOutputPath = path.join(
447
+ test_app.app_path,
448
+ "public/dist/default.entrypoint.css"
449
+ );
450
+
451
+ assert(
452
+ await fs
453
+ .stat(bundlePath)
454
+ .then(() => true)
455
+ .catch(() => false),
456
+ "Bundle should be generated"
457
+ );
458
+ assert(
459
+ await fs
460
+ .stat(cssOutputPath)
461
+ .then(() => true)
462
+ .catch(() => false),
463
+ "CSS should be generated"
464
+ );
465
+
466
+ await test_app.close();
467
+ }).timeout(100 * 1000);
468
+ });
469
+ });
@@ -0,0 +1,148 @@
1
+ import { promises as fs, existsSync, readFileSync } from "fs";
2
+ import { resolve, dirname } from "path";
3
+
4
+ import { formatWithPrettier } from "./utils/prettier.js";
5
+
6
+ interface PackageJson {
7
+ sealgen?: {
8
+ controllerDirs?: string[];
9
+ styleDirs?: string[];
10
+ [key: string]: unknown;
11
+ };
12
+ [key: string]: unknown;
13
+ }
14
+
15
+ function findParentProjectWithSealgen(): string {
16
+ let currentDir = process.cwd();
17
+
18
+ while (currentDir !== dirname(currentDir)) {
19
+ const packageJsonPath = resolve(currentDir, "package.json");
20
+
21
+ if (existsSync(packageJsonPath)) {
22
+ try {
23
+ const packageJsonContent = readFileSync(
24
+ packageJsonPath,
25
+ "utf-8"
26
+ );
27
+ const packageJson = JSON.parse(
28
+ packageJsonContent
29
+ ) as PackageJson;
30
+
31
+ // Check if this package.json has a sealgen configuration
32
+ if (packageJson.sealgen) {
33
+ return currentDir;
34
+ }
35
+ } catch (error) {
36
+ // If we can't read or parse the package.json, continue to parent
37
+ console.warn(
38
+ `Warning: Could not read package.json at ${packageJsonPath}:`,
39
+ error
40
+ );
41
+ }
42
+ }
43
+
44
+ currentDir = dirname(currentDir);
45
+ }
46
+
47
+ throw new Error(
48
+ "Could not find a parent project with sealgen configuration"
49
+ );
50
+ }
51
+
52
+ async function updatePackageJson(
53
+ projectDir: string,
54
+ moduleName: string,
55
+ subdirectory: string,
56
+ type: "controllerDirs" | "styleDirs"
57
+ ): Promise<void> {
58
+ const packageJsonPath = resolve(projectDir, "package.json");
59
+ const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8");
60
+ const packageJson = JSON.parse(packageJsonContent) as PackageJson;
61
+
62
+ // Initialize sealgen configuration if it doesn't exist
63
+ if (!packageJson.sealgen) {
64
+ packageJson.sealgen = {};
65
+ }
66
+
67
+ // Initialize the specific array if it doesn't exist
68
+ if (!packageJson.sealgen[type]) {
69
+ packageJson.sealgen[type] = [];
70
+ }
71
+
72
+ const pathToAdd = `node_modules/${moduleName}/${subdirectory}`;
73
+ const existingPaths = packageJson.sealgen[type] as string[];
74
+
75
+ // Check if the path is already present
76
+ if (existingPaths.includes(pathToAdd)) {
77
+ console.log(`${type} already contains: ${pathToAdd}`);
78
+ return;
79
+ }
80
+
81
+ // Add the new path
82
+ existingPaths.push(pathToAdd);
83
+
84
+ // Write back the updated package.json with proper formatting
85
+ const updatedContent = await formatWithPrettier(
86
+ JSON.stringify(packageJson, null, 2),
87
+ "json"
88
+ );
89
+
90
+ await fs.writeFile(packageJsonPath, updatedContent);
91
+ console.log(`Added ${pathToAdd} to ${type} in ${packageJsonPath}`);
92
+ }
93
+
94
+ export async function registerExternalControllers(
95
+ args: Record<string, string | boolean>
96
+ ): Promise<void> {
97
+ // Extract positional arguments from process.argv since yargs doesn't handle them for these commands
98
+ const moduleName = process.argv.at(3);
99
+ const subdirectory = process.argv.at(4);
100
+
101
+ if (!moduleName || !subdirectory) {
102
+ console.error(
103
+ "Usage: npx sealgen register-external-controllers <module-name> <subdirectory>"
104
+ );
105
+ process.exit(1);
106
+ }
107
+
108
+ try {
109
+ const projectDir = findParentProjectWithSealgen();
110
+ await updatePackageJson(
111
+ projectDir,
112
+ moduleName,
113
+ subdirectory,
114
+ "controllerDirs"
115
+ );
116
+ } catch (error) {
117
+ console.error("Error:", error instanceof Error ? error.message : error);
118
+ process.exit(1);
119
+ }
120
+ }
121
+
122
+ export async function registerExternalStyles(
123
+ args: Record<string, string | boolean>
124
+ ): Promise<void> {
125
+ // Extract positional arguments from process.argv since yargs doesn't handle them for these commands
126
+ const moduleName = process.argv.at(3);
127
+ const subdirectory = process.argv.at(4);
128
+
129
+ if (!moduleName || !subdirectory) {
130
+ console.error(
131
+ "Usage: npx sealgen register-external-styles <module-name> <subdirectory>"
132
+ );
133
+ process.exit(1);
134
+ }
135
+
136
+ try {
137
+ const projectDir = findParentProjectWithSealgen();
138
+ await updatePackageJson(
139
+ projectDir,
140
+ moduleName,
141
+ subdirectory,
142
+ "styleDirs"
143
+ );
144
+ } catch (error) {
145
+ console.error("Error:", error instanceof Error ? error.message : error);
146
+ process.exit(1);
147
+ }
148
+ }
@@ -12,6 +12,7 @@ export const default_handler: FieldHandler<
12
12
  { name, is_readonly, is_required },
13
13
  _,
14
14
  __,
15
+ ___,
15
16
  fields_var = `${toPascalCase(collection_name)}FormFields`
16
17
  ) => {
17
18
  const required_s = `${toPascalCase(
@@ -61,6 +61,11 @@ export type FieldHandler<
61
61
  field: ExtractedFieldInfo,
62
62
  importPath: (path: string) => string,
63
63
  defaults: Defaults,
64
+ all_other_handlers: Record<
65
+ string,
66
+ | FieldHandler<FieldHandlerResult, Partial<FieldHandlerResult>>
67
+ | undefined
68
+ >,
64
69
  fields_var?: string
65
70
  ) => Promise<Result>;
66
71
 
@@ -95,8 +100,10 @@ export async function getFieldHandler(
95
100
  field,
96
101
  importPath,
97
102
  {},
103
+ field_handlers,
98
104
  fields_var
99
105
  );
106
+
100
107
  const result = {
101
108
  ...defaults,
102
109
  ...((await field_handlers[field.type]?.(
@@ -104,6 +111,7 @@ export async function getFieldHandler(
104
111
  field,
105
112
  importPath,
106
113
  defaults,
114
+ field_handlers,
107
115
  fields_var
108
116
  )) || {}),
109
117
  };
@@ -30,7 +30,7 @@ export default async function extract_fields_from_collection(
30
30
  const c = new the_app().collections["${collection_name}"];
31
31
  const fields = [];
32
32
  for (const field_name in c.fields){
33
- const field = c.fields[field_name];
33
+ let field = c.fields[field_name];
34
34
  let type = field.typeName;
35
35
  let target = null;
36
36
  let referencing_collection = null;
@@ -39,7 +39,7 @@ for (const field_name in c.fields){
39
39
  let is_readonly = false;
40
40
  let is_required = field.required;
41
41
  let subfields = [];
42
- if(["derived-value", "cached-value"].includes(type)){
42
+ if(["derived-value", "cached-value", "settable-by"].includes(type)){
43
43
  type = field.virtual_field.typeName;
44
44
  }
45
45
  if(["derived-value", "cached-value", "reverse-single-reference"].includes(field.typeName)){
@@ -52,7 +52,7 @@ for (const field_name in c.fields){
52
52
  intermediary_field_that_points_there = field.intermediary_field_that_points_there
53
53
  }
54
54
  if( type == "structured-array" ) {
55
- subfields = Object.keys(field.subfields).map(key=>({name: key}));
55
+ subfields = Object.keys(field.subfields || field.virtual_field.subfields).map(key=>({name: key}));
56
56
  }
57
57
  fields.push({ name: field_name, type, target, referencing_collection, referencing_field, intermediary_field_that_points_there, is_readonly, is_required, subfields })
58
58
  }