create-next-hydra 0.2.1 → 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 (204) hide show
  1. package/README.md +156 -26
  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 +27 -15
  138. package/schema/selection-definition.json +206 -5
  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 -453
  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 -313
  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 -63
  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 -54
  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 -320
  166. package/dist/composition/schema.d.ts +0 -235
  167. package/dist/composition/schema.d.ts.map +0 -1
  168. package/dist/composition/schema.js +0 -115
  169. package/dist/composition/types.d.ts +0 -87
  170. package/dist/composition/types.d.ts.map +0 -1
  171. package/dist/composition/types.js +0 -6
  172. package/dist/composition/use.d.ts +0 -23
  173. package/dist/composition/use.d.ts.map +0 -1
  174. package/dist/composition/use.js +0 -182
  175. package/dist/composition/workspace.d.ts +0 -10
  176. package/dist/composition/workspace.d.ts.map +0 -1
  177. package/dist/composition/workspace.js +0 -208
  178. package/dist/constants.d.ts +0 -11
  179. package/dist/constants.d.ts.map +0 -1
  180. package/dist/constants.js +0 -38
  181. package/dist/fs-utils.d.ts +0 -9
  182. package/dist/fs-utils.d.ts.map +0 -1
  183. package/dist/fs-utils.js +0 -64
  184. package/dist/git.d.ts +0 -28
  185. package/dist/git.d.ts.map +0 -1
  186. package/dist/git.js +0 -107
  187. package/dist/instruction-renderer.d.ts +0 -17
  188. package/dist/instruction-renderer.d.ts.map +0 -1
  189. package/dist/instruction-renderer.js +0 -132
  190. package/dist/logger.d.ts +0 -13
  191. package/dist/logger.d.ts.map +0 -1
  192. package/dist/logger.js +0 -26
  193. package/dist/prompts.d.ts +0 -7
  194. package/dist/prompts.d.ts.map +0 -1
  195. package/dist/prompts.js +0 -47
  196. package/dist/sanitize.d.ts +0 -10
  197. package/dist/sanitize.d.ts.map +0 -1
  198. package/dist/sanitize.js +0 -23
  199. package/dist/scaffold.d.ts +0 -7
  200. package/dist/scaffold.d.ts.map +0 -1
  201. package/dist/scaffold.js +0 -325
  202. package/dist/version.d.ts +0 -2
  203. package/dist/version.d.ts.map +0 -1
  204. package/dist/version.js +0 -2
@@ -0,0 +1,55 @@
1
+ /*!
2
+ * Subscription lifecycle adapted from Effect's Parcel watcher:
3
+ * https://github.com/Effect-TS/effect/blob/d5e25b237f05670ee42b386cb40b2cb448fc11d7/packages/platform-node-shared/src/internal/fileSystem/parcelWatcher.ts
4
+ *
5
+ * MIT License
6
+ *
7
+ * Copyright (c) 2023 Effectful Technologies Inc
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in all
17
+ * copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ * SOFTWARE.
26
+ */
27
+ import { Cause, Context, Effect, FileSystem, Layer, Path, Schema, Stream } from "effect";
28
+ import type { Scope } from "effect";
29
+ import type { SourceInputs } from "./model.ts";
30
+ import { SourceInventory } from "./source-inventory.ts";
31
+ export interface SourceCoverage {
32
+ readonly root: string;
33
+ readonly files: readonly string[];
34
+ readonly inputs: SourceInputs | null;
35
+ }
36
+ declare const SourceWatchFailure_base: Schema.Class<SourceWatchFailure, Schema.TaggedStruct<"SourceWatchFailure", {
37
+ readonly diagnostic: Schema.Redacted<Schema.Unknown>;
38
+ readonly path: Schema.String;
39
+ readonly phase: Schema.Literals<readonly ["connect", "observe", "update"]>;
40
+ }>, Cause.YieldableError>;
41
+ export declare class SourceWatchFailure extends SourceWatchFailure_base {
42
+ get message(): string;
43
+ }
44
+ export interface SourceSubscription {
45
+ readonly invalidations: Stream.Stream<void, SourceWatchFailure>;
46
+ readonly update: (coverage: SourceCoverage) => Effect.Effect<void, SourceWatchFailure>;
47
+ }
48
+ declare const SourceChanges_base: Context.ServiceClass<SourceChanges, "create-next-hydra/SourceChanges", {
49
+ readonly open: (coverage: SourceCoverage) => Effect.Effect<SourceSubscription, SourceWatchFailure, Scope.Scope>;
50
+ }>;
51
+ export declare class SourceChanges extends SourceChanges_base {
52
+ static readonly layer: Layer.Layer<SourceChanges, never, FileSystem.FileSystem | Path.Path | SourceInventory>;
53
+ }
54
+ export {};
55
+ //# sourceMappingURL=source-changes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-changes.d.ts","sourceRoot":"","sources":["../src/source-changes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,EAEL,KAAK,EACL,OAAO,EACP,MAAM,EAEN,UAAU,EACV,KAAK,EAEL,IAAI,EAIJ,MAAM,EAEN,MAAM,EACP,MAAM,QAAQ,CAAC;AAChB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAGpC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,EACL,eAAe,EAGhB,MAAM,uBAAuB,CAAC;AAU/B,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAElC,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;CACtC;;;;;;AAED,qBAAa,kBAAmB,SAAQ,uBAOvC;IACC,IAAI,OAAO,WAEV;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;IAChE,QAAQ,CAAC,MAAM,EAAE,CACf,QAAQ,EAAE,cAAc,KACrB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;CAC9C;;mBAKkB,CACb,QAAQ,EAAE,cAAc,KACrB,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,kBAAkB,EAAE,KAAK,CAAC,KAAK,CAAC;;AAL3E,qBAAa,aAAc,SAAQ,kBAOG;IACpC,MAAM,CAAC,QAAQ,CAAC,KAAK,yFAyRnB;CACH"}
@@ -0,0 +1,225 @@
1
+ /*!
2
+ * Subscription lifecycle adapted from Effect's Parcel watcher:
3
+ * https://github.com/Effect-TS/effect/blob/d5e25b237f05670ee42b386cb40b2cb448fc11d7/packages/platform-node-shared/src/internal/fileSystem/parcelWatcher.ts
4
+ *
5
+ * MIT License
6
+ *
7
+ * Copyright (c) 2023 Effectful Technologies Inc
8
+ *
9
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ * of this software and associated documentation files (the "Software"), to deal
11
+ * in the Software without restriction, including without limitation the rights
12
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ * copies of the Software, and to permit persons to whom the Software is
14
+ * furnished to do so, subject to the following conditions:
15
+ *
16
+ * The above copyright notice and this permission notice shall be included in all
17
+ * copies or substantial portions of the Software.
18
+ *
19
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ * SOFTWARE.
26
+ */
27
+ import { Array as EffectArray, Cause, Context, Effect, Equivalence, FileSystem, Layer, Order, Path, Queue, Redacted, RegExp as EffectRegExp, Schema, ScopedRef, Stream, } from "effect";
28
+ import { isCanonicalSource } from "./file-policy.js";
29
+ import { SourceInventory, sourceInputMatcher, sourceWatchIgnores, } from "./source-inventory.js";
30
+ const sameControls = Equivalence.Array(Equivalence.Struct({
31
+ directory: Equivalence.String,
32
+ files: Equivalence.Array(Equivalence.String),
33
+ }));
34
+ export class SourceWatchFailure extends Schema.TaggedError()("SourceWatchFailure", {
35
+ diagnostic: Schema.Redacted(Schema.Unknown, { disallowJsonEncode: true }),
36
+ path: Schema.String,
37
+ phase: Schema.Literals(["connect", "observe", "update"]),
38
+ }) {
39
+ get message() {
40
+ return `Source observation failed during ${this.phase}: ${this.path}`;
41
+ }
42
+ }
43
+ export class SourceChanges extends Context.Service()("create-next-hydra/SourceChanges") {
44
+ static layer = Layer.effect(SourceChanges, Effect.gen(function* () {
45
+ const path = yield* Path.Path;
46
+ const fs = yield* FileSystem.FileSystem;
47
+ const inventory = yield* SourceInventory;
48
+ return SourceChanges.of({
49
+ open: Effect.fn("SourceChanges.open")(function* (initial) {
50
+ const root = path.resolve(initial.root);
51
+ const candidates = new Map();
52
+ const pending = yield* Queue.dropping(1);
53
+ const failure = (phase, diagnostic) => new SourceWatchFailure({
54
+ diagnostic,
55
+ path: root,
56
+ phase,
57
+ });
58
+ const matcher = (coverage) => {
59
+ const files = coverage.files.map((file) => path.resolve(root, file));
60
+ const selected = sourceInputMatcher(coverage.inputs);
61
+ return (file) => files.some((exact) => exact === file || exact.startsWith(`${file}${path.sep}`)) ||
62
+ selected(path.relative(root, file).split(path.sep).join("/"));
63
+ };
64
+ let matches = matcher(initial);
65
+ const outsideInputs = (coverage) => (coverage.inputs?.files ?? [])
66
+ .map((file) => path.resolve(root, file))
67
+ .filter((file) => {
68
+ const relative = path.relative(root, file);
69
+ return (relative === ".." ||
70
+ relative.startsWith(`..${path.sep}`) ||
71
+ path.isAbsolute(relative));
72
+ });
73
+ let externalFiles = outsideInputs(initial);
74
+ const withAncestors = (files, directory = root) => {
75
+ const paths = new Set();
76
+ const prefix = directory.endsWith(path.sep)
77
+ ? directory
78
+ : `${directory}${path.sep}`;
79
+ for (const file of files) {
80
+ let absolute = path.resolve(directory, file);
81
+ while (absolute !== directory && absolute.startsWith(prefix)) {
82
+ paths.add(absolute);
83
+ absolute = path.dirname(absolute);
84
+ }
85
+ }
86
+ return paths;
87
+ };
88
+ const capturedPaths = (coverage) => withAncestors([
89
+ ...coverage.files,
90
+ ...(coverage.inputs?.files ?? []),
91
+ ]);
92
+ let captured = capturedPaths(initial);
93
+ const visiblePaths = (phase) => inventory.list(root).pipe(Effect.map((files) => withAncestors(files)), Effect.mapError((error) => failure(phase, Redacted.make(error))));
94
+ let visible = yield* visiblePaths("connect");
95
+ // Load native code only when Watch is consumed, not for one-shot commands.
96
+ const parcel = yield* Effect.tryPromise({
97
+ catch: (error) => failure("connect", Redacted.make(error)),
98
+ try: async () => await import("@parcel/watcher"),
99
+ });
100
+ // Parcel has no cancellation signal. Acquire/release waits for setup
101
+ // before honoring interruption so a late subscription cannot leak.
102
+ const observe = (directory, receive, ignore) => Effect.acquireRelease(Effect.tryPromise({
103
+ catch: (error) => failure("connect", Redacted.make(error)),
104
+ try: async () => await parcel.subscribe(directory, (error, events) => {
105
+ if (error) {
106
+ Queue.failCauseUnsafe(pending, Cause.fail(failure("observe", Redacted.make(error))));
107
+ return;
108
+ }
109
+ // Do not short-circuit: retain every candidate in the batch.
110
+ let relevant = false;
111
+ for (const event of events) {
112
+ relevant = receive(event.path, event.type) || relevant;
113
+ }
114
+ if (relevant) {
115
+ Queue.offerUnsafe(pending, undefined);
116
+ }
117
+ }, { ignore }),
118
+ }), (subscription) => Effect.promise(async () => {
119
+ await subscription.unsubscribe();
120
+ }));
121
+ yield* observe(root, (file, type) => {
122
+ if (!matches(file)) {
123
+ // Linux can report only a directory when its contents arrive
124
+ // before recursive observation connects. Reconcile structural
125
+ // events through visibility without selecting unrelated paths.
126
+ return (type !== "update" &&
127
+ isCanonicalSource(path.relative(root, file).split(path.sep).join("/")));
128
+ }
129
+ // Remember explicit inputs at arrival, before coverage can narrow.
130
+ candidates.set(file, candidates.get(file) === true || captured.has(file));
131
+ return true;
132
+ }, sourceWatchIgnores);
133
+ const controlPaths = (phase) => Effect.gen(function* () {
134
+ const controls = [
135
+ ...(yield* inventory.controlFiles(root)),
136
+ ...externalFiles.map((file) => ({
137
+ directory: path.dirname(file),
138
+ files: [file],
139
+ })),
140
+ ];
141
+ const directories = new Map();
142
+ for (const control of controls) {
143
+ let { directory } = control;
144
+ // A configured ignore file may not have a parent directory yet.
145
+ // Watch its exact ancestor chain, without crawling sibling trees.
146
+ while (!(yield* fs.exists(directory))) {
147
+ directory = path.dirname(directory);
148
+ }
149
+ const files = directories.get(directory) ?? new Set();
150
+ for (const file of control.files) {
151
+ files.add(file);
152
+ }
153
+ directories.set(directory, files);
154
+ }
155
+ return EffectArray.sortWith([...directories].map(([directory, files]) => ({
156
+ directory,
157
+ files: EffectArray.sort(files, Order.String),
158
+ })), (control) => control.directory, Order.String);
159
+ }).pipe(Effect.mapError((error) => failure(phase, Redacted.make(error))));
160
+ let controlsChanged = false;
161
+ const connectControls = (controls) => Effect.forEach(controls, ({ directory, files }) => {
162
+ const paths = withAncestors(files, directory);
163
+ const allowed = [...paths]
164
+ .map((file) => EffectRegExp.escape(path.relative(directory, file).split(path.sep).join("/")))
165
+ .join("|");
166
+ // Observe exact controls without crawling Git objects or home directories.
167
+ return observe(directory, (file) => {
168
+ if (!paths.has(file)) {
169
+ return false;
170
+ }
171
+ controlsChanged = true;
172
+ if (externalFiles.some((input) => input === file ||
173
+ input.startsWith(`${file}${path.sep}`))) {
174
+ candidates.set(file, true);
175
+ }
176
+ return true;
177
+ }, [`!(${allowed})`]);
178
+ }, { discard: true }).pipe(Effect.as(controls));
179
+ const controls = yield* ScopedRef.fromAcquire(connectControls(yield* controlPaths("connect")));
180
+ const update = (coverage) => Effect.gen(function* () {
181
+ if (path.resolve(coverage.root) !== root) {
182
+ return yield* failure("update", Redacted.make("An observation subscription cannot change its source root"));
183
+ }
184
+ // The root remains connected. Callers broaden before preparation
185
+ // and narrow afterward; coverage updates are not source changes.
186
+ matches = matcher(coverage);
187
+ captured = capturedPaths(coverage);
188
+ // Keep last-known external artifacts connected while preparation
189
+ // is broken, so restoring a deleted artifact can repair Watch.
190
+ if (coverage.inputs !== null) {
191
+ const next = outsideInputs(coverage);
192
+ if (!Equivalence.Array(Equivalence.String)(externalFiles, next)) {
193
+ externalFiles = next;
194
+ const current = yield* controlPaths("update");
195
+ yield* ScopedRef.set(controls, connectControls(current));
196
+ }
197
+ }
198
+ });
199
+ const invalidations = Stream.fromQueue(pending).pipe(Stream.filterEffect(() => Effect.gen(function* () {
200
+ // Drain before yielding: events arriving during Git inspection
201
+ // stay queued for the next pass. Coalesce paths, not event history.
202
+ const batch = [...candidates];
203
+ candidates.clear();
204
+ if (controlsChanged) {
205
+ controlsChanged = false;
206
+ const current = yield* controlPaths("observe");
207
+ if (!sameControls(current, yield* ScopedRef.get(controls))) {
208
+ // Acquire the new subscriptions before releasing the old set.
209
+ yield* ScopedRef.set(controls, connectControls(current));
210
+ }
211
+ }
212
+ const current = yield* visiblePaths("observe");
213
+ // Any scan can discover a Git change before its native event
214
+ // arrives. Never consume that visibility change without signaling.
215
+ const relevant = batch.some(([file, explicit]) => explicit || visible.has(file) || current.has(file)) ||
216
+ [...visible].some((file) => !current.has(file) && matches(file)) ||
217
+ [...current].some((file) => !visible.has(file) && matches(file));
218
+ visible = current;
219
+ return relevant;
220
+ })));
221
+ return { invalidations, update };
222
+ }),
223
+ });
224
+ }));
225
+ }
@@ -0,0 +1,25 @@
1
+ import { Context, Effect, Layer, Path } from "effect";
2
+ import type { PlatformError } from "effect";
3
+ import { ChildProcessSpawner } from "effect/unstable/process";
4
+ import { InvalidComposition } from "./errors.ts";
5
+ import type { SourceInputs } from "./model.ts";
6
+ export declare const sourceWatchIgnores: string[];
7
+ export declare function sourceInputMatcher(inputs: SourceInputs | null): (file: string) => boolean;
8
+ export interface LocalEnvironmentFile {
9
+ readonly source: string;
10
+ readonly target: string;
11
+ }
12
+ export interface SourceControlFiles {
13
+ readonly directory: string;
14
+ readonly files: readonly string[];
15
+ }
16
+ declare const SourceInventory_base: Context.ServiceClass<SourceInventory, "create-next-hydra/SourceInventory", {
17
+ readonly controlFiles: (root: string) => Effect.Effect<readonly SourceControlFiles[], PlatformError.PlatformError | InvalidComposition>;
18
+ readonly list: (root: string) => Effect.Effect<readonly string[], PlatformError.PlatformError | InvalidComposition>;
19
+ readonly localEnvironment: (root: string) => Effect.Effect<readonly LocalEnvironmentFile[], PlatformError.PlatformError | InvalidComposition>;
20
+ }>;
21
+ export declare class SourceInventory extends SourceInventory_base {
22
+ static readonly layer: Layer.Layer<SourceInventory, InvalidComposition, Path.Path | ChildProcessSpawner.ChildProcessSpawner>;
23
+ }
24
+ export {};
25
+ //# sourceMappingURL=source-inventory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-inventory.d.ts","sourceRoot":"","sources":["../src/source-inventory.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,OAAO,EACP,MAAM,EACN,KAAK,EAEL,IAAI,EAEL,MAAM,QAAQ,CAAC;AAChB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAE5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAQjD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAK/C,eAAO,MAAM,kBAAkB,UAK9B,CAAC;AAEF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,YAAY,GAAG,IAAI,GAC1B,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAoC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;;2BAK0B,CACrB,IAAI,EAAE,MAAM,KACT,MAAM,CAAC,MAAM,CAChB,SAAS,kBAAkB,EAAE,EAC7B,aAAa,CAAC,aAAa,GAAG,kBAAkB,CACjD;mBACc,CACb,IAAI,EAAE,MAAM,KACT,MAAM,CAAC,MAAM,CAChB,SAAS,MAAM,EAAE,EACjB,aAAa,CAAC,aAAa,GAAG,kBAAkB,CACjD;+BAC0B,CACzB,IAAI,EAAE,MAAM,KACT,MAAM,CAAC,MAAM,CAChB,SAAS,oBAAoB,EAAE,EAC/B,aAAa,CAAC,aAAa,GAAG,kBAAkB,CACjD;;AApBL,qBAAa,eAAgB,SAAQ,oBAsBG;IACtC,MAAM,CAAC,QAAQ,CAAC,KAAK,wGAmNnB;CACH"}
@@ -0,0 +1,212 @@
1
+ import { Array as EffectArray, Config, Context, Effect, Layer, Order, Path, Stream, } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
+ import { InvalidComposition } from "./errors.js";
4
+ import { cacheDirectories, eligiblePackageFile, isEnvironmentFile, isCanonicalSource, } from "./file-policy.js";
5
+ import { relativeFile } from "./files.js";
6
+ import { packageManifestMatcher } from "./package-catalog.js";
7
+ // Prune recursive observation before reaching dependency trees or composed apps.
8
+ // Definitions remain observable; SourceChanges filters them by exact coverage.
9
+ export const sourceWatchIgnores = [
10
+ "**/.git",
11
+ "**/.workspace-composition*",
12
+ ...[...cacheDirectories].map((directory) => `**/${directory}`),
13
+ "workspaces/*/!(next-hydra.json)",
14
+ ];
15
+ export function sourceInputMatcher(inputs) {
16
+ const files = new Set(inputs?.files);
17
+ const excluded = new Set(inputs?.excluded);
18
+ const matchesManifest = packageManifestMatcher(inputs?.packagePatterns ?? []);
19
+ const ancestors = new Set();
20
+ for (const file of files) {
21
+ const parts = file.split("/");
22
+ for (let end = 1; end < parts.length; end += 1) {
23
+ ancestors.add(parts.slice(0, end).join("/"));
24
+ }
25
+ }
26
+ return (file) => {
27
+ if (!isCanonicalSource(file)) {
28
+ return false;
29
+ }
30
+ if (inputs === null || files.has(file) || ancestors.has(file)) {
31
+ return true;
32
+ }
33
+ // ShadCN includes explicitly name registry.json files. Package manifests
34
+ // and ignore rules can change discovery even outside the selected closure.
35
+ const name = file.split("/").at(-1);
36
+ if (name === "registry.json" ||
37
+ name === ".gitignore" ||
38
+ matchesManifest(file)) {
39
+ return true;
40
+ }
41
+ return (!excluded.has(file) &&
42
+ eligiblePackageFile(file) &&
43
+ inputs.packages.some((directory) => file === directory || file.startsWith(`${directory}/`)));
44
+ };
45
+ }
46
+ export class SourceInventory extends Context.Service()("create-next-hydra/SourceInventory") {
47
+ static layer = Layer.effect(SourceInventory, Effect.gen(function* () {
48
+ const processes = yield* ChildProcessSpawner.ChildProcessSpawner;
49
+ const path = yield* Path.Path;
50
+ const configHome = yield* Config.String("XDG_CONFIG_HOME").pipe(Config.withDefault(""), Effect.mapError(() => new InvalidComposition({
51
+ message: "Could not read XDG_CONFIG_HOME for Git source inventory",
52
+ })));
53
+ const defaultIgnore = configHome
54
+ ? path.join(configHome, "git/ignore")
55
+ : "~/.config/git/ignore";
56
+ const git = (root, args, accepted = [0]) => Effect.scoped(Effect.gen(function* () {
57
+ const process = yield* processes.spawn(ChildProcess.make("git", args, { cwd: root, stderr: "ignore" }));
58
+ const [output, code] = yield* Effect.all([
59
+ Stream.mkString(Stream.decodeText(process.stdout)),
60
+ process.exitCode,
61
+ ], { concurrency: "unbounded" });
62
+ if (!accepted.includes(code)) {
63
+ return yield* new InvalidComposition({
64
+ message: `Git source inventory failed (exit ${code})`,
65
+ });
66
+ }
67
+ return output;
68
+ }));
69
+ return SourceInventory.of({
70
+ controlFiles: Effect.fn("SourceInventory.controlFiles")(function* (root) {
71
+ const index = (yield* git(root, [
72
+ "rev-parse",
73
+ "--path-format=absolute",
74
+ "--git-path",
75
+ "index",
76
+ ])).trim();
77
+ const common = (yield* git(root, [
78
+ "rev-parse",
79
+ "--path-format=absolute",
80
+ "--git-common-dir",
81
+ ])).trim();
82
+ const directory = (yield* git(root, [
83
+ "rev-parse",
84
+ "--absolute-git-dir",
85
+ ])).trim();
86
+ const excludes = (yield* git(root, [
87
+ "config",
88
+ "--path",
89
+ "--default",
90
+ defaultIgnore,
91
+ "--get",
92
+ "core.excludesFile",
93
+ ])).trim();
94
+ // Git expands configured paths and evaluates conditional includes.
95
+ // Retain include targets even while empty/missing, without reading
96
+ // unrelated configuration values or interpreting config syntax.
97
+ const configuration = (yield* git(root, [
98
+ "config",
99
+ "--null",
100
+ "--path",
101
+ "--show-origin",
102
+ "--get-regexp",
103
+ "^(include|includeif\\..*)\\.path$",
104
+ ], [0, 1])).split("\0");
105
+ const controls = [
106
+ { directory: path.dirname(index), files: [index] },
107
+ {
108
+ directory,
109
+ files: [
110
+ path.join(directory, "HEAD"),
111
+ path.join(directory, "config.worktree"),
112
+ ],
113
+ },
114
+ {
115
+ directory: common,
116
+ files: [
117
+ path.join(common, "info/exclude"),
118
+ path.join(common, "config"),
119
+ ],
120
+ },
121
+ ];
122
+ // Git reports the platform's config paths even before they exist.
123
+ for (const variable of ["GIT_CONFIG_GLOBAL", "GIT_CONFIG_SYSTEM"]) {
124
+ const locations = yield* git(root, ["var", variable], [0, 1]);
125
+ for (const location of locations.split("\n").filter(Boolean)) {
126
+ const file = path.resolve(root, location);
127
+ controls.push({ directory: path.dirname(file), files: [file] });
128
+ }
129
+ }
130
+ for (let offset = 0; offset < configuration.length; offset += 2) {
131
+ const origin = configuration[offset];
132
+ const entry = configuration[offset + 1];
133
+ if (origin && entry) {
134
+ const include = entry.slice(entry.indexOf("\n") + 1);
135
+ const base = origin.startsWith("file:")
136
+ ? path.dirname(path.resolve(root, origin.slice(5)))
137
+ : root;
138
+ const file = path.resolve(base, include);
139
+ controls.push({ directory: path.dirname(file), files: [file] });
140
+ }
141
+ }
142
+ if (excludes) {
143
+ const file = path.resolve(root, excludes);
144
+ controls.push({ directory: path.dirname(file), files: [file] });
145
+ }
146
+ return controls;
147
+ }),
148
+ list: Effect.fn("SourceInventory.list")(function* (root) {
149
+ const inventory = yield* git(root, [
150
+ "ls-files",
151
+ "--cached",
152
+ "--others",
153
+ "--exclude-standard",
154
+ "-z",
155
+ ]);
156
+ const deleted = new Set((yield* git(root, ["ls-files", "--deleted", "-z"])).split("\0"));
157
+ return EffectArray.sort([...new Set(inventory.split("\0"))].filter((file) => file.length > 0 && !file.endsWith("/") && !deleted.has(file)), Order.String);
158
+ }),
159
+ localEnvironment: Effect.fn("SourceInventory.localEnvironment")(function* (root) {
160
+ const worktrees = yield* git(root, [
161
+ "worktree",
162
+ "list",
163
+ "--porcelain",
164
+ "-z",
165
+ ]);
166
+ const primary = worktrees
167
+ .split("\0")
168
+ .find((field) => field.startsWith("worktree "))
169
+ ?.slice(9);
170
+ if (!primary) {
171
+ return yield* new InvalidComposition({
172
+ message: "Git source inventory did not identify the primary worktree",
173
+ });
174
+ }
175
+ const selected = new Map();
176
+ for (const directory of new Set([primary, path.resolve(root)])) {
177
+ const ignored = yield* git(directory, [
178
+ "ls-files",
179
+ "--others",
180
+ "--ignored",
181
+ "--exclude-standard",
182
+ "-z",
183
+ "--",
184
+ ":(glob).env",
185
+ ":(glob).env.*",
186
+ ":(glob)apps/**/.env",
187
+ ":(glob)apps/**/.env.*",
188
+ ":(glob)packages/**/.env",
189
+ ":(glob)packages/**/.env.*",
190
+ ":(glob)tests/**/.env",
191
+ ":(glob)tests/**/.env.*",
192
+ ]);
193
+ for (const candidate of ignored.split("\0")) {
194
+ if (!candidate ||
195
+ !isEnvironmentFile(candidate) ||
196
+ candidate
197
+ .split("/")
198
+ .some((part) => part === ".git" || cacheDirectories.has(part))) {
199
+ continue;
200
+ }
201
+ const target = yield* relativeFile(candidate);
202
+ selected.set(target, {
203
+ source: path.join(directory, target),
204
+ target,
205
+ });
206
+ }
207
+ }
208
+ return EffectArray.sortWith(selected.values(), (file) => file.target, Order.String);
209
+ }),
210
+ });
211
+ }));
212
+ }
@@ -0,0 +1,4 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ import { InvalidComposition } from "./errors.ts";
3
+ export declare const findSourceRoot: (cwd: string) => Effect.Effect<string, InvalidComposition | import("effect/PlatformError").PlatformError, FileSystem.FileSystem | Path.Path>;
4
+ //# sourceMappingURL=source-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-root.d.ts","sourceRoot":"","sources":["../src/source-root.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,eAAO,MAAM,cAAc,8IAsBzB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ import { InvalidComposition } from "./errors.js";
3
+ export const findSourceRoot = Effect.fn("WorkspaceSources.findRoot")(function* (cwd) {
4
+ const fs = yield* FileSystem.FileSystem;
5
+ const path = yield* Path.Path;
6
+ let candidate = path.resolve(cwd);
7
+ while (true) {
8
+ if ((yield* fs.exists(path.join(candidate, "registry.json"))) &&
9
+ (yield* fs.exists(path.join(candidate, "pnpm-workspace.yaml")))) {
10
+ return candidate;
11
+ }
12
+ const parent = path.dirname(candidate);
13
+ if (parent === candidate) {
14
+ return yield* new InvalidComposition({
15
+ message: "Run workspace commands inside a source checkout containing registry.json and pnpm-workspace.yaml.",
16
+ });
17
+ }
18
+ candidate = parent;
19
+ }
20
+ });
@@ -0,0 +1,19 @@
1
+ import { Effect, FileSystem, Path, Ref } from "effect";
2
+ import { StagingRetained } from "./errors.ts";
3
+ declare const writer: unique symbol;
4
+ export interface RegistryInstallation {
5
+ readonly application: string;
6
+ readonly registry: string;
7
+ readonly control: string;
8
+ readonly [writer]: Ref.Ref<boolean>;
9
+ }
10
+ export interface Staging extends RegistryInstallation {
11
+ readonly root: string;
12
+ readonly inputs: string;
13
+ }
14
+ export declare const writing: (staging: RegistryInstallation) => Effect.Effect<void, never, never>;
15
+ export declare const stopped: (staging: RegistryInstallation) => Effect.Effect<void, never, never>;
16
+ export declare const withStaging: <A, E, R>(use: (staging: Staging) => Effect.Effect<A, E, R>) => Effect.Effect<A, StagingRetained | import("effect/PlatformError").PlatformError | E, FileSystem.FileSystem | Path.Path | R>;
17
+ export declare const withRegistryInstallation: <A, E, R>(application: string, use: (installation: RegistryInstallation) => Effect.Effect<A, E, R>) => Effect.Effect<A, StagingRetained | import("effect/PlatformError").PlatformError | E, FileSystem.FileSystem | Path.Path | R>;
18
+ export {};
19
+ //# sourceMappingURL=staging.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"staging.d.ts","sourceRoot":"","sources":["../src/staging.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,QAAQ,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,QAAA,MAAM,MAAM,eAA2B,CAAC;AAExC,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;CACrC;AACD,MAAM,WAAW,OAAQ,SAAQ,oBAAoB;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAGD,eAAO,MAAM,OAAO,GAAI,SAAS,oBAAoB,sCACrB,CAAC;AACjC,eAAO,MAAM,OAAO,GAAI,SAAS,oBAAoB,sCACpB,CAAC;AA+ClC,eAAO,MAAM,WAAW,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EACjC,KAAK,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,gIAC9B,CAAC;AAEtB,eAAO,MAAM,wBAAwB,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC,EAC9C,aAAa,MAAM,EACnB,KAAK,CAAC,YAAY,EAAE,oBAAoB,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,gIACnC,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { Effect, FileSystem, Path, Ref } from "effect";
2
+ import { StagingRetained } from "./errors.js";
3
+ const writer = Symbol("staging writer");
4
+ // Only the adapter participates in this private lifetime protocol.
5
+ export const writing = (staging) => Ref.set(staging[writer], true);
6
+ export const stopped = (staging) => Ref.set(staging[writer], false);
7
+ const withStorage = (use, application) => Effect.gen(function* () {
8
+ const fs = yield* FileSystem.FileSystem;
9
+ const path = yield* Path.Path;
10
+ return yield* Effect.acquireUseRelease(Effect.gen(function* () {
11
+ const root = yield* fs.makeTempDirectory({
12
+ prefix: "hydra-preparation-",
13
+ });
14
+ return {
15
+ application: application ?? path.join(root, "application"),
16
+ control: path.join(root, "control"),
17
+ inputs: path.join(root, "inputs"),
18
+ registry: path.join(root, "registry"),
19
+ root,
20
+ [writer]: yield* Ref.make(false),
21
+ };
22
+ }), (staging) => Effect.gen(function* () {
23
+ for (const directory of [
24
+ ...(application === undefined ? [staging.application] : []),
25
+ staging.inputs,
26
+ staging.registry,
27
+ staging.control,
28
+ ]) {
29
+ yield* fs
30
+ .makeDirectory(directory, { mode: 0o700 })
31
+ .pipe(Effect.uninterruptible);
32
+ }
33
+ return yield* use(staging);
34
+ }), (staging) => Effect.gen(function* () {
35
+ if (yield* Ref.get(staging[writer])) {
36
+ return yield* new StagingRetained({ directory: staging.root });
37
+ }
38
+ yield* fs.remove(staging.root, { recursive: true });
39
+ }));
40
+ });
41
+ export const withStaging = (use) => withStorage(use);
42
+ export const withRegistryInstallation = (application, use) => withStorage(use, application);