@trustless-work/blocks 0.0.7 → 0.0.8

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 (66) hide show
  1. package/bin/index.js +485 -17
  2. package/package.json +1 -1
  3. package/templates/escrows/details/Actions.tsx +144 -149
  4. package/templates/escrows/details/Entities.tsx +1 -1
  5. package/templates/escrows/details/EntityCard.tsx +1 -3
  6. package/templates/escrows/details/EscrowDetailDialog.tsx +16 -16
  7. package/templates/escrows/details/GeneralInformation.tsx +19 -22
  8. package/templates/escrows/details/MilestoneCard.tsx +46 -47
  9. package/templates/escrows/details/MilestoneDetailDialog.tsx +1 -2
  10. package/templates/escrows/details/Milestones.tsx +0 -5
  11. package/templates/escrows/details/SuccessReleaseDialog.tsx +4 -6
  12. package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +84 -49
  13. package/templates/escrows/escrows-by-role/cards/Filters.tsx +3 -5
  14. package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +8 -26
  15. package/templates/escrows/escrows-by-role/table/Filters.tsx +3 -5
  16. package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +89 -55
  17. package/templates/escrows/escrows-by-signer/cards/Filters.tsx +3 -5
  18. package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +8 -24
  19. package/templates/escrows/escrows-by-signer/table/Filters.tsx +3 -5
  20. package/templates/escrows/multi-release/dispute-milestone/button/DisputeEscrow.tsx +98 -0
  21. package/templates/escrows/multi-release/initialize-escrow/dialog/InitializeEscrow.tsx +528 -0
  22. package/templates/escrows/multi-release/initialize-escrow/form/InitializeEscrow.tsx +506 -0
  23. package/templates/escrows/multi-release/initialize-escrow/shared/schema.ts +179 -0
  24. package/templates/escrows/multi-release/initialize-escrow/shared/useInitializeEscrow.ts +175 -0
  25. package/templates/escrows/multi-release/release-milestone/button/ReleaseEscrow.tsx +116 -0
  26. package/templates/escrows/multi-release/resolve-dispute/button/ResolveDispute.tsx +122 -0
  27. package/templates/escrows/multi-release/resolve-dispute/dialog/ResolveDispute.tsx +178 -0
  28. package/templates/escrows/multi-release/resolve-dispute/form/ResolveDispute.tsx +156 -0
  29. package/templates/escrows/multi-release/resolve-dispute/shared/schema.ts +85 -0
  30. package/templates/escrows/multi-release/resolve-dispute/shared/useResolveDispute.ts +105 -0
  31. package/templates/escrows/multi-release/update-escrow/dialog/UpdateEscrow.tsx +471 -0
  32. package/templates/escrows/multi-release/update-escrow/form/UpdateEscrow.tsx +449 -0
  33. package/templates/escrows/multi-release/update-escrow/shared/schema.ts +152 -0
  34. package/templates/escrows/multi-release/update-escrow/shared/useUpdateEscrow.ts +254 -0
  35. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/button/ApproveMilestone.tsx +20 -7
  36. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/dialog/ApproveMilestone.tsx +3 -3
  37. package/templates/escrows/{single-release → single-multi-release}/approve-milestone/form/ApproveMilestone.tsx +3 -3
  38. package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/useApproveMilestone.ts +16 -16
  39. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/button/ChangeMilestoneStatus.tsx +4 -4
  40. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +4 -4
  41. package/templates/escrows/{single-release → single-multi-release}/change-milestone-status/form/ChangeMilestoneStatus.tsx +3 -3
  42. package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/useChangeMilestoneStatus.ts +1 -1
  43. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/button/FundEscrow.tsx +3 -3
  44. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/dialog/FundEscrow.tsx +3 -3
  45. package/templates/escrows/{single-release → single-multi-release}/fund-escrow/form/FundEscrow.tsx +3 -3
  46. package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/useFundEscrow.ts +1 -1
  47. package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +2 -2
  48. package/templates/escrows/single-release/initialize-escrow/dialog/InitializeEscrow.tsx +14 -6
  49. package/templates/escrows/single-release/initialize-escrow/form/InitializeEscrow.tsx +14 -6
  50. package/templates/escrows/single-release/initialize-escrow/shared/schema.ts +0 -57
  51. package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +42 -1
  52. package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +2 -2
  53. package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +3 -3
  54. package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +3 -6
  55. package/templates/escrows/single-release/resolve-dispute/form/ResolveDispute.tsx +2 -2
  56. package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +14 -1
  57. package/templates/escrows/single-release/update-escrow/dialog/UpdateEscrow.tsx +2 -2
  58. package/templates/escrows/single-release/update-escrow/form/UpdateEscrow.tsx +2 -2
  59. package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +12 -7
  60. package/templates/providers/EscrowDialogsProvider.tsx +1 -3
  61. package/templates/providers/EscrowProvider.tsx +27 -4
  62. package/templates/providers/TrustlessWork.tsx +1 -1
  63. package/templates/escrows/details/ProgressEscrow.tsx +0 -191
  64. /package/templates/escrows/{single-release/approve-milestone/shared → single-multi-release/approve-milestone}/schema.ts +0 -0
  65. /package/templates/escrows/{single-release/change-milestone-status/shared → single-multi-release/change-milestone-status}/schema.ts +0 -0
  66. /package/templates/escrows/{single-release/fund-escrow/shared → single-multi-release/fund-escrow}/schema.ts +0 -0
package/bin/index.js CHANGED
@@ -220,20 +220,211 @@ function parseFlags(argv) {
220
220
 
221
221
  function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
222
222
  const srcFile = path.join(TEMPLATES_DIR, `${name}.tsx`);
223
- const srcDir = path.join(TEMPLATES_DIR, name);
223
+ const requestedDir = path.join(TEMPLATES_DIR, name);
224
+ let srcDir = null;
225
+ if (fs.existsSync(requestedDir) && fs.lstatSync(requestedDir).isDirectory()) {
226
+ srcDir = requestedDir;
227
+ } else {
228
+ // Alias: allow multi-release/approve-milestone to fallback to existing source
229
+ if (name.startsWith("escrows/multi-release/approve-milestone")) {
230
+ const altMulti = path.join(
231
+ TEMPLATES_DIR,
232
+ "escrows",
233
+ "multi-release",
234
+ "approve-milestone"
235
+ );
236
+ const altSingle = path.join(
237
+ TEMPLATES_DIR,
238
+ "escrows",
239
+ "single-release",
240
+ "approve-milestone"
241
+ );
242
+ if (fs.existsSync(altMulti) && fs.lstatSync(altMulti).isDirectory()) {
243
+ srcDir = altMulti;
244
+ } else if (
245
+ fs.existsSync(altSingle) &&
246
+ fs.lstatSync(altSingle).isDirectory()
247
+ ) {
248
+ srcDir = altSingle;
249
+ }
250
+ }
251
+ }
224
252
  const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
225
253
 
226
254
  const config = loadConfig();
227
255
  const effectiveUiBase = uiBase || config.uiBase || "@/components/ui";
256
+ let currentEscrowType = null;
228
257
 
229
258
  function writeTransformed(srcPath, destPath) {
230
259
  const raw = fs.readFileSync(srcPath, "utf8");
231
- const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
260
+ let transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
261
+ // Resolve details placeholders to either multi-release modules (if present) or local compat
262
+ const applyDetailsPlaceholders = (content) => {
263
+ const resolveImport = (segments, compatFile) => {
264
+ const realWithExt = path.join(
265
+ outRoot,
266
+ "escrows",
267
+ "multi-release",
268
+ ...segments
269
+ );
270
+ const realCandidate = [
271
+ realWithExt,
272
+ realWithExt + ".tsx",
273
+ realWithExt + ".ts",
274
+ realWithExt + ".jsx",
275
+ realWithExt + ".js",
276
+ ].find((p) => fs.existsSync(p));
277
+ const realNoExt = realCandidate
278
+ ? realCandidate.replace(/\.(tsx|ts|jsx|js)$/i, "")
279
+ : null;
280
+ const compatWithExt = path.join(
281
+ path.dirname(destPath),
282
+ "compat",
283
+ compatFile
284
+ );
285
+ const compatCandidate = [
286
+ compatWithExt,
287
+ compatWithExt + ".tsx",
288
+ compatWithExt + ".ts",
289
+ compatWithExt + ".jsx",
290
+ compatWithExt + ".js",
291
+ ].find((p) => fs.existsSync(p));
292
+ const compatNoExt = (compatCandidate || compatWithExt).replace(
293
+ /\.(tsx|ts|jsx|js)$/i,
294
+ ""
295
+ );
296
+ const target = realNoExt || compatNoExt;
297
+ let rel = path.relative(path.dirname(destPath), target);
298
+ rel = rel.split(path.sep).join("/");
299
+ if (!rel.startsWith(".")) rel = "./" + rel;
300
+ return rel;
301
+ };
302
+ return content
303
+ .replaceAll(
304
+ "__MR_RELEASE_MODULE__",
305
+ resolveImport(
306
+ ["release-escrow", "button", "ReleaseEscrow"],
307
+ "ReleaseEscrow"
308
+ )
309
+ )
310
+ .replaceAll(
311
+ "__MR_DISPUTE_MODULE__",
312
+ resolveImport(
313
+ ["dispute-escrow", "button", "DisputeEscrow"],
314
+ "DisputeEscrow"
315
+ )
316
+ )
317
+ .replaceAll(
318
+ "__MR_RESOLVE_MODULE__",
319
+ resolveImport(
320
+ ["resolve-dispute", "dialog", "ResolveDispute"],
321
+ "ResolveDispute"
322
+ )
323
+ );
324
+ };
325
+ transformed = applyDetailsPlaceholders(transformed);
326
+ if (currentEscrowType) {
327
+ transformed = transformed.replaceAll(
328
+ "__ESCROW_TYPE__",
329
+ currentEscrowType
330
+ );
331
+ }
232
332
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
233
333
  fs.writeFileSync(destPath, transformed, "utf8");
234
334
  console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
235
335
  }
236
336
 
337
+ // Generic: materialize any module from templates/escrows/shared/<module>
338
+ if (!srcDir) {
339
+ const m = name.match(
340
+ /^escrows\/(single-release|multi-release)\/([^\/]+)(?:\/(button|dialog|form))?$/
341
+ );
342
+ if (m) {
343
+ const releaseType = m[1];
344
+ const moduleName = m[2];
345
+ const variant = m[3] || null;
346
+
347
+ const sharedModuleDir = path.join(
348
+ TEMPLATES_DIR,
349
+ "escrows",
350
+ "shared",
351
+ moduleName
352
+ );
353
+
354
+ if (
355
+ fs.existsSync(sharedModuleDir) &&
356
+ fs.lstatSync(sharedModuleDir).isDirectory()
357
+ ) {
358
+ currentEscrowType = releaseType;
359
+ const destBase = path.join(outRoot, "escrows", releaseType, moduleName);
360
+
361
+ function copyModuleRootFilesInto(targetDir) {
362
+ const entries = fs.readdirSync(sharedModuleDir, {
363
+ withFileTypes: true,
364
+ });
365
+ for (const entry of entries) {
366
+ if (entry.isDirectory()) continue;
367
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
368
+ const entrySrc = path.join(sharedModuleDir, entry.name);
369
+ const entryDest = path.join(targetDir, entry.name);
370
+ writeTransformed(entrySrc, entryDest);
371
+ }
372
+ }
373
+
374
+ function copyVariant(variantName) {
375
+ const variantSrc = path.join(sharedModuleDir, variantName);
376
+ const variantDest = path.join(destBase, variantName);
377
+ fs.mkdirSync(variantDest, { recursive: true });
378
+ if (
379
+ fs.existsSync(variantSrc) &&
380
+ fs.lstatSync(variantSrc).isDirectory()
381
+ ) {
382
+ const stack = [""];
383
+ while (stack.length) {
384
+ const rel = stack.pop();
385
+ const current = path.join(variantSrc, rel);
386
+ const entries = fs.readdirSync(current, { withFileTypes: true });
387
+ for (const entry of entries) {
388
+ const entryRel = path.join(rel, entry.name);
389
+ const entrySrc = path.join(variantSrc, entryRel);
390
+ const entryDest = path.join(variantDest, entryRel);
391
+ if (entry.isDirectory()) {
392
+ stack.push(entryRel);
393
+ continue;
394
+ }
395
+ if (/\.(tsx?|jsx?)$/i.test(entry.name)) {
396
+ writeTransformed(entrySrc, entryDest);
397
+ } else {
398
+ fs.mkdirSync(path.dirname(entryDest), { recursive: true });
399
+ fs.copyFileSync(entrySrc, entryDest);
400
+ console.log(
401
+ `✅ ${path.relative(PROJECT_ROOT, entryDest)} created`
402
+ );
403
+ }
404
+ }
405
+ }
406
+ }
407
+ // Always place module-level shared files into the variant directory
408
+ copyModuleRootFilesInto(variantDest);
409
+ }
410
+
411
+ if (variant) {
412
+ copyVariant(variant);
413
+ } else {
414
+ const variants = ["button", "dialog", "form"];
415
+ for (const v of variants) copyVariant(v);
416
+ }
417
+
418
+ if (shouldInstall && fs.existsSync(GLOBAL_DEPS_FILE)) {
419
+ const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
420
+ installDeps(meta);
421
+ }
422
+ currentEscrowType = null;
423
+ return;
424
+ }
425
+ }
426
+ }
427
+
237
428
  if (fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()) {
238
429
  const skipDetails =
239
430
  name === "escrows/escrows-by-role" ||
@@ -326,17 +517,20 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
326
517
  }
327
518
 
328
519
  try {
329
- const isSingleReleaseInitRoot =
330
- name === "escrows/single-release/approve-milestone";
331
- const isSingleReleaseInitDialog =
520
+ const isSRRoot = name === "escrows/single-release/approve-milestone";
521
+ const isSRDialog =
332
522
  name === "escrows/single-release/approve-milestone/dialog";
333
- const isSingleReleaseInitForm =
334
- name === "escrows/single-release/approve-milestone/form";
523
+ const isSRForm = name === "escrows/single-release/approve-milestone/form";
524
+
525
+ const isMRRoot = name === "escrows/multi-release/approve-milestone";
526
+ const isMRDialog =
527
+ name === "escrows/multi-release/approve-milestone/dialog";
528
+ const isMRForm = name === "escrows/multi-release/approve-milestone/form";
335
529
 
336
530
  const srcSharedDir = path.join(
337
531
  TEMPLATES_DIR,
338
532
  "escrows",
339
- "single-release",
533
+ "shared",
340
534
  "approve-milestone",
341
535
  "shared"
342
536
  );
@@ -352,12 +546,12 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
352
546
  }
353
547
  }
354
548
 
355
- if (isSingleReleaseInitRoot) {
549
+ if (isSRRoot || isMRRoot) {
356
550
  copySharedInto(path.join(destDir, "dialog"));
357
551
  copySharedInto(path.join(destDir, "form"));
358
- } else if (isSingleReleaseInitDialog) {
552
+ } else if (isSRDialog || isMRDialog) {
359
553
  copySharedInto(destDir);
360
- } else if (isSingleReleaseInitForm) {
554
+ } else if (isSRForm || isMRForm) {
361
555
  copySharedInto(destDir);
362
556
  }
363
557
  } catch (e) {
@@ -535,6 +729,132 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
535
729
  );
536
730
  }
537
731
 
732
+ // Post-copy: materialize shared files for multi-release modules
733
+ try {
734
+ const isMultiInitRoot =
735
+ name === "escrows/multi-release/initialize-escrow";
736
+ const isMultiInitDialog =
737
+ name === "escrows/multi-release/initialize-escrow/dialog";
738
+ const isMultiInitForm =
739
+ name === "escrows/multi-release/initialize-escrow/form";
740
+
741
+ const srcSharedDir = path.join(
742
+ TEMPLATES_DIR,
743
+ "escrows",
744
+ "multi-release",
745
+ "initialize-escrow",
746
+ "shared"
747
+ );
748
+
749
+ function copyMultiInitSharedInto(targetDir) {
750
+ if (!fs.existsSync(srcSharedDir)) return;
751
+ const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
752
+ for (const entry of entries) {
753
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
754
+ const entrySrc = path.join(srcSharedDir, entry.name);
755
+ const entryDest = path.join(targetDir, entry.name);
756
+ writeTransformed(entrySrc, entryDest);
757
+ }
758
+ }
759
+
760
+ if (isMultiInitRoot) {
761
+ copyMultiInitSharedInto(path.join(destDir, "dialog"));
762
+ copyMultiInitSharedInto(path.join(destDir, "form"));
763
+ } else if (isMultiInitDialog) {
764
+ copyMultiInitSharedInto(destDir);
765
+ } else if (isMultiInitForm) {
766
+ copyMultiInitSharedInto(destDir);
767
+ }
768
+ } catch (e) {
769
+ console.warn(
770
+ "⚠️ Failed to materialize shared multi-release initialize-escrow files:",
771
+ e?.message || e
772
+ );
773
+ }
774
+
775
+ try {
776
+ const isMultiResolveRoot =
777
+ name === "escrows/multi-release/resolve-dispute";
778
+ const isMultiResolveDialog =
779
+ name === "escrows/multi-release/resolve-dispute/dialog";
780
+ const isMultiResolveForm =
781
+ name === "escrows/multi-release/resolve-dispute/form";
782
+
783
+ const srcSharedDir = path.join(
784
+ TEMPLATES_DIR,
785
+ "escrows",
786
+ "multi-release",
787
+ "resolve-dispute",
788
+ "shared"
789
+ );
790
+
791
+ function copyMultiResolveSharedInto(targetDir) {
792
+ if (!fs.existsSync(srcSharedDir)) return;
793
+ const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
794
+ for (const entry of entries) {
795
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
796
+ const entrySrc = path.join(srcSharedDir, entry.name);
797
+ const entryDest = path.join(targetDir, entry.name);
798
+ writeTransformed(entrySrc, entryDest);
799
+ }
800
+ }
801
+
802
+ if (isMultiResolveRoot) {
803
+ copyMultiResolveSharedInto(path.join(destDir, "dialog"));
804
+ copyMultiResolveSharedInto(path.join(destDir, "form"));
805
+ } else if (isMultiResolveDialog) {
806
+ copyMultiResolveSharedInto(destDir);
807
+ } else if (isMultiResolveForm) {
808
+ copyMultiResolveSharedInto(destDir);
809
+ }
810
+ } catch (e) {
811
+ console.warn(
812
+ "⚠️ Failed to materialize shared multi-release resolve-dispute files:",
813
+ e?.message || e
814
+ );
815
+ }
816
+
817
+ try {
818
+ const isMultiUpdateRoot = name === "escrows/multi-release/update-escrow";
819
+ const isMultiUpdateDialog =
820
+ name === "escrows/multi-release/update-escrow/dialog";
821
+ const isMultiUpdateForm =
822
+ name === "escrows/multi-release/update-escrow/form";
823
+
824
+ const srcSharedDir = path.join(
825
+ TEMPLATES_DIR,
826
+ "escrows",
827
+ "multi-release",
828
+ "update-escrow",
829
+ "shared"
830
+ );
831
+
832
+ function copyMultiUpdateSharedInto(targetDir) {
833
+ if (!fs.existsSync(srcSharedDir)) return;
834
+ const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
835
+ for (const entry of entries) {
836
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
837
+ const entrySrc = path.join(srcSharedDir, entry.name);
838
+ const entryDest = path.join(targetDir, entry.name);
839
+ writeTransformed(entrySrc, entryDest);
840
+ }
841
+ }
842
+
843
+ if (isMultiUpdateRoot) {
844
+ copyMultiUpdateSharedInto(path.join(destDir, "dialog"));
845
+ copyMultiUpdateSharedInto(path.join(destDir, "form"));
846
+ } else if (isMultiUpdateDialog) {
847
+ copyMultiUpdateSharedInto(destDir);
848
+ } else if (isMultiUpdateForm) {
849
+ copyMultiUpdateSharedInto(destDir);
850
+ }
851
+ } catch (e) {
852
+ console.warn(
853
+ "⚠️ Failed to materialize shared multi-release update-escrow files:",
854
+ e?.message || e
855
+ );
856
+ }
857
+
538
858
  // If adding the whole single-release bundle, materialize all shared files
539
859
  try {
540
860
  if (name === "escrows/single-release") {
@@ -551,8 +871,8 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
551
871
  const srcSharedDir = path.join(
552
872
  TEMPLATES_DIR,
553
873
  "escrows",
554
- "single-release",
555
- mod,
874
+ mod === "approve-milestone" ? "shared" : "single-release",
875
+ mod === "approve-milestone" ? "approve-milestone" : mod,
556
876
  "shared"
557
877
  );
558
878
  if (!fs.existsSync(srcSharedDir)) continue;
@@ -580,6 +900,48 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
580
900
  );
581
901
  }
582
902
 
903
+ // If adding the whole multi-release bundle, materialize all shared files
904
+ try {
905
+ if (name === "escrows/multi-release") {
906
+ const modules = [
907
+ "initialize-escrow",
908
+ "resolve-dispute",
909
+ "update-escrow",
910
+ ];
911
+
912
+ for (const mod of modules) {
913
+ const srcSharedDir = path.join(
914
+ TEMPLATES_DIR,
915
+ "escrows",
916
+ "multi-release",
917
+ mod,
918
+ "shared"
919
+ );
920
+ if (!fs.existsSync(srcSharedDir)) continue;
921
+
922
+ const targets = [
923
+ path.join(destDir, mod, "dialog"),
924
+ path.join(destDir, mod, "form"),
925
+ ];
926
+
927
+ const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
928
+ for (const entry of entries) {
929
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
930
+ const entrySrc = path.join(srcSharedDir, entry.name);
931
+ for (const t of targets) {
932
+ const entryDest = path.join(t, entry.name);
933
+ writeTransformed(entrySrc, entryDest);
934
+ }
935
+ }
936
+ }
937
+ }
938
+ } catch (e) {
939
+ console.warn(
940
+ "⚠️ Failed to materialize shared files for multi-release bundle:",
941
+ e?.message || e
942
+ );
943
+ }
944
+
583
945
  // If adding the root escrows bundle, also materialize single-release shared files
584
946
  try {
585
947
  if (name === "escrows") {
@@ -597,8 +959,8 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
597
959
  const srcSharedDir = path.join(
598
960
  TEMPLATES_DIR,
599
961
  "escrows",
600
- "single-release",
601
- mod,
962
+ mod === "approve-milestone" ? "shared" : "single-release",
963
+ mod === "approve-milestone" ? "approve-milestone" : mod,
602
964
  "shared"
603
965
  );
604
966
  if (!fs.existsSync(srcSharedDir)) continue;
@@ -625,6 +987,49 @@ function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
625
987
  e?.message || e
626
988
  );
627
989
  }
990
+
991
+ // If adding the root escrows bundle, also materialize multi-release shared files
992
+ try {
993
+ if (name === "escrows") {
994
+ const modules = [
995
+ "initialize-escrow",
996
+ "resolve-dispute",
997
+ "update-escrow",
998
+ ];
999
+
1000
+ const baseTarget = path.join(destDir, "multi-release");
1001
+ for (const mod of modules) {
1002
+ const srcSharedDir = path.join(
1003
+ TEMPLATES_DIR,
1004
+ "escrows",
1005
+ "multi-release",
1006
+ mod,
1007
+ "shared"
1008
+ );
1009
+ if (!fs.existsSync(srcSharedDir)) continue;
1010
+
1011
+ const targets = [
1012
+ path.join(baseTarget, mod, "dialog"),
1013
+ path.join(baseTarget, mod, "form"),
1014
+ ];
1015
+
1016
+ const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
1017
+ for (const entry of entries) {
1018
+ if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
1019
+ const entrySrc = path.join(srcSharedDir, entry.name);
1020
+ for (const t of targets) {
1021
+ const entryDest = path.join(t, entry.name);
1022
+ writeTransformed(entrySrc, entryDest);
1023
+ }
1024
+ }
1025
+ }
1026
+ }
1027
+ } catch (e) {
1028
+ console.warn(
1029
+ "⚠️ Failed to materialize shared files for escrows root (multi-release):",
1030
+ e?.message || e
1031
+ );
1032
+ }
628
1033
  } else if (fs.existsSync(srcFile)) {
629
1034
  fs.mkdirSync(outRoot, { recursive: true });
630
1035
  const destFile = path.join(outRoot, name + ".tsx");
@@ -652,7 +1057,69 @@ function copySharedDetailsInto(targetRelativeDir, { uiBase } = {}) {
652
1057
 
653
1058
  function writeTransformed(srcPath, destPath) {
654
1059
  const raw = fs.readFileSync(srcPath, "utf8");
655
- const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
1060
+ let transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
1061
+ // Resolve details placeholders to either multi-release modules (if present) or local compat
1062
+ const resolveImport = (segments, compatFile) => {
1063
+ const realWithExt = path.join(
1064
+ outRoot,
1065
+ "escrows",
1066
+ "multi-release",
1067
+ ...segments
1068
+ );
1069
+ const realCandidate = [
1070
+ realWithExt,
1071
+ realWithExt + ".tsx",
1072
+ realWithExt + ".ts",
1073
+ realWithExt + ".jsx",
1074
+ realWithExt + ".js",
1075
+ ].find((p) => fs.existsSync(p));
1076
+ const realNoExt = realCandidate
1077
+ ? realCandidate.replace(/\.(tsx|ts|jsx|js)$/i, "")
1078
+ : null;
1079
+ const compatWithExt = path.join(
1080
+ path.dirname(destPath),
1081
+ "compat",
1082
+ compatFile
1083
+ );
1084
+ const compatCandidate = [
1085
+ compatWithExt,
1086
+ compatWithExt + ".tsx",
1087
+ compatWithExt + ".ts",
1088
+ compatWithExt + ".jsx",
1089
+ compatWithExt + ".js",
1090
+ ].find((p) => fs.existsSync(p));
1091
+ const compatNoExt = (compatCandidate || compatWithExt).replace(
1092
+ /\.(tsx|ts|jsx|js)$/i,
1093
+ ""
1094
+ );
1095
+ const target = realNoExt || compatNoExt;
1096
+ let rel = path.relative(path.dirname(destPath), target);
1097
+ rel = rel.split(path.sep).join("/");
1098
+ if (!rel.startsWith(".")) rel = "./" + rel;
1099
+ return rel;
1100
+ };
1101
+ transformed = transformed
1102
+ .replaceAll(
1103
+ "__MR_RELEASE_MODULE__",
1104
+ resolveImport(
1105
+ ["release-escrow", "button", "ReleaseEscrow"],
1106
+ "ReleaseEscrow"
1107
+ )
1108
+ )
1109
+ .replaceAll(
1110
+ "__MR_DISPUTE_MODULE__",
1111
+ resolveImport(
1112
+ ["dispute-escrow", "button", "DisputeEscrow"],
1113
+ "DisputeEscrow"
1114
+ )
1115
+ )
1116
+ .replaceAll(
1117
+ "__MR_RESOLVE_MODULE__",
1118
+ resolveImport(
1119
+ ["resolve-dispute", "dialog", "ResolveDispute"],
1120
+ "ResolveDispute"
1121
+ )
1122
+ );
656
1123
  fs.mkdirSync(path.dirname(destPath), { recursive: true });
657
1124
  fs.writeFileSync(destPath, transformed, "utf8");
658
1125
  console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
@@ -856,7 +1323,7 @@ if (args[0] === "init") {
856
1323
  }
857
1324
 
858
1325
  const addShadcn = await promptYesNo(
859
- "Add shadcn components (button, input, form, card, sonner, checkbox, dialog, textarea, sonner, select, table, calendar, popover, separator, calendar-05, badge, sheet, tabs, avatar)?",
1326
+ "Add shadcn components (button, input, form, card, sonner, checkbox, dialog, textarea, sonner, select, table, calendar, popover, separator, calendar-05, badge, sheet, tabs, avatar, tooltip)?",
860
1327
  true
861
1328
  );
862
1329
  if (addShadcn) {
@@ -883,6 +1350,7 @@ if (args[0] === "init") {
883
1350
  "sheet",
884
1351
  "tabs",
885
1352
  "avatar",
1353
+ "tooltip",
886
1354
  ]);
887
1355
  });
888
1356
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustless-work/blocks",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "author": "Trustless Work",
5
5
  "keywords": [
6
6
  "react",