create-next-hydra 0.3.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +27 -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,422 @@
1
+ import { createHash } from "node:crypto";
2
+ import { Array as EffectArray, Effect, FileSystem, Order, Path, Redacted, Schema, } from "effect";
3
+ import { InvalidComposition } from "./errors.js";
4
+ import { relativeFile, writeFile } from "./files.js";
5
+ import { ManifestJson, registryDependencies } from "./packages.js";
6
+ import { acquireRegistry, registryInstructions } from "./registry.js";
7
+ import { providerAliases, registryIndex } from "./selection.js";
8
+ import { Shadcn } from "./shadcn.js";
9
+ import { withRegistryInstallation } from "./staging.js";
10
+ import { WorkspaceDependencies } from "./workspace-dependencies.js";
11
+ export class AdditionChanged extends Schema.TaggedError()("AdditionChanged", {}) {
12
+ message = "The registry or project changed after inspection. Inspect the addition again before applying it. No installation was started.";
13
+ }
14
+ export class AdditionConflict extends Schema.TaggedError()("AdditionConflict", { paths: Schema.Array(Schema.String) }) {
15
+ get message() {
16
+ return `Adding this item would replace local work: ${this.paths.join(", ")}. Explicit replacement permission is required.`;
17
+ }
18
+ }
19
+ export class AdditionFailed extends Schema.TaggedError()("AdditionFailed", {
20
+ diagnostic: Schema.Redacted(Schema.String),
21
+ directory: Schema.String,
22
+ phase: Schema.Literals(["registry", "packages"]),
23
+ }) {
24
+ get message() {
25
+ return `Addition failed during ${this.phase} installation in ${this.directory}. Project changes may remain; inspect them before retrying. No rollback was attempted.`;
26
+ }
27
+ }
28
+ const json = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
29
+ const digest = (bytes) => createHash("sha256").update(bytes).digest("hex");
30
+ function fileStatus(actual, expected) {
31
+ if (actual === null) {
32
+ return "create";
33
+ }
34
+ return actual === digest(expected) ? "identical" : "changed";
35
+ }
36
+ // Published selection identities are also recognizable in projects that do not
37
+ // keep a composition receipt. Custom providers are described by the acquired graph.
38
+ const knownProviders = new Map([
39
+ [
40
+ "next-hydra/search/algolia",
41
+ { alias: "@repo/search-provider", package: "@repo/search-algolia" },
42
+ ],
43
+ [
44
+ "next-hydra/auth/clerk",
45
+ { alias: "@repo/auth", package: "@repo/auth-clerk" },
46
+ ],
47
+ [
48
+ "next-hydra/auth/workos",
49
+ { alias: "@repo/auth", package: "@repo/auth-workos" },
50
+ ],
51
+ [
52
+ "next-hydra/cms/contentstack",
53
+ { alias: "@repo/cms", package: "@repo/cms-contentstack" },
54
+ ],
55
+ [
56
+ "next-hydra/cms/drupal",
57
+ { alias: "@repo/cms", package: "@repo/cms-drupal" },
58
+ ],
59
+ [
60
+ "next-hydra/commerce/commercetools",
61
+ {
62
+ alias: "@repo/commerce-provider",
63
+ package: "@repo/commerce-commercetools",
64
+ },
65
+ ],
66
+ ]);
67
+ const validateAddition = Effect.fn("ExistingWorkspace.validateAddition")(function* (index, reference) {
68
+ if (index.metadataByName.get(index.references.get(reference) ?? "")?.kind ===
69
+ "provider") {
70
+ return yield* new InvalidComposition({
71
+ message: "Add does not switch providers. Select providers when composing an application.",
72
+ });
73
+ }
74
+ for (const item of index.items.values()) {
75
+ const metadata = index.metadataByName.get(item.name);
76
+ if (item.meta?.composition !== undefined ||
77
+ metadata?.kind === "preset" ||
78
+ metadata?.kind === "recipe" ||
79
+ (metadata?.conditionalDependencies?.length ?? 0) > 0 ||
80
+ (metadata?.assets?.length ?? 0) > 0 ||
81
+ (metadata?.pnpmPatches?.length ?? 0) > 0) {
82
+ return yield* new InvalidComposition({
83
+ message: "Add installs files into an existing project; select templates, presets and composition recipes when composing the application.",
84
+ });
85
+ }
86
+ if (item.css ||
87
+ item.cssVars ||
88
+ item.tailwind ||
89
+ ("fonts" in item &&
90
+ Array.isArray(item.fonts) &&
91
+ item.fonts.length > 0) ||
92
+ "config" in item) {
93
+ return yield* new InvalidComposition({
94
+ message: "This registry item needs a configured package-local ShadCN installation; root addition cannot apply its configuration changes.",
95
+ });
96
+ }
97
+ }
98
+ });
99
+ function packageStatus(actual, expected) {
100
+ if (actual === undefined) {
101
+ return "create";
102
+ }
103
+ return expected === undefined || actual === expected
104
+ ? "identical"
105
+ : "changed";
106
+ }
107
+ const installedRequirements = Effect.fn("ExistingWorkspace.installedRequirements")(function* (index, manifest) {
108
+ const selections = new Map([...index.metadataByName.values()].map((metadata) => [
109
+ metadata.id,
110
+ metadata,
111
+ ]));
112
+ const assumptions = [];
113
+ const packages = [];
114
+ const matches = (id) => {
115
+ const selection = selections.get(id);
116
+ if (selection?.kind !== "provider" ||
117
+ !selection.slot ||
118
+ !selection.binding) {
119
+ const known = knownProviders.get(id);
120
+ if (!known) {
121
+ return undefined;
122
+ }
123
+ const actual = manifest.dependencies?.[known.alias];
124
+ return (actual === known.package ||
125
+ actual?.startsWith(`workspace:${known.package}@`) === true ||
126
+ actual?.startsWith(`npm:${known.package}@`) === true);
127
+ }
128
+ return (manifest.dependencies?.[providerAliases[selection.slot]] ===
129
+ selection.binding.specifier);
130
+ };
131
+ for (const metadata of selections.values()) {
132
+ for (const slot of ["auth", "cms", "commerce", "search"]) {
133
+ const requirement = metadata.providerSlots?.[slot];
134
+ const present = manifest.dependencies?.[providerAliases[slot]] !== undefined;
135
+ if ((requirement === "required" && !present) ||
136
+ (requirement === "forbidden" && present)) {
137
+ return yield* new InvalidComposition({
138
+ message: `${metadata.id} ${requirement === "required" ? "requires" : "forbids"} an installed ${slot} provider.`,
139
+ });
140
+ }
141
+ }
142
+ if (metadata.kind === "provider" && matches(metadata.id) !== true) {
143
+ return yield* new InvalidComposition({
144
+ message: "The requested registry graph requires a different installed provider. Add cannot switch providers.",
145
+ });
146
+ }
147
+ for (const required of metadata.compatibility?.requires ?? []) {
148
+ const match = matches(required);
149
+ if (match === false) {
150
+ return yield* new InvalidComposition({
151
+ message: "An add-on requires a different installed provider.",
152
+ });
153
+ }
154
+ if (match === undefined && selections.get(required)?.kind !== "add-on") {
155
+ assumptions.push("A registry requirement cannot be verified: this project has no authoritative selection record.");
156
+ }
157
+ }
158
+ for (const conflict of metadata.compatibility?.conflicts ?? []) {
159
+ const match = matches(conflict);
160
+ if (match === true || selections.get(conflict)?.kind === "add-on") {
161
+ return yield* new InvalidComposition({
162
+ message: "The requested registry graph conflicts with an installed provider or selected add-on.",
163
+ });
164
+ }
165
+ if (match === undefined) {
166
+ assumptions.push("A registry conflict cannot be ruled out: this project has no authoritative selection record.");
167
+ }
168
+ }
169
+ packages.push(...(metadata.packages ?? []));
170
+ for (const requirement of metadata.providerDependencies ?? []) {
171
+ const name = providerAliases[requirement.slot];
172
+ const specifier = manifest.dependencies?.[name];
173
+ if (!specifier) {
174
+ return yield* new InvalidComposition({
175
+ message: `The installed application has no ${requirement.slot} provider binding.`,
176
+ });
177
+ }
178
+ packages.push({
179
+ cwd: requirement.cwd,
180
+ name,
181
+ section: requirement.section,
182
+ specifier,
183
+ });
184
+ }
185
+ }
186
+ return { assumptions, packages };
187
+ });
188
+ export const existingWorkspace = Effect.fn("Workspaces.existing")(function* (root) {
189
+ const fs = yield* FileSystem.FileSystem;
190
+ const path = yield* Path.Path;
191
+ const shadcn = yield* Shadcn;
192
+ const dependencies = yield* WorkspaceDependencies;
193
+ const directory = path.resolve(root);
194
+ const observe = (target) => fs.readFile(path.join(directory, target)).pipe(Effect.map(digest), Effect.catchIf((error) => error.reason._tag === "NotFound", () => Effect.succeed(null)));
195
+ const inspect = Effect.fn("ExistingWorkspace.inspect")(function* (reference) {
196
+ const graph = yield* acquireRegistry(directory, { reference }).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path), Effect.provideService(Shadcn, shadcn));
197
+ const index = yield* registryIndex(graph.registry, graph.references);
198
+ yield* validateAddition(index, reference);
199
+ // A project manifest is required, but its contents never appear in inspection.
200
+ yield* fs.access(path.join(directory, "package.json"));
201
+ const web = yield* fs
202
+ .readFileString(path.join(directory, "apps/web/package.json"))
203
+ .pipe(Effect.flatMap(Schema.decodeEffect(ManifestJson)), Effect.catchIf((error) => error._tag === "PlatformError" && error.reason._tag === "NotFound", () => Effect.succeed({})));
204
+ const installed = yield* installedRequirements(index, web);
205
+ const environment = EffectArray.sort(new Set(graph.registry.items.flatMap((item) => Object.keys(item.envVars ?? {}))), Order.String);
206
+ const facts = new Map();
207
+ const files = [];
208
+ const packages = [];
209
+ const requirements = new Map();
210
+ const manifests = new Map();
211
+ for (const requirement of installed.packages) {
212
+ const target = requirement.cwd === "."
213
+ ? "package.json"
214
+ : `${yield* relativeFile(requirement.cwd)}/package.json`;
215
+ const key = `${target}:${requirement.section}:${requirement.name}`;
216
+ const existing = requirements.get(key);
217
+ if (existing !== undefined && existing !== requirement.specifier) {
218
+ return yield* new InvalidComposition({
219
+ message: `Conflicting package requirements for ${key}`,
220
+ });
221
+ }
222
+ if (existing === undefined) {
223
+ packages.push({ ...requirement, target });
224
+ }
225
+ requirements.set(key, requirement.specifier);
226
+ facts.set(target, yield* observe(target));
227
+ }
228
+ for (const item of graph.registry.items) {
229
+ for (const file of item.files ?? []) {
230
+ if (!file.target?.startsWith("~/") ||
231
+ file.content === undefined ||
232
+ !["registry:file", "registry:item"].includes(file.type)) {
233
+ return yield* new InvalidComposition({
234
+ message: "Add requires hydrated exact-copy files with explicit workspace-root targets.",
235
+ });
236
+ }
237
+ const target = yield* relativeFile(file.target);
238
+ if (files.some((existing) => existing.target === target)) {
239
+ return yield* new InvalidComposition({
240
+ message: `Multiple registry files claim ${target}`,
241
+ });
242
+ }
243
+ const current = yield* observe(target);
244
+ facts.set(target, current);
245
+ files.push({ status: fileStatus(current, file.content), target });
246
+ if (target === "package.json" || target.endsWith("/package.json")) {
247
+ manifests.set(target, file.content);
248
+ }
249
+ }
250
+ }
251
+ const packageChanges = [];
252
+ const rootRequirements = yield* registryDependencies(graph.registry.items);
253
+ for (const requirement of packages) {
254
+ const native = rootRequirements.find((entry) => entry.name === requirement.name);
255
+ if (requirement.target === "package.json" &&
256
+ native?.specifier !== undefined &&
257
+ native.specifier !== requirement.specifier) {
258
+ return yield* new InvalidComposition({
259
+ message: `Conflicting root dependency requirements for ${requirement.name}`,
260
+ });
261
+ }
262
+ }
263
+ const rootManifest = yield* Schema.decodeEffect(ManifestJson)(manifests.get("package.json") ??
264
+ (yield* fs.readFileString(path.join(directory, "package.json"))));
265
+ for (const requirement of rootRequirements) {
266
+ const actual = rootManifest.dependencies?.[requirement.name] ??
267
+ rootManifest.devDependencies?.[requirement.name] ??
268
+ rootManifest.optionalDependencies?.[requirement.name] ??
269
+ rootManifest.peerDependencies?.[requirement.name];
270
+ packageChanges.push({
271
+ name: requirement.name,
272
+ section: requirement.section,
273
+ status: packageStatus(actual, requirement.specifier),
274
+ target: "package.json",
275
+ });
276
+ }
277
+ for (const requirement of packages) {
278
+ const manifest = yield* Schema.decodeEffect(ManifestJson)(manifests.get(requirement.target) ??
279
+ (yield* fs.readFileString(path.join(directory, requirement.target))));
280
+ const actual = manifest[requirement.section]?.[requirement.name];
281
+ packageChanges.push({
282
+ name: requirement.name,
283
+ section: requirement.section,
284
+ status: packageStatus(actual, requirement.specifier),
285
+ target: requirement.target,
286
+ });
287
+ }
288
+ for (const target of [
289
+ "package.json",
290
+ "components.json",
291
+ "apps/web/package.json",
292
+ // These are the native adapter's possible environment targets. Bind their
293
+ // presence and content to inspection without putting values in the report.
294
+ ...(environment.length
295
+ ? [".env.local", ".env", ".env.development.local", ".env.development"]
296
+ : []),
297
+ ]) {
298
+ facts.set(target, yield* observe(target));
299
+ }
300
+ const precondition = digest(yield* json({
301
+ artifacts: graph.registry.items,
302
+ directory,
303
+ facts: [...facts],
304
+ reference,
305
+ }));
306
+ return {
307
+ facts,
308
+ graph,
309
+ inspection: {
310
+ assumptions: installed.assumptions,
311
+ environment,
312
+ files,
313
+ packages: packageChanges,
314
+ precondition,
315
+ },
316
+ needsInstall: packages.length > 0 ||
317
+ rootRequirements.length > 0 ||
318
+ manifests.size > 0 ||
319
+ files.some(({ target }) => [
320
+ "pnpm-workspace.yaml",
321
+ "pnpm-lock.yaml",
322
+ ".npmrc",
323
+ ".pnpmfile.cjs",
324
+ ].includes(target)),
325
+ packages,
326
+ };
327
+ });
328
+ const inspectAdd = Effect.fn("ExistingWorkspace.inspectAdd")(function* (request) {
329
+ return (yield* inspect(request.reference)).inspection;
330
+ });
331
+ const add = Effect.fn("ExistingWorkspace.add")(function* (request) {
332
+ const checked = yield* inspect(request.reference);
333
+ if (request.expected !== undefined &&
334
+ request.expected !== checked.inspection.precondition) {
335
+ return yield* new AdditionChanged();
336
+ }
337
+ const conflicts = [
338
+ ...checked.inspection.files,
339
+ ...checked.inspection.packages,
340
+ ]
341
+ .filter((file) => file.status === "changed")
342
+ .map((file) => file.target);
343
+ if (!request.overwrite && conflicts.length) {
344
+ return yield* new AdditionConflict({ paths: conflicts });
345
+ }
346
+ yield* withRegistryInstallation(directory, (installation) => Effect.gen(function* () {
347
+ const artifacts = new Map(checked.graph.registry.items.map((item, ordinal) => [
348
+ item.name,
349
+ path.join(installation.registry, `${ordinal}.json`),
350
+ ]));
351
+ for (const [ordinal, item] of checked.graph.registry.items.entries()) {
352
+ const registryReferences = [];
353
+ for (const reference of item.registryDependencies ?? []) {
354
+ const dependency = artifacts.get(checked.graph.references.get(reference) ?? reference);
355
+ if (!dependency) {
356
+ return yield* new InvalidComposition({
357
+ message: "Registry dependency was not acquired.",
358
+ });
359
+ }
360
+ registryReferences.push(dependency);
361
+ }
362
+ yield* writeFile(installation.registry, {
363
+ content: new TextEncoder().encode(yield* json({
364
+ ...item,
365
+ docs: undefined,
366
+ registryDependencies: registryReferences,
367
+ })),
368
+ mode: 0o600,
369
+ target: `${ordinal}.json`,
370
+ });
371
+ }
372
+ for (const [target, value] of checked.facts) {
373
+ if ((yield* observe(target)) !== value) {
374
+ return yield* new AdditionChanged();
375
+ }
376
+ }
377
+ yield* shadcn
378
+ .install(installation, [...artifacts.values()], request.overwrite)
379
+ .pipe(Effect.mapError((error) => new AdditionFailed({
380
+ diagnostic: Redacted.make(String(error)),
381
+ directory,
382
+ phase: "registry",
383
+ })));
384
+ })).pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, path));
385
+ yield* Effect.gen(function* () {
386
+ // Read after native installation so its manifest edits are retained.
387
+ for (const requirement of checked.packages) {
388
+ const target = path.join(directory, requirement.target);
389
+ const manifest = yield* Schema.decodeEffect(ManifestJson)(yield* fs.readFileString(target));
390
+ const actual = manifest[requirement.section]?.[requirement.name];
391
+ if (actual !== undefined &&
392
+ actual !== requirement.specifier &&
393
+ !request.overwrite) {
394
+ return yield* new AdditionConflict({ paths: [requirement.target] });
395
+ }
396
+ const updated = {
397
+ ...manifest,
398
+ [requirement.section]: {
399
+ ...manifest[requirement.section],
400
+ [requirement.name]: requirement.specifier,
401
+ },
402
+ };
403
+ yield* fs
404
+ .writeFileString(target, `${yield* Schema.encodeEffect(ManifestJson)(updated)}\n`)
405
+ .pipe(Effect.uninterruptible);
406
+ }
407
+ }).pipe(Effect.mapError((error) => new AdditionFailed({
408
+ diagnostic: Redacted.make(String(error)),
409
+ directory,
410
+ phase: "packages",
411
+ })));
412
+ if (checked.needsInstall) {
413
+ yield* dependencies.install(directory);
414
+ }
415
+ return {
416
+ directory,
417
+ files: checked.inspection.files,
418
+ instructions: registryInstructions(checked.graph.registry.items),
419
+ };
420
+ });
421
+ return { add, inspectAdd };
422
+ });
@@ -0,0 +1,20 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { ChildProcessSpawner } from "effect/unstable/process";
3
+ declare const ApplicationGitFailed_base: Schema.Class<ApplicationGitFailed, Schema.TaggedStruct<"ApplicationGitFailed", {
4
+ readonly diagnostic: Schema.Redacted<Schema.Unknown>;
5
+ readonly directory: Schema.String;
6
+ }>, import("effect/Cause").YieldableError>;
7
+ export declare class ApplicationGitFailed extends ApplicationGitFailed_base {
8
+ get message(): string;
9
+ }
10
+ export declare const initializeApplicationGit: (directory: string, commit: boolean) => Effect.Effect<{
11
+ committed: boolean;
12
+ initialized: boolean;
13
+ warning?: undefined;
14
+ } | {
15
+ committed: false;
16
+ initialized: boolean;
17
+ warning: string;
18
+ }, ApplicationGitFailed, ChildProcessSpawner.ChildProcessSpawner>;
19
+ export {};
20
+ //# sourceMappingURL=application-git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"application-git.d.ts","sourceRoot":"","sources":["../src/application-git.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAY,MAAM,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;;;;;AAI5E,qBAAa,oBAAqB,SAAQ,yBAGzC;IACC,IAAI,OAAO,WAEV;CACF;AAED,eAAO,MAAM,wBAAwB;;;;;;;;iEAuEnC,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { Effect, Redacted, Schema } from "effect";
2
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
3
+ import { confirmStopped, groupExists } from "./process-group.js";
4
+ export class ApplicationGitFailed extends Schema.TaggedError()("ApplicationGitFailed", { diagnostic: Schema.Redacted(Schema.Unknown), directory: Schema.String }) {
5
+ get message() {
6
+ return `Git initialization failed in ${this.directory}. The composed application remains available for inspection.`;
7
+ }
8
+ }
9
+ export const initializeApplicationGit = Effect.fn("Workspaces.initializeApplicationGit")(function* (directory, commit) {
10
+ const processes = yield* ChildProcessSpawner.ChildProcessSpawner;
11
+ const git = (args) => Effect.scoped(Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
12
+ const handle = yield* processes.spawn(ChildProcess.make("git", args, {
13
+ cwd: directory,
14
+ forceKillAfter: "100 millis",
15
+ stderr: "ignore",
16
+ stdin: "ignore",
17
+ stdout: "ignore",
18
+ }));
19
+ return yield* restore(Effect.gen(function* () {
20
+ const code = yield* handle.exitCode;
21
+ if (yield* groupExists(handle.pid)) {
22
+ return yield* new ApplicationGitFailed({
23
+ diagnostic: Redacted.make("Git left an active process"),
24
+ directory,
25
+ });
26
+ }
27
+ return code;
28
+ })).pipe(Effect.onExit(() => confirmStopped(handle).pipe(Effect.onExit(() => handle.unref.pipe(Effect.asVoid)))));
29
+ }))).pipe(Effect.mapError((error) => new ApplicationGitFailed({
30
+ diagnostic: Redacted.make(error),
31
+ directory,
32
+ })));
33
+ if ((yield* git(["init"])) !== 0) {
34
+ return yield* new ApplicationGitFailed({
35
+ diagnostic: Redacted.make("git init failed"),
36
+ directory,
37
+ });
38
+ }
39
+ if (!commit) {
40
+ return { committed: false, initialized: true };
41
+ }
42
+ if ((yield* git(["add", "-A"])) !== 0) {
43
+ return yield* new ApplicationGitFailed({
44
+ diagnostic: Redacted.make("git add failed"),
45
+ directory,
46
+ });
47
+ }
48
+ const committed = (yield* git(["commit", "-m", "Initial commit"])) === 0;
49
+ if (committed) {
50
+ return { committed, initialized: true };
51
+ }
52
+ return {
53
+ committed,
54
+ initialized: true,
55
+ warning: "Git was initialized, but the initial commit failed. Check your Git identity and hooks, then commit from the project directory.",
56
+ };
57
+ });
@@ -0,0 +1,22 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { InvalidComposition } from "./errors.ts";
3
+ export declare const applicationIgnoreRules = "node_modules/\n.next/\n.turbo/\n.env\n.env.*\n!.env.example\n*.tsbuildinfo\n";
4
+ export declare function normalizeApplicationName(name: string): string;
5
+ export declare const yamlDocument: <A>(bytes: Uint8Array<ArrayBufferLike>, target: string, schema: Schema.Decoder<A, never>) => Effect.Effect<{
6
+ data: A;
7
+ document: import("yaml").Document.Parsed<import("yaml").Alias.Parsed, true> | import("yaml").Document.Parsed<import("yaml").Scalar.Parsed, true> | import("yaml").Document.Parsed<import("yaml").YAMLMap.Parsed<import("yaml").ParsedNode, import("yaml").ParsedNode | null>, true> | import("yaml").Document.Parsed<import("yaml").YAMLSeq.Parsed<import("yaml").ParsedNode>, true>;
8
+ }, InvalidComposition | Schema.SchemaError, never>;
9
+ export declare const applicationWorkspace: (settings: {
10
+ data: {
11
+ readonly packages: readonly string[];
12
+ };
13
+ document: import("yaml").Document.Parsed<import("yaml").Alias.Parsed, true> | import("yaml").Document.Parsed<import("yaml").Scalar.Parsed, true> | import("yaml").Document.Parsed<import("yaml").YAMLMap.Parsed<import("yaml").ParsedNode, import("yaml").ParsedNode | null>, true> | import("yaml").Document.Parsed<import("yaml").YAMLSeq.Parsed<import("yaml").ParsedNode>, true>;
14
+ }, lockSource: Uint8Array<ArrayBufferLike>, targets: ReadonlySet<string>, patches: ReadonlyMap<string, string>) => Effect.Effect<Map<string, NodeJS.NonSharedUint8Array>, InvalidComposition | Schema.SchemaError, never>;
15
+ export declare const readWorkspaceSettings: (source: Uint8Array) => Effect.Effect<{
16
+ data: {
17
+ readonly packages: readonly string[];
18
+ };
19
+ document: import("yaml").Document.Parsed<import("yaml").Alias.Parsed, true> | import("yaml").Document.Parsed<import("yaml").Scalar.Parsed, true> | import("yaml").Document.Parsed<import("yaml").YAMLMap.Parsed<import("yaml").ParsedNode, import("yaml").ParsedNode | null>, true> | import("yaml").Document.Parsed<import("yaml").YAMLSeq.Parsed<import("yaml").ParsedNode>, true>;
20
+ }, InvalidComposition | Schema.SchemaError, never>;
21
+ export declare const applicationManifest: (source: Uint8Array<ArrayBufferLike>, name: string, browserTests: boolean) => Effect.Effect<NodeJS.NonSharedUint8Array, InvalidComposition | Schema.SchemaError, never>;
22
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,eAAO,MAAM,sBAAsB,iFAOlC,CAAC;AAEF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAW7D;AAsBD,eAAO,MAAM,YAAY,GAAoD,CAAC;;;kDAoB5E,CAAC;AAEH,eAAO,MAAM,oBAAoB;;;;;yNAqC/B,CAAC;AAEH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,UAAU;;;;;kDACQ,CAAC;AAEjE,eAAO,MAAM,mBAAmB,yKA6B/B,CAAC"}
@@ -0,0 +1,90 @@
1
+ import { Effect, Schema } from "effect";
2
+ import { parseDocument } from "yaml";
3
+ import { InvalidComposition } from "./errors.js";
4
+ import { applicationPackagePatterns } from "./package-catalog.js";
5
+ export const applicationIgnoreRules = `node_modules/
6
+ .next/
7
+ .turbo/
8
+ .env
9
+ .env.*
10
+ !.env.example
11
+ *.tsbuildinfo
12
+ `;
13
+ export function normalizeApplicationName(name) {
14
+ return (name
15
+ .trim()
16
+ .toLowerCase()
17
+ .replaceAll(/[^a-z0-9._-]+/gu, "-")
18
+ .replaceAll(/-+/gu, "-")
19
+ .replace(/^[._-]+/u, "")
20
+ .slice(0, 214)
21
+ .replace(/[._-]+$/u, "") || "application");
22
+ }
23
+ const RootManifest = Schema.fromJsonString(Schema.Struct({
24
+ devDependencies: Schema.Record(Schema.String, Schema.String),
25
+ engines: Schema.optionalKey(Schema.Record(Schema.String, Schema.String)),
26
+ packageManager: Schema.optionalKey(Schema.String),
27
+ }));
28
+ const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown, { space: 2 }));
29
+ const WorkspaceSettings = Schema.Struct({
30
+ packages: Schema.Array(Schema.String),
31
+ });
32
+ const Lockfile = Schema.Struct({
33
+ importers: Schema.Record(Schema.String, Schema.Unknown),
34
+ patchedDependencies: Schema.optionalKey(Schema.Record(Schema.String, Schema.Unknown)),
35
+ });
36
+ export const yamlDocument = Effect.fn("Composition.yamlDocument")(function* (bytes, target, schema) {
37
+ const document = yield* Effect.try({
38
+ catch: () => new InvalidComposition({ message: `Invalid YAML: ${target}` }),
39
+ try: () => parseDocument(new TextDecoder().decode(bytes)),
40
+ });
41
+ if (document.errors.length > 0) {
42
+ return yield* new InvalidComposition({
43
+ message: `Invalid YAML: ${target}`,
44
+ });
45
+ }
46
+ const decode = yield* Effect.try({
47
+ catch: () => new InvalidComposition({ message: `Invalid YAML: ${target}` }),
48
+ try: () => Schema.decodeUnknownEffect(schema)(document.toJS()),
49
+ });
50
+ const data = yield* decode;
51
+ return { data, document };
52
+ });
53
+ export const applicationWorkspace = Effect.fn("Composition.applicationWorkspace")(function* (settings, lockSource, targets, patches) {
54
+ settings.document.set("packages", applicationPackagePatterns(settings.data.packages, targets));
55
+ settings.document.set("patchedDependencies", Object.fromEntries(patches));
56
+ const lock = yield* yamlDocument(lockSource, "pnpm-lock.yaml", Lockfile);
57
+ lock.document.set("importers", Object.fromEntries(Object.entries(lock.data.importers).filter(([cwd]) => targets.has(cwd === "." ? "package.json" : `${cwd}/package.json`))));
58
+ lock.document.set("patchedDependencies", Object.fromEntries(Object.entries(lock.data.patchedDependencies ?? {}).filter(([dependency]) => patches.has(dependency))));
59
+ return new Map([
60
+ [
61
+ "pnpm-workspace.yaml",
62
+ new TextEncoder().encode(settings.document.toString()),
63
+ ],
64
+ ["pnpm-lock.yaml", new TextEncoder().encode(lock.document.toString())],
65
+ ]);
66
+ });
67
+ export const readWorkspaceSettings = (source) => yamlDocument(source, "pnpm-workspace.yaml", WorkspaceSettings);
68
+ export const applicationManifest = Effect.fn("Composition.applicationManifest")(function* (source, name, browserTests) {
69
+ const manifest = yield* Schema.decodeEffect(RootManifest)(new TextDecoder().decode(source));
70
+ if (!manifest.devDependencies.portless) {
71
+ return yield* new InvalidComposition({
72
+ message: "The source workspace must declare Portless in devDependencies",
73
+ });
74
+ }
75
+ const devDependencies = {
76
+ "@typescript/native": manifest.devDependencies["@typescript/native"],
77
+ portless: manifest.devDependencies.portless,
78
+ turbo: manifest.devDependencies.turbo,
79
+ typescript: "catalog:",
80
+ };
81
+ const scripts = {
82
+ build: "turbo run build",
83
+ dev: "turbo run dev",
84
+ test: "turbo run test",
85
+ "test:e2e": browserTests ? "turbo run e2e" : undefined,
86
+ "test:e2e:list": browserTests ? "turbo run e2e:list" : undefined,
87
+ typecheck: "turbo run typecheck",
88
+ };
89
+ return new TextEncoder().encode(`${yield* encodeJson({ devDependencies, engines: manifest.engines, name, packageManager: manifest.packageManager, private: true, scripts })}\n`);
90
+ });
package/dist/cli.js CHANGED
@@ -1,19 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { cancel } from "@clack/prompts";
3
- import { runCli } from "./index.js";
4
- import { UserCancelledError } from "./prompts.js";
5
- async function main() {
6
- try {
7
- await runCli(process.argv);
8
- }
9
- catch (error) {
10
- if (error instanceof UserCancelledError) {
11
- process.exitCode = 1;
12
- return;
13
- }
14
- const message = error instanceof Error ? error.message : "Unknown error";
15
- cancel(message);
16
- process.exitCode = 1;
17
- }
18
- }
19
- main();
2
+ import { NodeRuntime } from "@effect/platform-node";
3
+ import { Effect } from "effect";
4
+ import { cliProgram } from "./index.js";
5
+ import { runtime } from "./runtime.js";
6
+ NodeRuntime.runMain(cliProgram().pipe(Effect.provide(runtime)));