create-next-hydra 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.md +126 -27
  2. package/dist/add-command.d.ts +11 -0
  3. package/dist/add-command.d.ts.map +1 -0
  4. package/dist/add-command.js +75 -0
  5. package/dist/addition.d.ts +67 -0
  6. package/dist/addition.d.ts.map +1 -0
  7. package/dist/addition.js +422 -0
  8. package/dist/application-git.d.ts +20 -0
  9. package/dist/application-git.d.ts.map +1 -0
  10. package/dist/application-git.js +57 -0
  11. package/dist/baseline.d.ts +22 -0
  12. package/dist/baseline.d.ts.map +1 -0
  13. package/dist/baseline.js +90 -0
  14. package/dist/cli.js +5 -18
  15. package/dist/commands.d.ts +22 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +197 -0
  18. package/dist/composition-lint.d.ts +20 -0
  19. package/dist/composition-lint.d.ts.map +1 -0
  20. package/dist/composition-lint.js +288 -0
  21. package/dist/composition.d.ts +24 -0
  22. package/dist/composition.d.ts.map +1 -0
  23. package/dist/composition.js +420 -0
  24. package/dist/create-command.d.ts +21 -0
  25. package/dist/create-command.d.ts.map +1 -0
  26. package/dist/create-command.js +128 -0
  27. package/dist/demo-instrumentation.d.ts +5 -0
  28. package/dist/demo-instrumentation.d.ts.map +1 -0
  29. package/dist/demo-instrumentation.js +300 -0
  30. package/dist/environment.d.ts +7 -0
  31. package/dist/environment.d.ts.map +1 -0
  32. package/dist/environment.js +81 -0
  33. package/dist/errors.d.ts +156 -0
  34. package/dist/errors.d.ts.map +1 -0
  35. package/dist/errors.js +140 -0
  36. package/dist/file-policy.d.ts +6 -0
  37. package/dist/file-policy.d.ts.map +1 -0
  38. package/dist/file-policy.js +38 -0
  39. package/dist/files.d.ts +7 -0
  40. package/dist/files.d.ts.map +1 -0
  41. package/dist/files.js +66 -0
  42. package/dist/hosting.d.ts +5 -0
  43. package/dist/hosting.d.ts.map +1 -0
  44. package/dist/hosting.js +107 -0
  45. package/dist/index.d.ts +4 -6
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +17 -89
  48. package/dist/initialization.d.ts +14 -0
  49. package/dist/initialization.d.ts.map +1 -0
  50. package/dist/initialization.js +266 -0
  51. package/dist/lint-workspaces.d.ts +2 -0
  52. package/dist/lint-workspaces.d.ts.map +1 -0
  53. package/dist/lint-workspaces.js +62 -0
  54. package/dist/model.d.ts +143 -0
  55. package/dist/model.d.ts.map +1 -0
  56. package/dist/model.js +61 -0
  57. package/dist/package-catalog.d.ts +8 -0
  58. package/dist/package-catalog.d.ts.map +1 -0
  59. package/dist/package-catalog.js +61 -0
  60. package/dist/packages.d.ts +57 -0
  61. package/dist/packages.d.ts.map +1 -0
  62. package/dist/packages.js +237 -0
  63. package/dist/process-group.d.ts +5 -0
  64. package/dist/process-group.d.ts.map +1 -0
  65. package/dist/process-group.js +23 -0
  66. package/dist/reference-workspace-bindings.d.ts +38 -0
  67. package/dist/reference-workspace-bindings.d.ts.map +1 -0
  68. package/dist/reference-workspace-bindings.js +41 -0
  69. package/dist/reference-workspace.d.ts +6 -0
  70. package/dist/reference-workspace.d.ts.map +1 -0
  71. package/dist/reference-workspace.js +62 -0
  72. package/dist/registry.d.ts +187 -0
  73. package/dist/registry.d.ts.map +1 -0
  74. package/dist/registry.js +107 -0
  75. package/dist/runtime.d.ts +9 -0
  76. package/dist/runtime.d.ts.map +1 -0
  77. package/dist/runtime.js +22 -0
  78. package/dist/selection.d.ts +704 -0
  79. package/dist/selection.d.ts.map +1 -0
  80. package/dist/selection.js +424 -0
  81. package/dist/shadcn-protocol.d.ts +22 -0
  82. package/dist/shadcn-protocol.d.ts.map +1 -0
  83. package/dist/shadcn-protocol.js +13 -0
  84. package/dist/shadcn-worker.d.ts +2 -0
  85. package/dist/shadcn-worker.d.ts.map +1 -0
  86. package/dist/shadcn-worker.js +43 -0
  87. package/dist/shadcn.d.ts +22 -0
  88. package/dist/shadcn.d.ts.map +1 -0
  89. package/dist/shadcn.js +101 -0
  90. package/dist/source-changes.d.ts +55 -0
  91. package/dist/source-changes.d.ts.map +1 -0
  92. package/dist/source-changes.js +225 -0
  93. package/dist/source-inventory.d.ts +25 -0
  94. package/dist/source-inventory.d.ts.map +1 -0
  95. package/dist/source-inventory.js +212 -0
  96. package/dist/source-root.d.ts +4 -0
  97. package/dist/source-root.d.ts.map +1 -0
  98. package/dist/source-root.js +20 -0
  99. package/dist/staging.d.ts +19 -0
  100. package/dist/staging.d.ts.map +1 -0
  101. package/dist/staging.js +42 -0
  102. package/dist/tasks.d.ts +44 -0
  103. package/dist/tasks.d.ts.map +1 -0
  104. package/dist/tasks.js +137 -0
  105. package/dist/templates.d.ts +217 -0
  106. package/dist/templates.d.ts.map +1 -0
  107. package/dist/templates.js +161 -0
  108. package/dist/test-reference-workspace.d.ts +2 -0
  109. package/dist/test-reference-workspace.d.ts.map +1 -0
  110. package/dist/test-reference-workspace.js +6 -0
  111. package/dist/types.d.ts +2 -9
  112. package/dist/types.d.ts.map +1 -1
  113. package/dist/typescript-paths.d.ts +10 -0
  114. package/dist/typescript-paths.d.ts.map +1 -0
  115. package/dist/typescript-paths.js +52 -0
  116. package/dist/workspace-dependencies.d.ts +26 -0
  117. package/dist/workspace-dependencies.d.ts.map +1 -0
  118. package/dist/workspace-dependencies.js +478 -0
  119. package/dist/workspace-files.d.ts +24 -0
  120. package/dist/workspace-files.d.ts.map +1 -0
  121. package/dist/workspace-files.js +395 -0
  122. package/dist/workspace-lock.d.ts +12 -0
  123. package/dist/workspace-lock.d.ts.map +1 -0
  124. package/dist/workspace-lock.js +104 -0
  125. package/dist/workspace-snapshots.d.ts +24 -0
  126. package/dist/workspace-snapshots.d.ts.map +1 -0
  127. package/dist/workspace-snapshots.js +218 -0
  128. package/dist/workspace-sources.d.ts +27 -0
  129. package/dist/workspace-sources.d.ts.map +1 -0
  130. package/dist/workspace-sources.js +88 -0
  131. package/dist/workspace-state.d.ts +325 -0
  132. package/dist/workspace-state.d.ts.map +1 -0
  133. package/dist/workspace-state.js +571 -0
  134. package/dist/workspaces.d.ts +73 -0
  135. package/dist/workspaces.d.ts.map +1 -0
  136. package/dist/workspaces.js +396 -0
  137. package/package.json +25 -20
  138. package/schema/selection-definition.json +143 -4
  139. package/dist/clone.d.ts +0 -9
  140. package/dist/clone.d.ts.map +0 -1
  141. package/dist/clone.js +0 -19
  142. package/dist/composition/add.d.ts +0 -14
  143. package/dist/composition/add.d.ts.map +0 -1
  144. package/dist/composition/add.js +0 -484
  145. package/dist/composition/catalog.d.ts +0 -26
  146. package/dist/composition/catalog.d.ts.map +0 -1
  147. package/dist/composition/catalog.js +0 -317
  148. package/dist/composition/errors.d.ts +0 -5
  149. package/dist/composition/errors.d.ts.map +0 -1
  150. package/dist/composition/errors.js +0 -8
  151. package/dist/composition/format.d.ts +0 -6
  152. package/dist/composition/format.d.ts.map +0 -1
  153. package/dist/composition/format.js +0 -65
  154. package/dist/composition/install.d.ts +0 -16
  155. package/dist/composition/install.d.ts.map +0 -1
  156. package/dist/composition/install.js +0 -175
  157. package/dist/composition/packages.d.ts +0 -6
  158. package/dist/composition/packages.d.ts.map +0 -1
  159. package/dist/composition/packages.js +0 -55
  160. package/dist/composition/paths.d.ts +0 -4
  161. package/dist/composition/paths.d.ts.map +0 -1
  162. package/dist/composition/paths.js +0 -47
  163. package/dist/composition/planner.d.ts +0 -4
  164. package/dist/composition/planner.d.ts.map +0 -1
  165. package/dist/composition/planner.js +0 -366
  166. package/dist/composition/schema.d.ts +0 -294
  167. package/dist/composition/schema.d.ts.map +0 -1
  168. package/dist/composition/schema.js +0 -166
  169. package/dist/composition/types.d.ts +0 -111
  170. package/dist/composition/types.d.ts.map +0 -1
  171. package/dist/composition/types.js +0 -6
  172. package/dist/composition/typescript-paths.d.ts +0 -6
  173. package/dist/composition/typescript-paths.d.ts.map +0 -1
  174. package/dist/composition/typescript-paths.js +0 -157
  175. package/dist/composition/use.d.ts +0 -23
  176. package/dist/composition/use.d.ts.map +0 -1
  177. package/dist/composition/use.js +0 -187
  178. package/dist/composition/workspace.d.ts +0 -10
  179. package/dist/composition/workspace.d.ts.map +0 -1
  180. package/dist/composition/workspace.js +0 -210
  181. package/dist/constants.d.ts +0 -11
  182. package/dist/constants.d.ts.map +0 -1
  183. package/dist/constants.js +0 -39
  184. package/dist/fs-utils.d.ts +0 -9
  185. package/dist/fs-utils.d.ts.map +0 -1
  186. package/dist/fs-utils.js +0 -64
  187. package/dist/git.d.ts +0 -28
  188. package/dist/git.d.ts.map +0 -1
  189. package/dist/git.js +0 -107
  190. package/dist/instruction-renderer.d.ts +0 -17
  191. package/dist/instruction-renderer.d.ts.map +0 -1
  192. package/dist/instruction-renderer.js +0 -132
  193. package/dist/logger.d.ts +0 -13
  194. package/dist/logger.d.ts.map +0 -1
  195. package/dist/logger.js +0 -26
  196. package/dist/prompts.d.ts +0 -7
  197. package/dist/prompts.d.ts.map +0 -1
  198. package/dist/prompts.js +0 -54
  199. package/dist/sanitize.d.ts +0 -10
  200. package/dist/sanitize.d.ts.map +0 -1
  201. package/dist/sanitize.js +0 -110
  202. package/dist/scaffold.d.ts +0 -7
  203. package/dist/scaffold.d.ts.map +0 -1
  204. package/dist/scaffold.js +0 -330
  205. package/dist/version.d.ts +0 -2
  206. package/dist/version.d.ts.map +0 -1
  207. package/dist/version.js +0 -2
@@ -0,0 +1,396 @@
1
+ import { Array as EffectArray, Cause, Context, Effect, FileSystem, Layer, Order, Path, Redacted, Schema, Fiber, Queue, Ref, Result, Stream, } from "effect";
2
+ import { ChildProcessSpawner } from "effect/unstable/process";
3
+ import { existingWorkspace } from "./addition.js";
4
+ import { initializeApplicationGit } from "./application-git.js";
5
+ import { Composition } from "./composition.js";
6
+ import { planEnvironmentCopy, copyEnvironment } from "./environment.js";
7
+ import { DestinationNotEmpty, InvalidComposition, MaterializationFailed, SourceChanged, SnapshotUnavailable, WorkspaceRecoveryRequired, WorkspaceUninitialized, } from "./errors.js";
8
+ import { relativeFile, writeFile } from "./files.js";
9
+ import { inspectInitialization, listUnregisteredFiles, namedInitializationFiles, validatePreservedFiles, workspaceSetting, } from "./initialization.js";
10
+ import { WorkspaceDefinition } from "./model.js";
11
+ import { Shadcn } from "./shadcn.js";
12
+ import { SourceChanges, SourceWatchFailure } from "./source-changes.js";
13
+ import { SourceInventory } from "./source-inventory.js";
14
+ import { WorkspaceDependencies } from "./workspace-dependencies.js";
15
+ import { WorkspaceFiles } from "./workspace-files.js";
16
+ import { WorkspaceSnapshots, snapshotTarget } from "./workspace-snapshots.js";
17
+ import { WorkspaceSources } from "./workspace-sources.js";
18
+ import { WorkspaceState } from "./workspace-state.js";
19
+ export class Workspaces extends Context.Service()("create-next-hydra/Workspaces") {
20
+ static layer = Layer.effect(Workspaces, Effect.gen(function* () {
21
+ const sources = yield* WorkspaceSources;
22
+ const composition = yield* Composition;
23
+ const inventory = yield* SourceInventory;
24
+ const fs = yield* FileSystem.FileSystem;
25
+ const path = yield* Path.Path;
26
+ const state = yield* WorkspaceState;
27
+ const workspaceFiles = yield* WorkspaceFiles;
28
+ const dependencies = yield* WorkspaceDependencies;
29
+ const snapshots = yield* WorkspaceSnapshots;
30
+ const sourceChanges = yield* SourceChanges;
31
+ const processes = yield* ChildProcessSpawner.ChildProcessSpawner;
32
+ const shadcn = yield* Shadcn;
33
+ const initialize = Effect.fn("Workspaces.initialize")((request) => Effect.sync(() => {
34
+ const destination = path.resolve(request.destination);
35
+ const inspect = inspectInitialization(destination).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
36
+ return {
37
+ materialize: Effect.fn("FreshWorkspace.materialize")(function* (options) {
38
+ if (request.source.kind === "working-tree" &&
39
+ destination === path.resolve(request.source.root)) {
40
+ return yield* new InvalidComposition({
41
+ message: "Source and destination must differ",
42
+ });
43
+ }
44
+ yield* inspect;
45
+ const prepared = yield* sources.use(request.source, (source) => composition.prepare({
46
+ name: request.name,
47
+ selection: request.selection,
48
+ source,
49
+ }));
50
+ yield* inspect;
51
+ const { files } = prepared;
52
+ for (const file of files) {
53
+ if (yield* fs.exists(path.join(destination, file.target))) {
54
+ return yield* new DestinationNotEmpty({
55
+ directory: destination,
56
+ });
57
+ }
58
+ }
59
+ const environment = yield* planEnvironmentCopy(destination, files, options.environment === "copy-missing-local" &&
60
+ request.source.kind === "working-tree"
61
+ ? yield* inventory.localEnvironment(request.source.root)
62
+ : []).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
63
+ yield* fs
64
+ .makeDirectory(destination, { recursive: true })
65
+ .pipe(Effect.uninterruptible);
66
+ const completedFiles = [];
67
+ for (const file of files) {
68
+ yield* writeFile(destination, file, true).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path), Effect.mapError((error) => new MaterializationFailed({
69
+ completedFiles: [...completedFiles],
70
+ diagnostic: Redacted.make(error),
71
+ directory: destination,
72
+ failedFile: file.target,
73
+ })));
74
+ completedFiles.push(file.target);
75
+ }
76
+ const environmentFilesCreated = yield* copyEnvironment(destination, environment).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
77
+ const dependencyResult = yield* dependencies.reconcile(destination, files, options);
78
+ const git = options.git && options.git !== "skip"
79
+ ? yield* initializeApplicationGit(destination, options.git === "commit").pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, processes))
80
+ : undefined;
81
+ const result = {
82
+ ...dependencyResult,
83
+ destination,
84
+ environmentFilesCreated,
85
+ files: files.map((file) => file.target),
86
+ instructions: prepared.instructions,
87
+ origins: files.map(({ target, origin }) => ({
88
+ origin,
89
+ target,
90
+ })),
91
+ removedFiles: [],
92
+ };
93
+ return git === undefined ? result : { ...result, git };
94
+ }),
95
+ };
96
+ }));
97
+ const fresh = Effect.fn("Workspaces.fresh")((request) => initialize(request));
98
+ const named = Effect.fn("Workspaces.named")(function* ({ sourceRoot, name, }) {
99
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/u.test(name)) {
100
+ return yield* new InvalidComposition({
101
+ message: "Workspace name must contain 1–63 lowercase letters, digits or hyphens, beginning and ending with a letter or digit",
102
+ });
103
+ }
104
+ const destination = path.resolve(sourceRoot, "workspaces", name);
105
+ const definitionPath = path.join(destination, "next-hydra.json");
106
+ const readDefinition = Effect.gen(function* () {
107
+ const definition = yield* fs.readFileString(definitionPath);
108
+ const parsed = yield* Schema.decodeEffect(Schema.fromJsonString(WorkspaceDefinition))(definition, { onExcessProperty: "error" });
109
+ yield* validatePreservedFiles(parsed.preserve);
110
+ return {
111
+ definition,
112
+ port: parsed.development?.port,
113
+ preserve: parsed.preserve,
114
+ demoArchitecture: parsed.demo?.architecture,
115
+ selection: { addOns: parsed.addOns, providers: parsed.providers },
116
+ };
117
+ });
118
+ const operations = {
119
+ check: state
120
+ .withRead({ directory: destination, sourceRoot }, (observation) => Effect.gen(function* () {
121
+ if (observation.pending !== null &&
122
+ (observation.pending.kind !== "installation" ||
123
+ !observation.pending.stopped)) {
124
+ return yield* new WorkspaceRecoveryRequired({
125
+ directory: destination,
126
+ });
127
+ }
128
+ const { definition, port, preserve, selection, demoArchitecture, } = yield* readDefinition;
129
+ const prepared = yield* sources.use({ kind: "working-tree", root: sourceRoot }, (source) => composition.prepare({
130
+ name,
131
+ port,
132
+ selection,
133
+ source,
134
+ demoArchitecture,
135
+ }));
136
+ const preserved = yield* inspectInitialization(destination, definition);
137
+ const output = yield* namedInitializationFiles(prepared.files, preserved, preserve);
138
+ const files = output.filter((file) => file.target !== ".gitignore");
139
+ const changes = [
140
+ ...(yield* workspaceFiles.inspect(observation, files, preserve)),
141
+ ];
142
+ if (!preserved.has(".gitignore")) {
143
+ changes.push({ kind: "setting", target: ".gitignore" });
144
+ }
145
+ if ((yield* fs.readFileString(definitionPath)) !== definition) {
146
+ return yield* new SourceChanged({
147
+ paths: ["next-hydra.json"],
148
+ });
149
+ }
150
+ return {
151
+ changes,
152
+ destination,
153
+ initialized: observation.initialized,
154
+ ready: observation.initialized && changes.length === 0,
155
+ };
156
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path)), { allowIncomplete: true })
157
+ .pipe(Effect.withSpan("NamedWorkspace.check")),
158
+ diff: state
159
+ .withRead({ directory: destination, sourceRoot }, (observation) => Effect.gen(function* () {
160
+ if (!observation.initialized) {
161
+ return yield* new WorkspaceUninitialized({
162
+ directory: destination,
163
+ });
164
+ }
165
+ if (!observation.snapshot) {
166
+ return yield* new SnapshotUnavailable({
167
+ directory: destination,
168
+ });
169
+ }
170
+ const pending = observation.pending?.kind === "files"
171
+ ? observation.pending.files
172
+ : [];
173
+ const targets = [
174
+ ...new Set([
175
+ ...observation.entries.map((entry) => entry.target),
176
+ ...pending.map((entry) => entry.target),
177
+ ]),
178
+ ];
179
+ const owned = new Set([
180
+ ...targets,
181
+ ...observation.preserved,
182
+ ...pending.flatMap((entry) => entry.temporary === null ? [] : [entry.temporary]),
183
+ ]);
184
+ const unregisteredFiles = yield* listUnregisteredFiles(destination, owned);
185
+ const { files, conflicts } = yield* workspaceFiles.read(destination, targets);
186
+ const comparison = yield* snapshots.diff({ directory: destination, sourceRoot }, observation.snapshot.commit, files);
187
+ return {
188
+ ...comparison,
189
+ conflicts,
190
+ incomplete: observation.revision !== observation.snapshot.revision,
191
+ origins: observation.snapshot.origins.filter((file) => comparison.changes.some((change) => change.target === file.target)),
192
+ snapshot: observation.snapshot.commit,
193
+ unregisteredFiles: unregisteredFiles.filter((target) => snapshotTarget(target) && !conflicts.includes(target)),
194
+ };
195
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path)), { allowIncomplete: true })
196
+ .pipe(Effect.withSpan("NamedWorkspace.diff")),
197
+ explain: Effect.fn("NamedWorkspace.explain")(function* (target) {
198
+ const selectedTarget = target === undefined ? undefined : yield* relativeFile(target);
199
+ const { definition, port, selection, demoArchitecture } = yield* readDefinition;
200
+ const prepared = yield* sources.use({ kind: "working-tree", root: sourceRoot }, (source) => composition.prepare({
201
+ name,
202
+ port,
203
+ selection,
204
+ source,
205
+ demoArchitecture,
206
+ }));
207
+ const files = prepared.files
208
+ .filter((file) => !workspaceSetting(file.target) &&
209
+ (selectedTarget === undefined ||
210
+ file.target === selectedTarget))
211
+ .map(({ target: file, origin }) => ({ origin, target: file }));
212
+ if (selectedTarget !== undefined &&
213
+ workspaceSetting(selectedTarget) &&
214
+ (yield* fs.exists(path.join(destination, selectedTarget))) &&
215
+ (yield* fs.stat(path.join(destination, selectedTarget))).type ===
216
+ "File") {
217
+ files.push({
218
+ origin: {
219
+ kind: "workspace-setting",
220
+ source: `workspaces/${name}/${selectedTarget}`,
221
+ },
222
+ target: selectedTarget,
223
+ });
224
+ }
225
+ if (selectedTarget !== undefined && files.length === 0) {
226
+ return yield* new InvalidComposition({
227
+ message: `No selected composition file: ${selectedTarget}`,
228
+ });
229
+ }
230
+ if ((yield* fs.readFileString(definitionPath)) !== definition) {
231
+ return yield* new SourceChanged({ paths: ["next-hydra.json"] });
232
+ }
233
+ return { destination, files, sourceRoot: path.resolve(sourceRoot) };
234
+ }),
235
+ sync: Effect.fn("NamedWorkspace.sync")(function* (options, environmentCopied = Effect.void, observeInputs = () => Effect.succeed(false)) {
236
+ return yield* state.withWrite({ directory: destination, sourceRoot }, (access) => Effect.gen(function* () {
237
+ const { definition, port, preserve, selection, demoArchitecture, } = yield* readDefinition;
238
+ const inspect = inspectInitialization(destination, definition);
239
+ yield* dependencies.recover(access);
240
+ yield* workspaceFiles.recover(access);
241
+ yield* inspect;
242
+ const prepare = sources.use({ kind: "working-tree", root: sourceRoot }, (source) => composition.prepare({
243
+ name,
244
+ port,
245
+ selection,
246
+ source,
247
+ demoArchitecture,
248
+ }));
249
+ let prepared = yield* prepare;
250
+ // Watch may discover inputs beyond its connected roots. Subscribe
251
+ // and re-read them before applying any output from that discovery.
252
+ while (yield* observeInputs(prepared.inputs)) {
253
+ prepared = yield* prepare;
254
+ }
255
+ const preserved = yield* inspect;
256
+ const output = yield* namedInitializationFiles(prepared.files, preserved, preserve);
257
+ // Ignore rules are seeded once and become workspace-owned settings.
258
+ const settings = output.filter((file) => file.target === ".gitignore");
259
+ const files = output.filter((file) => file.target !== ".gitignore");
260
+ const environment = yield* planEnvironmentCopy(destination, files, options.environment === "copy-missing-local"
261
+ ? yield* inventory.localEnvironment(sourceRoot)
262
+ : []);
263
+ const applied = yield* workspaceFiles.apply(access, files, [
264
+ ...settings.map((file) => file.target),
265
+ ...environment.map((file) => file.target),
266
+ ], preserve);
267
+ for (const setting of settings) {
268
+ yield* access.initialize(setting.target, writeFile(destination, setting, true));
269
+ }
270
+ const environmentFilesCreated = yield* copyEnvironment(destination, environment, access.initialize);
271
+ yield* environmentCopied;
272
+ yield* inspect;
273
+ yield* applied.complete;
274
+ const dependencyState = yield* dependencies.reconcile(destination, files, options, access);
275
+ const observation = yield* access.observation;
276
+ const captured = yield* workspaceFiles.capture(destination, observation.entries);
277
+ const commit = yield* snapshots.capture({ directory: destination, sourceRoot }, captured, observation.snapshot?.commit);
278
+ // Reobserve against the same applied fingerprints before the
279
+ // receipt selects these immutable bytes as its baseline.
280
+ yield* workspaceFiles.capture(destination, observation.entries);
281
+ yield* access.publishSnapshot(commit, observation.revision);
282
+ return {
283
+ ...dependencyState,
284
+ destination,
285
+ environmentFilesCreated,
286
+ files: files.map((file) => file.target),
287
+ instructions: prepared.instructions,
288
+ origins: prepared.files
289
+ .filter((file) => workspaceSetting(file.target) === undefined)
290
+ .map(({ target, origin }) => ({ origin, target })),
291
+ recoveryEvidence: access.recoveryEvidence,
292
+ removedFiles: applied.removedFiles,
293
+ };
294
+ }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path)), options);
295
+ }),
296
+ };
297
+ return {
298
+ ...operations,
299
+ watch: (options) => Stream.unwrap(Effect.gen(function* () {
300
+ const coverage = (inputs) => ({
301
+ files: [`workspaces/${name}/next-hydra.json`],
302
+ inputs,
303
+ root: path.resolve(sourceRoot),
304
+ });
305
+ const subscription = yield* sourceChanges.open(coverage(null));
306
+ const pending = yield* Queue.dropping(1);
307
+ const copied = yield* Ref.make(false);
308
+ let observedExternal = new Set();
309
+ const collector = yield* subscription.invalidations.pipe(Stream.runForEach(() => Queue.offer(pending, undefined)), Effect.andThen(Effect.fail(new SourceWatchFailure({
310
+ diagnostic: Redacted.make("Observation ended unexpectedly"),
311
+ path: sourceRoot,
312
+ phase: "observe",
313
+ }))), Effect.forkScoped);
314
+ const synchronize = Effect.gen(function* () {
315
+ // Observe discoveries before reading any newly selected input.
316
+ // Failed preparation keeps this conservative scope for repair.
317
+ yield* subscription.update(coverage(null));
318
+ const inputs = yield* Ref.make(null);
319
+ const outcome = yield* operations
320
+ .sync({
321
+ ...options,
322
+ environment: (yield* Ref.get(copied))
323
+ ? "preserve"
324
+ : options.environment,
325
+ }, Ref.set(copied, true), (prepared) => Effect.gen(function* () {
326
+ yield* Ref.set(inputs, prepared);
327
+ yield* subscription.update(coverage(prepared));
328
+ const external = prepared.files.filter((file) => file.startsWith("../") || path.isAbsolute(file));
329
+ const discovered = external.some((file) => !observedExternal.has(file));
330
+ observedExternal = new Set(external);
331
+ if (discovered) {
332
+ yield* Ref.set(inputs, null);
333
+ yield* subscription.update(coverage(null));
334
+ }
335
+ return discovered;
336
+ }))
337
+ .pipe(Effect.matchCauseEffect({
338
+ onFailure: (cause) => {
339
+ if (Cause.hasDies(cause) ||
340
+ Cause.hasInterrupts(cause)) {
341
+ return Effect.failCause(cause);
342
+ }
343
+ const failure = Cause.findError(cause);
344
+ if (Result.isFailure(failure)) {
345
+ return Effect.failCause(failure.failure);
346
+ }
347
+ const error = failure.success;
348
+ if (error._tag === "SourceWatchFailure") {
349
+ return Effect.failCause(cause);
350
+ }
351
+ return Effect.succeed({
352
+ _tag: "RefreshFailed",
353
+ error,
354
+ });
355
+ },
356
+ onSuccess: (result) => Effect.succeed({
357
+ _tag: "Synchronized",
358
+ result,
359
+ }),
360
+ }));
361
+ yield* subscription.update(coverage(yield* Ref.get(inputs)));
362
+ return outcome;
363
+ });
364
+ const subsequent = Effect.gen(function* () {
365
+ yield* Queue.take(pending);
366
+ yield* Effect.sleep("50 millis");
367
+ yield* Queue.clear(pending);
368
+ return yield* synchronize;
369
+ });
370
+ return Stream.concat(Stream.fromEffect(synchronize), Stream.fromEffectRepeat(subsequent)).pipe(Stream.interruptWhen(Fiber.join(collector)));
371
+ })),
372
+ };
373
+ });
374
+ const discover = Effect.fn("Workspaces.discover")(function* (sourceRoot) {
375
+ const root = path.resolve(sourceRoot);
376
+ const references = [];
377
+ for (const file of yield* inventory.list(root)) {
378
+ const [directory, name, definition, extra] = file.split("/");
379
+ if (directory === "workspaces" &&
380
+ name &&
381
+ !name.startsWith(".") &&
382
+ definition === "next-hydra.json" &&
383
+ extra === undefined) {
384
+ references.push({ name, sourceRoot: root });
385
+ }
386
+ }
387
+ return EffectArray.sortWith(references, (reference) => reference.name, Order.String);
388
+ });
389
+ return Workspaces.of({
390
+ discover,
391
+ existing: ({ root }) => existingWorkspace(root).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path), Effect.provideService(Shadcn, shadcn), Effect.provideService(WorkspaceDependencies, dependencies)),
392
+ fresh,
393
+ named,
394
+ });
395
+ }));
396
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-next-hydra",
3
- "version": "0.3.0",
4
- "description": "CLI to scaffold a new next-hydra project",
3
+ "version": "0.4.1",
4
+ "description": "CLI to compose applications from the Next Hydra registry",
5
5
  "keywords": [
6
6
  "cli",
7
7
  "create-next-app",
@@ -23,6 +23,10 @@
23
23
  "types": "./dist/index.d.ts",
24
24
  "import": "./dist/index.js"
25
25
  },
26
+ "./reference-workspace": {
27
+ "types": "./dist/reference-workspace-bindings.d.ts",
28
+ "import": "./dist/reference-workspace-bindings.js"
29
+ },
26
30
  "./cli": {
27
31
  "import": "./dist/cli.js"
28
32
  }
@@ -37,24 +41,20 @@
37
41
  "schema"
38
42
  ],
39
43
  "dependencies": {
40
- "@clack/prompts": "^0.11.0",
41
- "commander": "^14.0.0",
44
+ "@effect/platform-node": "4.0.0-rc.117",
45
+ "@parcel/watcher": "^2.6.0",
46
+ "effect": "4.0.0-rc.117",
42
47
  "jsonc-parser": "^3.3.1",
48
+ "oxfmt": "0.61.0",
43
49
  "shadcn": "^4.16.2",
44
- "yaml": "^2.9.0",
45
- "zod": "^3.25.28"
50
+ "typescript": "npm:@typescript/typescript6@6.0.2",
51
+ "yaml": "^2.9.0"
46
52
  },
47
53
  "devDependencies": {
54
+ "@effect/vitest": "4.0.0-rc.117",
48
55
  "@types/node": "24.13.3",
49
- "effect": "4.0.0-rc.111",
50
- "typescript": "npm:@typescript/typescript6@6.0.2",
51
- "vitest": "4.1.11",
52
- "@repo/actions": "0.0.0",
53
- "@repo/auth-workos": "0.0.0",
54
- "@repo/auth-clerk": "0.0.0",
55
- "@repo/commerce": "0.0.0",
56
- "@repo/typescript-config": "0.0.0",
57
- "@repo/registration": "0.0.0"
56
+ "vitest": "5.0.1",
57
+ "@repo/typescript-config": "0.0.0"
58
58
  },
59
59
  "engines": {
60
60
  "node": "24.x"
@@ -64,12 +64,17 @@
64
64
  },
65
65
  "scripts": {
66
66
  "prebuild": "node scripts/clean-dist.js",
67
- "build": "tsc -p tsconfig.json",
68
- "pack": "npm pack",
69
- "publish:dry-run": "npm publish --access public --dry-run",
70
- "publish:public": "npm publish --access public",
67
+ "build": "tsc6 -p tsconfig.json",
68
+ "compose": "pnpm --workspace-root exec turbo run build --filter=create-next-hydra && node dist/cli.js compose",
69
+ "pack": "pnpm pack",
70
+ "publish:dry-run": "pnpm publish --access public --dry-run",
71
+ "publish:public": "pnpm publish --access public",
71
72
  "test": "NODE_ENV=test vitest run --exclude 'tests/e2e/**'",
73
+ "pretest:e2e": "pnpm run build",
72
74
  "test:e2e": "NODE_ENV=test vitest run tests/e2e",
73
- "typecheck": "pnpm --workspace-root exec tsc --noEmit --project packages/create-next-hydra/tsconfig.json"
75
+ "typecheck": "tsc6 --noEmit -p tsconfig.json && tsc6 -p tsconfig.tests.json",
76
+ "workspace:test": "node dist/test-reference-workspace.js",
77
+ "workspace:lint": "node dist/lint-workspaces.js",
78
+ "lint:staged": "pnpm run build && node dist/lint-workspaces.js --staged"
74
79
  }
75
80
  }