@trustless-work/blocks 0.0.6 → 0.0.7
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.
- package/README.md +39 -13
- package/bin/index.js +1128 -1137
- package/package.json +44 -44
- package/templates/escrows/details/EscrowDetailDialog.tsx +3 -3
- package/templates/escrows/details/GeneralInformation.tsx +2 -2
- package/templates/escrows/details/SuccessReleaseDialog.tsx +2 -3
- package/templates/escrows/details/useDetailsEscrow.ts +2 -2
- package/templates/escrows/escrows-by-role/cards/EscrowsCards.tsx +32 -16
- package/templates/escrows/escrows-by-role/table/EscrowsTable.tsx +34 -18
- package/templates/escrows/escrows-by-role/useEscrowsByRole.shared.ts +33 -25
- package/templates/escrows/escrows-by-signer/cards/EscrowsCards.tsx +22 -16
- package/templates/escrows/escrows-by-signer/table/EscrowsTable.tsx +23 -17
- package/templates/escrows/escrows-by-signer/useEscrowsBySigner.shared.ts +32 -25
- package/templates/escrows/single-release/approve-milestone/button/ApproveMilestone.tsx +1 -1
- package/templates/escrows/single-release/approve-milestone/dialog/ApproveMilestone.tsx +1 -1
- package/templates/escrows/single-release/approve-milestone/form/ApproveMilestone.tsx +1 -1
- package/templates/escrows/single-release/approve-milestone/shared/useApproveMilestone.ts +1 -1
- package/templates/escrows/single-release/change-milestone-status/button/ChangeMilestoneStatus.tsx +1 -1
- package/templates/escrows/single-release/change-milestone-status/dialog/ChangeMilestoneStatus.tsx +1 -1
- package/templates/escrows/single-release/change-milestone-status/form/ChangeMilestoneStatus.tsx +1 -1
- package/templates/escrows/single-release/change-milestone-status/shared/useChangeMilestoneStatus.ts +1 -1
- package/templates/escrows/single-release/dispute-escrow/button/DisputeEscrow.tsx +1 -1
- package/templates/escrows/single-release/fund-escrow/button/FundEscrow.tsx +1 -1
- package/templates/escrows/single-release/fund-escrow/shared/useFundEscrow.ts +1 -1
- package/templates/escrows/single-release/initialize-escrow/shared/useInitializeEscrow.ts +1 -1
- package/templates/escrows/single-release/release-escrow/button/ReleaseEscrow.tsx +3 -3
- package/templates/escrows/single-release/resolve-dispute/button/ResolveDispute.tsx +1 -1
- package/templates/escrows/single-release/resolve-dispute/dialog/ResolveDispute.tsx +1 -1
- package/templates/escrows/single-release/resolve-dispute/shared/useResolveDispute.ts +1 -1
- package/templates/escrows/single-release/update-escrow/shared/useUpdateEscrow.ts +224 -224
- package/templates/providers/ReactQueryClientProvider.tsx +3 -1
- /package/templates/{escrows/escrow-context → providers}/EscrowAmountProvider.tsx +0 -0
- /package/templates/{escrows/escrow-context → providers}/EscrowDialogsProvider.tsx +0 -0
- /package/templates/{escrows/escrow-context → providers}/EscrowProvider.tsx +0 -0
package/bin/index.js
CHANGED
|
@@ -1,1138 +1,1129 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/*
|
|
4
|
-
AUTHOR: @trustless-work / Joel Vargas
|
|
5
|
-
COPYRIGHT: 2025 Trustless Work
|
|
6
|
-
LICENSE: MIT
|
|
7
|
-
VERSION: 1.0.0
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { fileURLToPath } from "node:url";
|
|
13
|
-
import { spawnSync, spawn } from "node:child_process";
|
|
14
|
-
import readline from "node:readline";
|
|
15
|
-
|
|
16
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
-
const __dirname = path.dirname(__filename);
|
|
18
|
-
|
|
19
|
-
const PROJECT_ROOT = process.cwd();
|
|
20
|
-
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
21
|
-
const GLOBAL_DEPS_FILE = path.join(TEMPLATES_DIR, "deps.json");
|
|
22
|
-
|
|
23
|
-
const args = process.argv.slice(2);
|
|
24
|
-
|
|
25
|
-
function detectPM() {
|
|
26
|
-
if (fs.existsSync(path.join(PROJECT_ROOT, "pnpm-lock.yaml"))) return "pnpm";
|
|
27
|
-
if (fs.existsSync(path.join(PROJECT_ROOT, "yarn.lock"))) return "yarn";
|
|
28
|
-
if (fs.existsSync(path.join(PROJECT_ROOT, "bun.lockb"))) return "bun";
|
|
29
|
-
return "npm";
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function run(cmd, args) {
|
|
33
|
-
const r = spawnSync(cmd, args.filter(Boolean), {
|
|
34
|
-
stdio: "inherit",
|
|
35
|
-
cwd: PROJECT_ROOT,
|
|
36
|
-
shell: true,
|
|
37
|
-
});
|
|
38
|
-
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function tryRun(cmd, args, errorMessage) {
|
|
42
|
-
const r = spawnSync(cmd, args.filter(Boolean), {
|
|
43
|
-
stdio: "inherit",
|
|
44
|
-
cwd: PROJECT_ROOT,
|
|
45
|
-
shell: true,
|
|
46
|
-
});
|
|
47
|
-
if (r.status !== 0) {
|
|
48
|
-
console.error(errorMessage);
|
|
49
|
-
process.exit(r.status ?? 1);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async function runAsync(cmd, args) {
|
|
54
|
-
return new Promise((resolve, reject) => {
|
|
55
|
-
const child = spawn(cmd, args.filter(Boolean), {
|
|
56
|
-
stdio: "inherit",
|
|
57
|
-
cwd: PROJECT_ROOT,
|
|
58
|
-
shell: true,
|
|
59
|
-
});
|
|
60
|
-
child.on("close", (code) => {
|
|
61
|
-
if (code === 0) resolve();
|
|
62
|
-
else reject(new Error(`${cmd} exited with code ${code}`));
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const COLORS = {
|
|
68
|
-
reset: "\x1b[0m",
|
|
69
|
-
green: "\x1b[32m",
|
|
70
|
-
gray: "\x1b[90m",
|
|
71
|
-
blueTW: "\x1b[38;2;0;107;228m",
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
function logCheck(message) {
|
|
75
|
-
console.log(`${COLORS.green}✔${COLORS.reset} ${message}`);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function startSpinner(message) {
|
|
79
|
-
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
80
|
-
let i = 0;
|
|
81
|
-
process.stdout.write(`${frames[0]} ${message}`);
|
|
82
|
-
const timer = setInterval(() => {
|
|
83
|
-
i = (i + 1) % frames.length;
|
|
84
|
-
process.stdout.write(`\r${frames[i]} ${message}`);
|
|
85
|
-
}, 80);
|
|
86
|
-
return () => {
|
|
87
|
-
clearInterval(timer);
|
|
88
|
-
process.stdout.write("\r");
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function withSpinner(message, fn) {
|
|
93
|
-
const stop = startSpinner(message);
|
|
94
|
-
try {
|
|
95
|
-
await fn();
|
|
96
|
-
stop();
|
|
97
|
-
logCheck(message);
|
|
98
|
-
} catch (err) {
|
|
99
|
-
stop();
|
|
100
|
-
throw err;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function promptYesNo(question, def = true) {
|
|
105
|
-
const rl = readline.createInterface({
|
|
106
|
-
input: process.stdin,
|
|
107
|
-
output: process.stdout,
|
|
108
|
-
});
|
|
109
|
-
const suffix = def ? "(Y/n)" : "(y/N)";
|
|
110
|
-
const answer = await new Promise((res) =>
|
|
111
|
-
rl.question(`${question} ${suffix} `, (ans) => res(ans))
|
|
112
|
-
);
|
|
113
|
-
rl.close();
|
|
114
|
-
const a = String(answer).trim().toLowerCase();
|
|
115
|
-
if (!a) return def;
|
|
116
|
-
return a.startsWith("y");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function oscHyperlink(text, url) {
|
|
120
|
-
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function printBannerTRUSTLESSWORK() {
|
|
124
|
-
const map = {
|
|
125
|
-
T: ["******", " ** ", " ** ", " ** ", " ** "],
|
|
126
|
-
R: ["***** ", "** **", "***** ", "** ** ", "** **"],
|
|
127
|
-
U: ["** **", "** **", "** **", "** **", " **** "],
|
|
128
|
-
S: [" **** ", "** ", " **** ", " **", " **** "],
|
|
129
|
-
L: ["** ", "** ", "** ", "** ", "******"],
|
|
130
|
-
E: ["******", "** ", "***** ", "** ", "******"],
|
|
131
|
-
W: ["** **", "** **", "** * **", "*** ***", "** **"],
|
|
132
|
-
O: [" **** ", "** **", "** **", "** **", " **** "],
|
|
133
|
-
K: ["** **", "** ** ", "**** ", "** ** ", "** **"],
|
|
134
|
-
" ": [" ", " ", " ", " ", " "],
|
|
135
|
-
};
|
|
136
|
-
const text = "TRUSTLESS WORK";
|
|
137
|
-
const rows = ["", "", "", "", ""];
|
|
138
|
-
for (const ch of text) {
|
|
139
|
-
const glyph = map[ch] || map[" "];
|
|
140
|
-
for (let i = 0; i < 5; i++) {
|
|
141
|
-
rows[i] += glyph[i] + " ";
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
console.log("\n\n");
|
|
145
|
-
for (const line of rows) {
|
|
146
|
-
console.log(`${COLORS.blueTW}${line}${COLORS.reset}`);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function readProjectPackageJson() {
|
|
151
|
-
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
152
|
-
if (!fs.existsSync(pkgPath)) return null;
|
|
153
|
-
try {
|
|
154
|
-
return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
155
|
-
} catch {
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function installDeps({ dependencies = {}, devDependencies = {} }) {
|
|
161
|
-
const pm = detectPM();
|
|
162
|
-
const BLOCKED = new Set([
|
|
163
|
-
"tailwindcss",
|
|
164
|
-
"@tailwindcss/cli",
|
|
165
|
-
"@tailwindcss/postcss",
|
|
166
|
-
"@tailwindcss/vite",
|
|
167
|
-
"postcss",
|
|
168
|
-
"autoprefixer",
|
|
169
|
-
"postcss-import",
|
|
170
|
-
]);
|
|
171
|
-
const depList = Object.entries(dependencies)
|
|
172
|
-
.filter(([k]) => !BLOCKED.has(k))
|
|
173
|
-
.map(([k, v]) => `${k}@${v}`);
|
|
174
|
-
const devList = Object.entries(devDependencies)
|
|
175
|
-
.filter(([k]) => !BLOCKED.has(k))
|
|
176
|
-
.map(([k, v]) => `${k}@${v}`);
|
|
177
|
-
|
|
178
|
-
if (depList.length) {
|
|
179
|
-
if (pm === "pnpm") run("pnpm", ["add", ...depList]);
|
|
180
|
-
else if (pm === "yarn") run("yarn", ["add", ...depList]);
|
|
181
|
-
else if (pm === "bun") run("bun", ["add", ...depList]);
|
|
182
|
-
else run("npm", ["install", ...depList]);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (devList.length) {
|
|
186
|
-
if (pm === "pnpm") run("pnpm", ["add", "-D", ...devList]);
|
|
187
|
-
else if (pm === "yarn") run("yarn", ["add", "-D", ...devList]);
|
|
188
|
-
else if (pm === "bun") run("bun", ["add", "-d", ...devList]);
|
|
189
|
-
else run("npm", ["install", "-D", ...devList]);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function loadConfig() {
|
|
194
|
-
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
195
|
-
if (fs.existsSync(cfgPath)) {
|
|
196
|
-
try {
|
|
197
|
-
return JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
198
|
-
} catch (e) {
|
|
199
|
-
console.warn("⚠️ Failed to parse .twblocks.json, ignoring.");
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return {};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function parseFlags(argv) {
|
|
206
|
-
const flags = {};
|
|
207
|
-
for (let i = 0; i < argv.length; i++) {
|
|
208
|
-
const a = argv[i];
|
|
209
|
-
if (a.startsWith("--ui-base=")) {
|
|
210
|
-
flags.uiBase = a.split("=").slice(1).join("=");
|
|
211
|
-
} else if (a === "--ui-base") {
|
|
212
|
-
flags.uiBase = argv[i + 1];
|
|
213
|
-
i++;
|
|
214
|
-
} else if (a === "--install" || a === "-i") {
|
|
215
|
-
flags.install = true;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return flags;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
|
|
222
|
-
const srcFile = path.join(TEMPLATES_DIR, `${name}.tsx`);
|
|
223
|
-
const srcDir = path.join(TEMPLATES_DIR, name);
|
|
224
|
-
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
225
|
-
|
|
226
|
-
const config = loadConfig();
|
|
227
|
-
const effectiveUiBase = uiBase || config.uiBase || "@/components/ui";
|
|
228
|
-
|
|
229
|
-
function writeTransformed(srcPath, destPath) {
|
|
230
|
-
const raw = fs.readFileSync(srcPath, "utf8");
|
|
231
|
-
const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
|
|
232
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
233
|
-
fs.writeFileSync(destPath, transformed, "utf8");
|
|
234
|
-
console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()) {
|
|
238
|
-
const skipDetails =
|
|
239
|
-
name === "escrows/escrows-by-role" ||
|
|
240
|
-
name === "escrows/escrows-by-signer" ||
|
|
241
|
-
name === "escrows";
|
|
242
|
-
// Copy directory recursively
|
|
243
|
-
const destDir = path.join(outRoot, name);
|
|
244
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
245
|
-
const stack = [""];
|
|
246
|
-
while (stack.length) {
|
|
247
|
-
const rel = stack.pop();
|
|
248
|
-
const current = path.join(srcDir, rel);
|
|
249
|
-
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
250
|
-
for (const entry of entries) {
|
|
251
|
-
const entryRel = path.join(rel, entry.name);
|
|
252
|
-
// Skip copying any shared directory at any depth
|
|
253
|
-
const parts = entryRel.split(path.sep);
|
|
254
|
-
if (parts.includes("shared")) {
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
if (skipDetails) {
|
|
258
|
-
const top = parts[0] || "";
|
|
259
|
-
const firstTwo = parts.slice(0, 2).join(path.sep);
|
|
260
|
-
if (
|
|
261
|
-
top === "details" ||
|
|
262
|
-
firstTwo === path.join("escrows-by-role", "details") ||
|
|
263
|
-
firstTwo === path.join("escrows-by-signer", "details")
|
|
264
|
-
) {
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
const entrySrc = path.join(srcDir, entryRel);
|
|
269
|
-
const entryDest = path.join(destDir, entryRel);
|
|
270
|
-
if (entry.isDirectory()) {
|
|
271
|
-
stack.push(entryRel);
|
|
272
|
-
continue;
|
|
273
|
-
}
|
|
274
|
-
// Only process text files (.ts, .tsx, .js, .jsx)
|
|
275
|
-
if (/\.(tsx?|jsx?)$/i.test(entry.name)) {
|
|
276
|
-
writeTransformed(entrySrc, entryDest);
|
|
277
|
-
} else {
|
|
278
|
-
fs.mkdirSync(path.dirname(entryDest), { recursive: true });
|
|
279
|
-
fs.copyFileSync(entrySrc, entryDest);
|
|
280
|
-
console.log(`✅ ${path.relative(PROJECT_ROOT, entryDest)} created`);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Post-copy: materialize shared initialize-escrow files into dialog/form
|
|
286
|
-
try {
|
|
287
|
-
const isSingleReleaseInitRoot =
|
|
288
|
-
name === "escrows/single-release/initialize-escrow";
|
|
289
|
-
const isSingleReleaseInitDialog =
|
|
290
|
-
name === "escrows/single-release/initialize-escrow/dialog";
|
|
291
|
-
const isSingleReleaseInitForm =
|
|
292
|
-
name === "escrows/single-release/initialize-escrow/form";
|
|
293
|
-
|
|
294
|
-
const srcSharedDir = path.join(
|
|
295
|
-
TEMPLATES_DIR,
|
|
296
|
-
"escrows",
|
|
297
|
-
"single-release",
|
|
298
|
-
"initialize-escrow",
|
|
299
|
-
"shared"
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
function copySharedInto(targetDir) {
|
|
303
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
304
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
305
|
-
for (const entry of entries) {
|
|
306
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
307
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
308
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
309
|
-
writeTransformed(entrySrc, entryDest);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (isSingleReleaseInitRoot) {
|
|
314
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
315
|
-
copySharedInto(path.join(destDir, "form"));
|
|
316
|
-
} else if (isSingleReleaseInitDialog) {
|
|
317
|
-
copySharedInto(destDir);
|
|
318
|
-
} else if (isSingleReleaseInitForm) {
|
|
319
|
-
copySharedInto(destDir);
|
|
320
|
-
}
|
|
321
|
-
} catch (e) {
|
|
322
|
-
console.warn(
|
|
323
|
-
"⚠️ Failed to materialize shared initialize-escrow files:",
|
|
324
|
-
e?.message || e
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
try {
|
|
329
|
-
const isSingleReleaseInitRoot =
|
|
330
|
-
name === "escrows/single-release/approve-milestone";
|
|
331
|
-
const isSingleReleaseInitDialog =
|
|
332
|
-
name === "escrows/single-release/approve-milestone/dialog";
|
|
333
|
-
const isSingleReleaseInitForm =
|
|
334
|
-
name === "escrows/single-release/approve-milestone/form";
|
|
335
|
-
|
|
336
|
-
const srcSharedDir = path.join(
|
|
337
|
-
TEMPLATES_DIR,
|
|
338
|
-
"escrows",
|
|
339
|
-
"single-release",
|
|
340
|
-
"approve-milestone",
|
|
341
|
-
"shared"
|
|
342
|
-
);
|
|
343
|
-
|
|
344
|
-
function copySharedInto(targetDir) {
|
|
345
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
346
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
347
|
-
for (const entry of entries) {
|
|
348
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
349
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
350
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
351
|
-
writeTransformed(entrySrc, entryDest);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (isSingleReleaseInitRoot) {
|
|
356
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
357
|
-
copySharedInto(path.join(destDir, "form"));
|
|
358
|
-
} else if (isSingleReleaseInitDialog) {
|
|
359
|
-
copySharedInto(destDir);
|
|
360
|
-
} else if (isSingleReleaseInitForm) {
|
|
361
|
-
copySharedInto(destDir);
|
|
362
|
-
}
|
|
363
|
-
} catch (e) {
|
|
364
|
-
console.warn(
|
|
365
|
-
"⚠️ Failed to materialize shared approve-milestone files:",
|
|
366
|
-
e?.message || e
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const isSingleReleaseInitRoot =
|
|
372
|
-
name === "escrows/single-release/change-milestone-status";
|
|
373
|
-
const isSingleReleaseInitDialog =
|
|
374
|
-
name === "escrows/single-release/change-milestone-status/dialog";
|
|
375
|
-
const isSingleReleaseInitForm =
|
|
376
|
-
name === "escrows/single-release/change-milestone-status/form";
|
|
377
|
-
|
|
378
|
-
const srcSharedDir = path.join(
|
|
379
|
-
TEMPLATES_DIR,
|
|
380
|
-
"escrows",
|
|
381
|
-
"single-release",
|
|
382
|
-
"change-milestone-status",
|
|
383
|
-
"shared"
|
|
384
|
-
);
|
|
385
|
-
|
|
386
|
-
function copySharedInto(targetDir) {
|
|
387
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
388
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
389
|
-
for (const entry of entries) {
|
|
390
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
391
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
392
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
393
|
-
writeTransformed(entrySrc, entryDest);
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (isSingleReleaseInitRoot) {
|
|
398
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
399
|
-
copySharedInto(path.join(destDir, "form"));
|
|
400
|
-
} else if (isSingleReleaseInitDialog) {
|
|
401
|
-
copySharedInto(destDir);
|
|
402
|
-
} else if (isSingleReleaseInitForm) {
|
|
403
|
-
copySharedInto(destDir);
|
|
404
|
-
}
|
|
405
|
-
} catch (e) {
|
|
406
|
-
console.warn(
|
|
407
|
-
"⚠️ Failed to materialize shared change-milestone-status files:",
|
|
408
|
-
e?.message || e
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
try {
|
|
413
|
-
const isSingleReleaseInitRoot =
|
|
414
|
-
name === "escrows/single-release/fund-escrow";
|
|
415
|
-
const isSingleReleaseInitDialog =
|
|
416
|
-
name === "escrows/single-release/fund-escrow/dialog";
|
|
417
|
-
const isSingleReleaseInitForm =
|
|
418
|
-
name === "escrows/single-release/fund-escrow/form";
|
|
419
|
-
|
|
420
|
-
const srcSharedDir = path.join(
|
|
421
|
-
TEMPLATES_DIR,
|
|
422
|
-
"escrows",
|
|
423
|
-
"single-release",
|
|
424
|
-
"fund-escrow",
|
|
425
|
-
"shared"
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
function copySharedInto(targetDir) {
|
|
429
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
430
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
431
|
-
for (const entry of entries) {
|
|
432
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
433
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
434
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
435
|
-
writeTransformed(entrySrc, entryDest);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (isSingleReleaseInitRoot) {
|
|
440
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
441
|
-
copySharedInto(path.join(destDir, "form"));
|
|
442
|
-
} else if (isSingleReleaseInitDialog) {
|
|
443
|
-
copySharedInto(destDir);
|
|
444
|
-
} else if (isSingleReleaseInitForm) {
|
|
445
|
-
copySharedInto(destDir);
|
|
446
|
-
}
|
|
447
|
-
} catch (e) {
|
|
448
|
-
console.warn(
|
|
449
|
-
"⚠️ Failed to materialize shared fund-escrow files:",
|
|
450
|
-
e?.message || e
|
|
451
|
-
);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
const isSingleReleaseInitRoot =
|
|
456
|
-
name === "escrows/single-release/resolve-dispute";
|
|
457
|
-
const isSingleReleaseInitDialog =
|
|
458
|
-
name === "escrows/single-release/resolve-dispute/dialog";
|
|
459
|
-
const isSingleReleaseInitForm =
|
|
460
|
-
name === "escrows/single-release/resolve-dispute/form";
|
|
461
|
-
|
|
462
|
-
const srcSharedDir = path.join(
|
|
463
|
-
TEMPLATES_DIR,
|
|
464
|
-
"escrows",
|
|
465
|
-
"single-release",
|
|
466
|
-
"resolve-dispute",
|
|
467
|
-
"shared"
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
function copySharedInto(targetDir) {
|
|
471
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
472
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
473
|
-
for (const entry of entries) {
|
|
474
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
475
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
476
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
477
|
-
writeTransformed(entrySrc, entryDest);
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (isSingleReleaseInitRoot) {
|
|
482
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
483
|
-
copySharedInto(path.join(destDir, "form"));
|
|
484
|
-
} else if (isSingleReleaseInitDialog) {
|
|
485
|
-
copySharedInto(destDir);
|
|
486
|
-
} else if (isSingleReleaseInitForm) {
|
|
487
|
-
copySharedInto(destDir);
|
|
488
|
-
}
|
|
489
|
-
} catch (e) {
|
|
490
|
-
console.warn(
|
|
491
|
-
"⚠️ Failed to materialize shared resolve-dispute files:",
|
|
492
|
-
e?.message || e
|
|
493
|
-
);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
try {
|
|
497
|
-
const isSingleReleaseInitRoot =
|
|
498
|
-
name === "escrows/single-release/update-escrow";
|
|
499
|
-
const isSingleReleaseInitDialog =
|
|
500
|
-
name === "escrows/single-release/update-escrow/dialog";
|
|
501
|
-
const isSingleReleaseInitForm =
|
|
502
|
-
name === "escrows/single-release/update-escrow/form";
|
|
503
|
-
|
|
504
|
-
const srcSharedDir = path.join(
|
|
505
|
-
TEMPLATES_DIR,
|
|
506
|
-
"escrows",
|
|
507
|
-
"single-release",
|
|
508
|
-
"update-escrow",
|
|
509
|
-
"shared"
|
|
510
|
-
);
|
|
511
|
-
|
|
512
|
-
function copySharedInto(targetDir) {
|
|
513
|
-
if (!fs.existsSync(srcSharedDir)) return;
|
|
514
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
515
|
-
for (const entry of entries) {
|
|
516
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
517
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
518
|
-
const entryDest = path.join(targetDir, entry.name);
|
|
519
|
-
writeTransformed(entrySrc, entryDest);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (isSingleReleaseInitRoot) {
|
|
524
|
-
copySharedInto(path.join(destDir, "dialog"));
|
|
525
|
-
copySharedInto(path.join(destDir, "form"));
|
|
526
|
-
} else if (isSingleReleaseInitDialog) {
|
|
527
|
-
copySharedInto(destDir);
|
|
528
|
-
} else if (isSingleReleaseInitForm) {
|
|
529
|
-
copySharedInto(destDir);
|
|
530
|
-
}
|
|
531
|
-
} catch (e) {
|
|
532
|
-
console.warn(
|
|
533
|
-
"⚠️ Failed to materialize shared update-escrow files:",
|
|
534
|
-
e?.message || e
|
|
535
|
-
);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// If adding the whole single-release bundle, materialize all shared files
|
|
539
|
-
try {
|
|
540
|
-
if (name === "escrows/single-release") {
|
|
541
|
-
const modules = [
|
|
542
|
-
"initialize-escrow",
|
|
543
|
-
"approve-milestone",
|
|
544
|
-
"change-milestone-status",
|
|
545
|
-
"fund-escrow",
|
|
546
|
-
"resolve-dispute",
|
|
547
|
-
"update-escrow",
|
|
548
|
-
];
|
|
549
|
-
|
|
550
|
-
for (const mod of modules) {
|
|
551
|
-
const srcSharedDir = path.join(
|
|
552
|
-
TEMPLATES_DIR,
|
|
553
|
-
"escrows",
|
|
554
|
-
"single-release",
|
|
555
|
-
mod,
|
|
556
|
-
"shared"
|
|
557
|
-
);
|
|
558
|
-
if (!fs.existsSync(srcSharedDir)) continue;
|
|
559
|
-
|
|
560
|
-
const targets = [
|
|
561
|
-
path.join(destDir, mod, "dialog"),
|
|
562
|
-
path.join(destDir, mod, "form"),
|
|
563
|
-
];
|
|
564
|
-
|
|
565
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
566
|
-
for (const entry of entries) {
|
|
567
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
568
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
569
|
-
for (const t of targets) {
|
|
570
|
-
const entryDest = path.join(t, entry.name);
|
|
571
|
-
writeTransformed(entrySrc, entryDest);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
} catch (e) {
|
|
577
|
-
console.warn(
|
|
578
|
-
"⚠️ Failed to materialize shared files for single-release bundle:",
|
|
579
|
-
e?.message || e
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
// If adding the root escrows bundle, also materialize single-release shared files
|
|
584
|
-
try {
|
|
585
|
-
if (name === "escrows") {
|
|
586
|
-
const modules = [
|
|
587
|
-
"initialize-escrow",
|
|
588
|
-
"approve-milestone",
|
|
589
|
-
"change-milestone-status",
|
|
590
|
-
"fund-escrow",
|
|
591
|
-
"resolve-dispute",
|
|
592
|
-
"update-escrow",
|
|
593
|
-
];
|
|
594
|
-
|
|
595
|
-
const baseTarget = path.join(destDir, "single-release");
|
|
596
|
-
for (const mod of modules) {
|
|
597
|
-
const srcSharedDir = path.join(
|
|
598
|
-
TEMPLATES_DIR,
|
|
599
|
-
"escrows",
|
|
600
|
-
"single-release",
|
|
601
|
-
mod,
|
|
602
|
-
"shared"
|
|
603
|
-
);
|
|
604
|
-
if (!fs.existsSync(srcSharedDir)) continue;
|
|
605
|
-
|
|
606
|
-
const targets = [
|
|
607
|
-
path.join(baseTarget, mod, "dialog"),
|
|
608
|
-
path.join(baseTarget, mod, "form"),
|
|
609
|
-
];
|
|
610
|
-
|
|
611
|
-
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
612
|
-
for (const entry of entries) {
|
|
613
|
-
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
614
|
-
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
615
|
-
for (const t of targets) {
|
|
616
|
-
const entryDest = path.join(t, entry.name);
|
|
617
|
-
writeTransformed(entrySrc, entryDest);
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
} catch (e) {
|
|
623
|
-
console.warn(
|
|
624
|
-
"⚠️ Failed to materialize shared files for escrows root:",
|
|
625
|
-
e?.message || e
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
} else if (fs.existsSync(srcFile)) {
|
|
629
|
-
fs.mkdirSync(outRoot, { recursive: true });
|
|
630
|
-
const destFile = path.join(outRoot, name + ".tsx");
|
|
631
|
-
writeTransformed(srcFile, destFile);
|
|
632
|
-
} else {
|
|
633
|
-
console.error(`❌ The template "${name}" does not exist`);
|
|
634
|
-
process.exit(1);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (shouldInstall && fs.existsSync(GLOBAL_DEPS_FILE)) {
|
|
638
|
-
const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
|
|
639
|
-
installDeps(meta);
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function copySharedDetailsInto(targetRelativeDir, { uiBase } = {}) {
|
|
644
|
-
const srcDir = path.join(TEMPLATES_DIR, "escrows", "details");
|
|
645
|
-
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
646
|
-
const destDir = path.join(outRoot, targetRelativeDir);
|
|
647
|
-
const config = loadConfig();
|
|
648
|
-
const effectiveUiBase = uiBase || config.uiBase || "@/components/ui";
|
|
649
|
-
|
|
650
|
-
if (!fs.existsSync(srcDir)) return;
|
|
651
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
652
|
-
|
|
653
|
-
function writeTransformed(srcPath, destPath) {
|
|
654
|
-
const raw = fs.readFileSync(srcPath, "utf8");
|
|
655
|
-
const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
|
|
656
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
657
|
-
fs.writeFileSync(destPath, transformed, "utf8");
|
|
658
|
-
console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
const stack = [""];
|
|
662
|
-
while (stack.length) {
|
|
663
|
-
const rel = stack.pop();
|
|
664
|
-
const current = path.join(srcDir, rel);
|
|
665
|
-
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
666
|
-
for (const entry of entries) {
|
|
667
|
-
const entryRel = path.join(rel, entry.name);
|
|
668
|
-
const entrySrc = path.join(srcDir, entryRel);
|
|
669
|
-
const entryDest = path.join(destDir, entryRel);
|
|
670
|
-
if (entry.isDirectory()) {
|
|
671
|
-
stack.push(entryRel);
|
|
672
|
-
continue;
|
|
673
|
-
}
|
|
674
|
-
if (/\.(tsx?|jsx?)$/i.test(entry.name)) {
|
|
675
|
-
writeTransformed(entrySrc, entryDest);
|
|
676
|
-
} else {
|
|
677
|
-
fs.mkdirSync(path.dirname(entryDest), { recursive: true });
|
|
678
|
-
fs.copyFileSync(entrySrc, entryDest);
|
|
679
|
-
console.log(`✅ ${path.relative(PROJECT_ROOT, entryDest)} created`);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function copySharedRoleSignerHooks(kind = "both") {
|
|
686
|
-
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
687
|
-
|
|
688
|
-
const mappings = [];
|
|
689
|
-
if (kind === "both" || kind === "role") {
|
|
690
|
-
mappings.push({
|
|
691
|
-
src: path.join(
|
|
692
|
-
TEMPLATES_DIR,
|
|
693
|
-
"escrows",
|
|
694
|
-
"escrows-by-role",
|
|
695
|
-
"useEscrowsByRole.shared.ts"
|
|
696
|
-
),
|
|
697
|
-
dest: path.join(
|
|
698
|
-
outRoot,
|
|
699
|
-
"escrows",
|
|
700
|
-
"escrows-by-role",
|
|
701
|
-
"useEscrowsByRole.shared.ts"
|
|
702
|
-
),
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
if (kind === "both" || kind === "signer") {
|
|
706
|
-
mappings.push({
|
|
707
|
-
src: path.join(
|
|
708
|
-
TEMPLATES_DIR,
|
|
709
|
-
"escrows",
|
|
710
|
-
"escrows-by-signer",
|
|
711
|
-
"useEscrowsBySigner.shared.ts"
|
|
712
|
-
),
|
|
713
|
-
dest: path.join(
|
|
714
|
-
outRoot,
|
|
715
|
-
"escrows",
|
|
716
|
-
"escrows-by-signer",
|
|
717
|
-
"useEscrowsBySigner.shared.ts"
|
|
718
|
-
),
|
|
719
|
-
});
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
for (const { src, dest } of mappings) {
|
|
723
|
-
if (!fs.existsSync(src)) continue;
|
|
724
|
-
const raw = fs.readFileSync(src, "utf8");
|
|
725
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
726
|
-
fs.writeFileSync(dest, raw, "utf8");
|
|
727
|
-
console.log(`✅ ${path.relative(PROJECT_ROOT, dest)} created`);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
function findLayoutFile() {
|
|
732
|
-
const candidates = [
|
|
733
|
-
path.join(PROJECT_ROOT, "app", "layout.tsx"),
|
|
734
|
-
path.join(PROJECT_ROOT, "app", "layout.ts"),
|
|
735
|
-
path.join(PROJECT_ROOT, "app", "layout.jsx"),
|
|
736
|
-
path.join(PROJECT_ROOT, "app", "layout.js"),
|
|
737
|
-
path.join(PROJECT_ROOT, "src", "app", "layout.tsx"),
|
|
738
|
-
path.join(PROJECT_ROOT, "src", "app", "layout.ts"),
|
|
739
|
-
path.join(PROJECT_ROOT, "src", "app", "layout.jsx"),
|
|
740
|
-
path.join(PROJECT_ROOT, "src", "app", "layout.js"),
|
|
741
|
-
];
|
|
742
|
-
return candidates.find((p) => fs.existsSync(p)) || null;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
function injectProvidersIntoLayout(
|
|
746
|
-
layoutPath,
|
|
747
|
-
{ reactQuery = false, trustless = false, wallet = false, escrow = false } = {}
|
|
748
|
-
) {
|
|
749
|
-
try {
|
|
750
|
-
let content = fs.readFileSync(layoutPath, "utf8");
|
|
751
|
-
|
|
752
|
-
const importRQ =
|
|
753
|
-
'import { ReactQueryClientProvider } from "@/components/tw-blocks/providers/ReactQueryClientProvider";\n';
|
|
754
|
-
const importTW =
|
|
755
|
-
'import { TrustlessWorkProvider } from "@/components/tw-blocks/providers/TrustlessWork";\n';
|
|
756
|
-
const importEscrow =
|
|
757
|
-
'import { EscrowProvider } from "@/components/tw-blocks/
|
|
758
|
-
const importWallet =
|
|
759
|
-
'import { WalletProvider } from "@/components/tw-blocks/wallet-kit/WalletProvider";\n';
|
|
760
|
-
const commentText =
|
|
761
|
-
"// Use these imports to wrap your application (<ReactQueryClientProvider>, <TrustlessWorkProvider>, <WalletProvider> y <EscrowProvider>)\n";
|
|
762
|
-
|
|
763
|
-
const needImport = (name) =>
|
|
764
|
-
!new RegExp(
|
|
765
|
-
`import\\s+[^;]*${name}[^;]*from\\s+['\"][^'\"]+['\"];?`
|
|
766
|
-
).test(content);
|
|
767
|
-
|
|
768
|
-
let importsToAdd = "";
|
|
769
|
-
if (reactQuery && needImport("ReactQueryClientProvider"))
|
|
770
|
-
importsToAdd += importRQ;
|
|
771
|
-
if (trustless && needImport("TrustlessWorkProvider"))
|
|
772
|
-
importsToAdd += importTW;
|
|
773
|
-
if (wallet && needImport("WalletProvider")) importsToAdd += importWallet;
|
|
774
|
-
if (escrow && needImport("EscrowProvider")) importsToAdd += importEscrow;
|
|
775
|
-
|
|
776
|
-
if (importsToAdd) {
|
|
777
|
-
const importStmtRegex = /^import.*;\s*$/gm;
|
|
778
|
-
let last = null;
|
|
779
|
-
for (const m of content.matchAll(importStmtRegex)) last = m;
|
|
780
|
-
if (last) {
|
|
781
|
-
const idx = last.index + last[0].length;
|
|
782
|
-
content =
|
|
783
|
-
content.slice(0, idx) +
|
|
784
|
-
"\n" +
|
|
785
|
-
importsToAdd +
|
|
786
|
-
commentText +
|
|
787
|
-
content.slice(idx);
|
|
788
|
-
} else {
|
|
789
|
-
content = importsToAdd + commentText + content;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
const hasTag = (tag) => new RegExp(`<${tag}[\\s>]`).test(content);
|
|
794
|
-
const wrapInside = (containerTag, newTag) => {
|
|
795
|
-
const open = content.match(new RegExp(`<${containerTag}(\\s[^>]*)?>`));
|
|
796
|
-
if (!open) return false;
|
|
797
|
-
const openIdx = open.index + open[0].length;
|
|
798
|
-
const closeIdx = content.indexOf(`</${containerTag}>`, openIdx);
|
|
799
|
-
if (closeIdx === -1) return false;
|
|
800
|
-
content =
|
|
801
|
-
content.slice(0, openIdx) +
|
|
802
|
-
`\n<${newTag}>\n` +
|
|
803
|
-
content.slice(openIdx, closeIdx) +
|
|
804
|
-
`\n</${newTag}>\n` +
|
|
805
|
-
content.slice(closeIdx);
|
|
806
|
-
return true;
|
|
807
|
-
};
|
|
808
|
-
|
|
809
|
-
const ensureTag = (tag) => {
|
|
810
|
-
if (hasTag(tag)) return;
|
|
811
|
-
const bodyOpen = content.match(/<body[^>]*>/);
|
|
812
|
-
const bodyCloseIdx = content.lastIndexOf("</body>");
|
|
813
|
-
if (!bodyOpen || bodyCloseIdx === -1) return;
|
|
814
|
-
const bodyOpenIdx = bodyOpen.index + bodyOpen[0].length;
|
|
815
|
-
if (tag === "TrustlessWorkProvider") {
|
|
816
|
-
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
817
|
-
}
|
|
818
|
-
if (tag === "WalletProvider") {
|
|
819
|
-
if (wrapInside("TrustlessWorkProvider", tag)) return;
|
|
820
|
-
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
821
|
-
}
|
|
822
|
-
if (tag === "EscrowProvider") {
|
|
823
|
-
if (wrapInside("WalletProvider", tag)) return;
|
|
824
|
-
if (wrapInside("TrustlessWorkProvider", tag)) return;
|
|
825
|
-
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
826
|
-
}
|
|
827
|
-
content =
|
|
828
|
-
content.slice(0, bodyOpenIdx) +
|
|
829
|
-
`\n<${tag}>\n` +
|
|
830
|
-
content.slice(bodyOpenIdx, bodyCloseIdx) +
|
|
831
|
-
`\n</${tag}>\n` +
|
|
832
|
-
content.slice(bodyCloseIdx);
|
|
833
|
-
};
|
|
834
|
-
|
|
835
|
-
if (reactQuery) ensureTag("ReactQueryClientProvider");
|
|
836
|
-
if (trustless) ensureTag("TrustlessWorkProvider");
|
|
837
|
-
if (wallet) ensureTag("WalletProvider");
|
|
838
|
-
if (escrow) ensureTag("EscrowProvider");
|
|
839
|
-
|
|
840
|
-
fs.writeFileSync(layoutPath, content, "utf8");
|
|
841
|
-
logCheck(
|
|
842
|
-
`Updated ${path.relative(PROJECT_ROOT, layoutPath)} with providers`
|
|
843
|
-
);
|
|
844
|
-
} catch (e) {
|
|
845
|
-
console.error("❌ Failed to update layout with providers:", e.message);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
if (args[0] === "init") {
|
|
850
|
-
console.log("\n▶ Setting up shadcn/ui components...");
|
|
851
|
-
const doInit = await promptYesNo("Run shadcn init now?", true);
|
|
852
|
-
if (doInit) {
|
|
853
|
-
run("npx", ["shadcn@latest", "init"]);
|
|
854
|
-
} else {
|
|
855
|
-
console.log("\x1b[90m– Skipped shadcn init\x1b[0m");
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
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)?",
|
|
860
|
-
true
|
|
861
|
-
);
|
|
862
|
-
if (addShadcn) {
|
|
863
|
-
await withSpinner("Installing shadcn/ui components", async () => {
|
|
864
|
-
await runAsync("npx", [
|
|
865
|
-
"shadcn@latest",
|
|
866
|
-
"add",
|
|
867
|
-
"button",
|
|
868
|
-
"input",
|
|
869
|
-
"form",
|
|
870
|
-
"card",
|
|
871
|
-
"sonner",
|
|
872
|
-
"checkbox",
|
|
873
|
-
"dialog",
|
|
874
|
-
"textarea",
|
|
875
|
-
"sonner",
|
|
876
|
-
"select",
|
|
877
|
-
"table",
|
|
878
|
-
"calendar",
|
|
879
|
-
"popover",
|
|
880
|
-
"separator",
|
|
881
|
-
"calendar-05",
|
|
882
|
-
"badge",
|
|
883
|
-
"sheet",
|
|
884
|
-
"tabs",
|
|
885
|
-
"avatar",
|
|
886
|
-
]);
|
|
887
|
-
});
|
|
888
|
-
} else {
|
|
889
|
-
console.log("\x1b[90m– Skipped adding shadcn components\x1b[0m");
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
if (!fs.existsSync(GLOBAL_DEPS_FILE)) {
|
|
893
|
-
console.error("❌ deps.json not found in templates/");
|
|
894
|
-
process.exit(1);
|
|
895
|
-
}
|
|
896
|
-
const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
|
|
897
|
-
const installLibs = await promptYesNo(
|
|
898
|
-
"Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit.tech/stellar-wallets-kit, react-day-picker & zod) dependencies now?",
|
|
899
|
-
true
|
|
900
|
-
);
|
|
901
|
-
if (installLibs) {
|
|
902
|
-
await withSpinner("Installing required dependencies", async () => {
|
|
903
|
-
installDeps(meta);
|
|
904
|
-
});
|
|
905
|
-
} else {
|
|
906
|
-
console.log("\x1b[90m– Skipped installing required dependencies\x1b[0m");
|
|
907
|
-
}
|
|
908
|
-
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
909
|
-
if (!fs.existsSync(cfgPath)) {
|
|
910
|
-
fs.writeFileSync(
|
|
911
|
-
cfgPath,
|
|
912
|
-
JSON.stringify({ uiBase: "@/components/ui" }, null, 2)
|
|
913
|
-
);
|
|
914
|
-
console.log(
|
|
915
|
-
`\x1b[32m✔\x1b[0m Created ${path.relative(
|
|
916
|
-
PROJECT_ROOT,
|
|
917
|
-
cfgPath
|
|
918
|
-
)} with default uiBase`
|
|
919
|
-
);
|
|
920
|
-
}
|
|
921
|
-
console.log("\x1b[32m✔\x1b[0m shadcn/ui components step completed");
|
|
922
|
-
|
|
923
|
-
const wantProviders = await promptYesNo(
|
|
924
|
-
"Install TanStack Query and Trustless Work providers and wrap app/layout with them?",
|
|
925
|
-
true
|
|
926
|
-
);
|
|
927
|
-
if (wantProviders) {
|
|
928
|
-
await withSpinner("Installing providers", async () => {
|
|
929
|
-
copyTemplate("providers");
|
|
930
|
-
});
|
|
931
|
-
const layoutPath = findLayoutFile();
|
|
932
|
-
if (layoutPath) {
|
|
933
|
-
await withSpinner("Updating app/layout with providers", async () => {
|
|
934
|
-
injectProvidersIntoLayout(layoutPath, {
|
|
935
|
-
reactQuery: true,
|
|
936
|
-
trustless: true,
|
|
937
|
-
});
|
|
938
|
-
});
|
|
939
|
-
} else {
|
|
940
|
-
console.warn(
|
|
941
|
-
"⚠️ Could not find app/layout file. Skipped automatic wiring."
|
|
942
|
-
);
|
|
943
|
-
}
|
|
944
|
-
} else {
|
|
945
|
-
console.log("\x1b[90m– Skipped installing providers\x1b[0m");
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
printBannerTRUSTLESSWORK();
|
|
949
|
-
console.log("\n\nResources");
|
|
950
|
-
console.log("- " + oscHyperlink("Website", "https://trustlesswork.com"));
|
|
951
|
-
console.log(
|
|
952
|
-
"- " + oscHyperlink("Documentation", "https://docs.trustlesswork.com")
|
|
953
|
-
);
|
|
954
|
-
console.log("- " + oscHyperlink("Demo", "https://demo.trustlesswork.com"));
|
|
955
|
-
console.log(
|
|
956
|
-
"- " + oscHyperlink("Backoffice", "https://dapp.trustlesswork.com")
|
|
957
|
-
);
|
|
958
|
-
console.log(
|
|
959
|
-
"- " + oscHyperlink("GitHub", "https://github.com/trustless-work")
|
|
960
|
-
);
|
|
961
|
-
console.log(
|
|
962
|
-
"- " + oscHyperlink("Escrow Viewer", "https://viewer.trustlesswork.com")
|
|
963
|
-
);
|
|
964
|
-
console.log(
|
|
965
|
-
"- " + oscHyperlink("Telegram", "https://t.me/+kmr8tGegxLU0NTA5")
|
|
966
|
-
);
|
|
967
|
-
console.log(
|
|
968
|
-
"- " +
|
|
969
|
-
oscHyperlink(
|
|
970
|
-
"LinkedIn",
|
|
971
|
-
"https://www.linkedin.com/company/trustlesswork/posts/?feedView=all"
|
|
972
|
-
)
|
|
973
|
-
);
|
|
974
|
-
console.log("- " + oscHyperlink("X", "https://x.com/TrustlessWork"));
|
|
975
|
-
} else if (args[0] === "add" && args[1]) {
|
|
976
|
-
const flags = parseFlags(args.slice(2));
|
|
977
|
-
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
978
|
-
if (!fs.existsSync(cfgPath)) {
|
|
979
|
-
console.error(
|
|
980
|
-
"❌ Missing initial setup. Run 'trustless-work init' first to install dependencies and create .twblocks.json (uiBase)."
|
|
981
|
-
);
|
|
982
|
-
console.error(
|
|
983
|
-
" After init, re-run: trustless-work add " +
|
|
984
|
-
args[1] +
|
|
985
|
-
(flags.uiBase ? ' --ui-base "' + flags.uiBase + '"' : "")
|
|
986
|
-
);
|
|
987
|
-
process.exit(1);
|
|
988
|
-
}
|
|
989
|
-
copyTemplate(args[1], {
|
|
990
|
-
uiBase: flags.uiBase,
|
|
991
|
-
shouldInstall: !!flags.install,
|
|
992
|
-
});
|
|
993
|
-
|
|
994
|
-
// Post-add wiring for specific templates
|
|
995
|
-
const layoutPath = findLayoutFile();
|
|
996
|
-
if (layoutPath) {
|
|
997
|
-
if (args[1] === "wallet-kit" || args[1].startsWith("wallet-kit/")) {
|
|
998
|
-
injectProvidersIntoLayout(layoutPath, { wallet: true });
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
---
|
|
1056
|
-
trustless-work
|
|
1057
|
-
|
|
1058
|
-
---
|
|
1059
|
-
trustless-work add
|
|
1060
|
-
|
|
1061
|
-
---
|
|
1062
|
-
trustless-work add
|
|
1063
|
-
|
|
1064
|
-
---
|
|
1065
|
-
trustless-work add
|
|
1066
|
-
|
|
1067
|
-
---
|
|
1068
|
-
trustless-work add
|
|
1069
|
-
|
|
1070
|
-
---
|
|
1071
|
-
trustless-work add
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
trustless-work add escrows/
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
trustless-work add escrows/
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
---
|
|
1085
|
-
trustless-work add escrows/
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
trustless-work add escrows/
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
trustless-work add escrows/single-release
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
- trustless-work add escrows/single-release/
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
- trustless-work add escrows/single-release/
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
- trustless-work add escrows/single-release/
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
- trustless-work add escrows/single-release/
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
- trustless-work add escrows/single-release/
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
- trustless-work add escrows/single-release/
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
- trustless-work add escrows/single-release/
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
- trustless-work add escrows/single-release/
|
|
1123
|
-
- trustless-work add escrows/single-release/
|
|
1124
|
-
|
|
1125
|
-
---
|
|
1126
|
-
- trustless-work add escrows/single-release/
|
|
1127
|
-
- trustless-work add escrows/single-release/
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
--- Release escrow ---
|
|
1131
|
-
- trustless-work add escrows/single-release/release-escrow
|
|
1132
|
-
- trustless-work add escrows/single-release/release-escrow/button
|
|
1133
|
-
|
|
1134
|
-
--- Dispute escrow ---
|
|
1135
|
-
- trustless-work add escrows/single-release/dispute-escrow
|
|
1136
|
-
- trustless-work add escrows/single-release/dispute-escrow/button
|
|
1137
|
-
`);
|
|
1138
|
-
}
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
AUTHOR: @trustless-work / Joel Vargas
|
|
5
|
+
COPYRIGHT: 2025 Trustless Work
|
|
6
|
+
LICENSE: MIT
|
|
7
|
+
VERSION: 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
13
|
+
import { spawnSync, spawn } from "node:child_process";
|
|
14
|
+
import readline from "node:readline";
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const PROJECT_ROOT = process.cwd();
|
|
20
|
+
const TEMPLATES_DIR = path.join(__dirname, "..", "templates");
|
|
21
|
+
const GLOBAL_DEPS_FILE = path.join(TEMPLATES_DIR, "deps.json");
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
|
|
25
|
+
function detectPM() {
|
|
26
|
+
if (fs.existsSync(path.join(PROJECT_ROOT, "pnpm-lock.yaml"))) return "pnpm";
|
|
27
|
+
if (fs.existsSync(path.join(PROJECT_ROOT, "yarn.lock"))) return "yarn";
|
|
28
|
+
if (fs.existsSync(path.join(PROJECT_ROOT, "bun.lockb"))) return "bun";
|
|
29
|
+
return "npm";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function run(cmd, args) {
|
|
33
|
+
const r = spawnSync(cmd, args.filter(Boolean), {
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
cwd: PROJECT_ROOT,
|
|
36
|
+
shell: true,
|
|
37
|
+
});
|
|
38
|
+
if (r.status !== 0) process.exit(r.status ?? 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function tryRun(cmd, args, errorMessage) {
|
|
42
|
+
const r = spawnSync(cmd, args.filter(Boolean), {
|
|
43
|
+
stdio: "inherit",
|
|
44
|
+
cwd: PROJECT_ROOT,
|
|
45
|
+
shell: true,
|
|
46
|
+
});
|
|
47
|
+
if (r.status !== 0) {
|
|
48
|
+
console.error(errorMessage);
|
|
49
|
+
process.exit(r.status ?? 1);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function runAsync(cmd, args) {
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const child = spawn(cmd, args.filter(Boolean), {
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
cwd: PROJECT_ROOT,
|
|
58
|
+
shell: true,
|
|
59
|
+
});
|
|
60
|
+
child.on("close", (code) => {
|
|
61
|
+
if (code === 0) resolve();
|
|
62
|
+
else reject(new Error(`${cmd} exited with code ${code}`));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const COLORS = {
|
|
68
|
+
reset: "\x1b[0m",
|
|
69
|
+
green: "\x1b[32m",
|
|
70
|
+
gray: "\x1b[90m",
|
|
71
|
+
blueTW: "\x1b[38;2;0;107;228m",
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
function logCheck(message) {
|
|
75
|
+
console.log(`${COLORS.green}✔${COLORS.reset} ${message}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function startSpinner(message) {
|
|
79
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
80
|
+
let i = 0;
|
|
81
|
+
process.stdout.write(`${frames[0]} ${message}`);
|
|
82
|
+
const timer = setInterval(() => {
|
|
83
|
+
i = (i + 1) % frames.length;
|
|
84
|
+
process.stdout.write(`\r${frames[i]} ${message}`);
|
|
85
|
+
}, 80);
|
|
86
|
+
return () => {
|
|
87
|
+
clearInterval(timer);
|
|
88
|
+
process.stdout.write("\r");
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function withSpinner(message, fn) {
|
|
93
|
+
const stop = startSpinner(message);
|
|
94
|
+
try {
|
|
95
|
+
await fn();
|
|
96
|
+
stop();
|
|
97
|
+
logCheck(message);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
stop();
|
|
100
|
+
throw err;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function promptYesNo(question, def = true) {
|
|
105
|
+
const rl = readline.createInterface({
|
|
106
|
+
input: process.stdin,
|
|
107
|
+
output: process.stdout,
|
|
108
|
+
});
|
|
109
|
+
const suffix = def ? "(Y/n)" : "(y/N)";
|
|
110
|
+
const answer = await new Promise((res) =>
|
|
111
|
+
rl.question(`${question} ${suffix} `, (ans) => res(ans))
|
|
112
|
+
);
|
|
113
|
+
rl.close();
|
|
114
|
+
const a = String(answer).trim().toLowerCase();
|
|
115
|
+
if (!a) return def;
|
|
116
|
+
return a.startsWith("y");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function oscHyperlink(text, url) {
|
|
120
|
+
return `\x1b]8;;${url}\x1b\\${text}\x1b]8;;\x1b\\`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function printBannerTRUSTLESSWORK() {
|
|
124
|
+
const map = {
|
|
125
|
+
T: ["******", " ** ", " ** ", " ** ", " ** "],
|
|
126
|
+
R: ["***** ", "** **", "***** ", "** ** ", "** **"],
|
|
127
|
+
U: ["** **", "** **", "** **", "** **", " **** "],
|
|
128
|
+
S: [" **** ", "** ", " **** ", " **", " **** "],
|
|
129
|
+
L: ["** ", "** ", "** ", "** ", "******"],
|
|
130
|
+
E: ["******", "** ", "***** ", "** ", "******"],
|
|
131
|
+
W: ["** **", "** **", "** * **", "*** ***", "** **"],
|
|
132
|
+
O: [" **** ", "** **", "** **", "** **", " **** "],
|
|
133
|
+
K: ["** **", "** ** ", "**** ", "** ** ", "** **"],
|
|
134
|
+
" ": [" ", " ", " ", " ", " "],
|
|
135
|
+
};
|
|
136
|
+
const text = "TRUSTLESS WORK";
|
|
137
|
+
const rows = ["", "", "", "", ""];
|
|
138
|
+
for (const ch of text) {
|
|
139
|
+
const glyph = map[ch] || map[" "];
|
|
140
|
+
for (let i = 0; i < 5; i++) {
|
|
141
|
+
rows[i] += glyph[i] + " ";
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.log("\n\n");
|
|
145
|
+
for (const line of rows) {
|
|
146
|
+
console.log(`${COLORS.blueTW}${line}${COLORS.reset}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readProjectPackageJson() {
|
|
151
|
+
const pkgPath = path.join(PROJECT_ROOT, "package.json");
|
|
152
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
153
|
+
try {
|
|
154
|
+
return JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
155
|
+
} catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function installDeps({ dependencies = {}, devDependencies = {} }) {
|
|
161
|
+
const pm = detectPM();
|
|
162
|
+
const BLOCKED = new Set([
|
|
163
|
+
"tailwindcss",
|
|
164
|
+
"@tailwindcss/cli",
|
|
165
|
+
"@tailwindcss/postcss",
|
|
166
|
+
"@tailwindcss/vite",
|
|
167
|
+
"postcss",
|
|
168
|
+
"autoprefixer",
|
|
169
|
+
"postcss-import",
|
|
170
|
+
]);
|
|
171
|
+
const depList = Object.entries(dependencies)
|
|
172
|
+
.filter(([k]) => !BLOCKED.has(k))
|
|
173
|
+
.map(([k, v]) => `${k}@${v}`);
|
|
174
|
+
const devList = Object.entries(devDependencies)
|
|
175
|
+
.filter(([k]) => !BLOCKED.has(k))
|
|
176
|
+
.map(([k, v]) => `${k}@${v}`);
|
|
177
|
+
|
|
178
|
+
if (depList.length) {
|
|
179
|
+
if (pm === "pnpm") run("pnpm", ["add", ...depList]);
|
|
180
|
+
else if (pm === "yarn") run("yarn", ["add", ...depList]);
|
|
181
|
+
else if (pm === "bun") run("bun", ["add", ...depList]);
|
|
182
|
+
else run("npm", ["install", ...depList]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (devList.length) {
|
|
186
|
+
if (pm === "pnpm") run("pnpm", ["add", "-D", ...devList]);
|
|
187
|
+
else if (pm === "yarn") run("yarn", ["add", "-D", ...devList]);
|
|
188
|
+
else if (pm === "bun") run("bun", ["add", "-d", ...devList]);
|
|
189
|
+
else run("npm", ["install", "-D", ...devList]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function loadConfig() {
|
|
194
|
+
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
195
|
+
if (fs.existsSync(cfgPath)) {
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(fs.readFileSync(cfgPath, "utf8"));
|
|
198
|
+
} catch (e) {
|
|
199
|
+
console.warn("⚠️ Failed to parse .twblocks.json, ignoring.");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return {};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function parseFlags(argv) {
|
|
206
|
+
const flags = {};
|
|
207
|
+
for (let i = 0; i < argv.length; i++) {
|
|
208
|
+
const a = argv[i];
|
|
209
|
+
if (a.startsWith("--ui-base=")) {
|
|
210
|
+
flags.uiBase = a.split("=").slice(1).join("=");
|
|
211
|
+
} else if (a === "--ui-base") {
|
|
212
|
+
flags.uiBase = argv[i + 1];
|
|
213
|
+
i++;
|
|
214
|
+
} else if (a === "--install" || a === "-i") {
|
|
215
|
+
flags.install = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return flags;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function copyTemplate(name, { uiBase, shouldInstall = false } = {}) {
|
|
222
|
+
const srcFile = path.join(TEMPLATES_DIR, `${name}.tsx`);
|
|
223
|
+
const srcDir = path.join(TEMPLATES_DIR, name);
|
|
224
|
+
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
225
|
+
|
|
226
|
+
const config = loadConfig();
|
|
227
|
+
const effectiveUiBase = uiBase || config.uiBase || "@/components/ui";
|
|
228
|
+
|
|
229
|
+
function writeTransformed(srcPath, destPath) {
|
|
230
|
+
const raw = fs.readFileSync(srcPath, "utf8");
|
|
231
|
+
const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
|
|
232
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
233
|
+
fs.writeFileSync(destPath, transformed, "utf8");
|
|
234
|
+
console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()) {
|
|
238
|
+
const skipDetails =
|
|
239
|
+
name === "escrows/escrows-by-role" ||
|
|
240
|
+
name === "escrows/escrows-by-signer" ||
|
|
241
|
+
name === "escrows";
|
|
242
|
+
// Copy directory recursively
|
|
243
|
+
const destDir = path.join(outRoot, name);
|
|
244
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
245
|
+
const stack = [""];
|
|
246
|
+
while (stack.length) {
|
|
247
|
+
const rel = stack.pop();
|
|
248
|
+
const current = path.join(srcDir, rel);
|
|
249
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const entryRel = path.join(rel, entry.name);
|
|
252
|
+
// Skip copying any shared directory at any depth
|
|
253
|
+
const parts = entryRel.split(path.sep);
|
|
254
|
+
if (parts.includes("shared")) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (skipDetails) {
|
|
258
|
+
const top = parts[0] || "";
|
|
259
|
+
const firstTwo = parts.slice(0, 2).join(path.sep);
|
|
260
|
+
if (
|
|
261
|
+
top === "details" ||
|
|
262
|
+
firstTwo === path.join("escrows-by-role", "details") ||
|
|
263
|
+
firstTwo === path.join("escrows-by-signer", "details")
|
|
264
|
+
) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const entrySrc = path.join(srcDir, entryRel);
|
|
269
|
+
const entryDest = path.join(destDir, entryRel);
|
|
270
|
+
if (entry.isDirectory()) {
|
|
271
|
+
stack.push(entryRel);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
// Only process text files (.ts, .tsx, .js, .jsx)
|
|
275
|
+
if (/\.(tsx?|jsx?)$/i.test(entry.name)) {
|
|
276
|
+
writeTransformed(entrySrc, entryDest);
|
|
277
|
+
} else {
|
|
278
|
+
fs.mkdirSync(path.dirname(entryDest), { recursive: true });
|
|
279
|
+
fs.copyFileSync(entrySrc, entryDest);
|
|
280
|
+
console.log(`✅ ${path.relative(PROJECT_ROOT, entryDest)} created`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Post-copy: materialize shared initialize-escrow files into dialog/form
|
|
286
|
+
try {
|
|
287
|
+
const isSingleReleaseInitRoot =
|
|
288
|
+
name === "escrows/single-release/initialize-escrow";
|
|
289
|
+
const isSingleReleaseInitDialog =
|
|
290
|
+
name === "escrows/single-release/initialize-escrow/dialog";
|
|
291
|
+
const isSingleReleaseInitForm =
|
|
292
|
+
name === "escrows/single-release/initialize-escrow/form";
|
|
293
|
+
|
|
294
|
+
const srcSharedDir = path.join(
|
|
295
|
+
TEMPLATES_DIR,
|
|
296
|
+
"escrows",
|
|
297
|
+
"single-release",
|
|
298
|
+
"initialize-escrow",
|
|
299
|
+
"shared"
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
function copySharedInto(targetDir) {
|
|
303
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
304
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
305
|
+
for (const entry of entries) {
|
|
306
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
307
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
308
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
309
|
+
writeTransformed(entrySrc, entryDest);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (isSingleReleaseInitRoot) {
|
|
314
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
315
|
+
copySharedInto(path.join(destDir, "form"));
|
|
316
|
+
} else if (isSingleReleaseInitDialog) {
|
|
317
|
+
copySharedInto(destDir);
|
|
318
|
+
} else if (isSingleReleaseInitForm) {
|
|
319
|
+
copySharedInto(destDir);
|
|
320
|
+
}
|
|
321
|
+
} catch (e) {
|
|
322
|
+
console.warn(
|
|
323
|
+
"⚠️ Failed to materialize shared initialize-escrow files:",
|
|
324
|
+
e?.message || e
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const isSingleReleaseInitRoot =
|
|
330
|
+
name === "escrows/single-release/approve-milestone";
|
|
331
|
+
const isSingleReleaseInitDialog =
|
|
332
|
+
name === "escrows/single-release/approve-milestone/dialog";
|
|
333
|
+
const isSingleReleaseInitForm =
|
|
334
|
+
name === "escrows/single-release/approve-milestone/form";
|
|
335
|
+
|
|
336
|
+
const srcSharedDir = path.join(
|
|
337
|
+
TEMPLATES_DIR,
|
|
338
|
+
"escrows",
|
|
339
|
+
"single-release",
|
|
340
|
+
"approve-milestone",
|
|
341
|
+
"shared"
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
function copySharedInto(targetDir) {
|
|
345
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
346
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
347
|
+
for (const entry of entries) {
|
|
348
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
349
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
350
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
351
|
+
writeTransformed(entrySrc, entryDest);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if (isSingleReleaseInitRoot) {
|
|
356
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
357
|
+
copySharedInto(path.join(destDir, "form"));
|
|
358
|
+
} else if (isSingleReleaseInitDialog) {
|
|
359
|
+
copySharedInto(destDir);
|
|
360
|
+
} else if (isSingleReleaseInitForm) {
|
|
361
|
+
copySharedInto(destDir);
|
|
362
|
+
}
|
|
363
|
+
} catch (e) {
|
|
364
|
+
console.warn(
|
|
365
|
+
"⚠️ Failed to materialize shared approve-milestone files:",
|
|
366
|
+
e?.message || e
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
try {
|
|
371
|
+
const isSingleReleaseInitRoot =
|
|
372
|
+
name === "escrows/single-release/change-milestone-status";
|
|
373
|
+
const isSingleReleaseInitDialog =
|
|
374
|
+
name === "escrows/single-release/change-milestone-status/dialog";
|
|
375
|
+
const isSingleReleaseInitForm =
|
|
376
|
+
name === "escrows/single-release/change-milestone-status/form";
|
|
377
|
+
|
|
378
|
+
const srcSharedDir = path.join(
|
|
379
|
+
TEMPLATES_DIR,
|
|
380
|
+
"escrows",
|
|
381
|
+
"single-release",
|
|
382
|
+
"change-milestone-status",
|
|
383
|
+
"shared"
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
function copySharedInto(targetDir) {
|
|
387
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
388
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
389
|
+
for (const entry of entries) {
|
|
390
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
391
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
392
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
393
|
+
writeTransformed(entrySrc, entryDest);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (isSingleReleaseInitRoot) {
|
|
398
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
399
|
+
copySharedInto(path.join(destDir, "form"));
|
|
400
|
+
} else if (isSingleReleaseInitDialog) {
|
|
401
|
+
copySharedInto(destDir);
|
|
402
|
+
} else if (isSingleReleaseInitForm) {
|
|
403
|
+
copySharedInto(destDir);
|
|
404
|
+
}
|
|
405
|
+
} catch (e) {
|
|
406
|
+
console.warn(
|
|
407
|
+
"⚠️ Failed to materialize shared change-milestone-status files:",
|
|
408
|
+
e?.message || e
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
try {
|
|
413
|
+
const isSingleReleaseInitRoot =
|
|
414
|
+
name === "escrows/single-release/fund-escrow";
|
|
415
|
+
const isSingleReleaseInitDialog =
|
|
416
|
+
name === "escrows/single-release/fund-escrow/dialog";
|
|
417
|
+
const isSingleReleaseInitForm =
|
|
418
|
+
name === "escrows/single-release/fund-escrow/form";
|
|
419
|
+
|
|
420
|
+
const srcSharedDir = path.join(
|
|
421
|
+
TEMPLATES_DIR,
|
|
422
|
+
"escrows",
|
|
423
|
+
"single-release",
|
|
424
|
+
"fund-escrow",
|
|
425
|
+
"shared"
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
function copySharedInto(targetDir) {
|
|
429
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
430
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
431
|
+
for (const entry of entries) {
|
|
432
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
433
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
434
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
435
|
+
writeTransformed(entrySrc, entryDest);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (isSingleReleaseInitRoot) {
|
|
440
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
441
|
+
copySharedInto(path.join(destDir, "form"));
|
|
442
|
+
} else if (isSingleReleaseInitDialog) {
|
|
443
|
+
copySharedInto(destDir);
|
|
444
|
+
} else if (isSingleReleaseInitForm) {
|
|
445
|
+
copySharedInto(destDir);
|
|
446
|
+
}
|
|
447
|
+
} catch (e) {
|
|
448
|
+
console.warn(
|
|
449
|
+
"⚠️ Failed to materialize shared fund-escrow files:",
|
|
450
|
+
e?.message || e
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const isSingleReleaseInitRoot =
|
|
456
|
+
name === "escrows/single-release/resolve-dispute";
|
|
457
|
+
const isSingleReleaseInitDialog =
|
|
458
|
+
name === "escrows/single-release/resolve-dispute/dialog";
|
|
459
|
+
const isSingleReleaseInitForm =
|
|
460
|
+
name === "escrows/single-release/resolve-dispute/form";
|
|
461
|
+
|
|
462
|
+
const srcSharedDir = path.join(
|
|
463
|
+
TEMPLATES_DIR,
|
|
464
|
+
"escrows",
|
|
465
|
+
"single-release",
|
|
466
|
+
"resolve-dispute",
|
|
467
|
+
"shared"
|
|
468
|
+
);
|
|
469
|
+
|
|
470
|
+
function copySharedInto(targetDir) {
|
|
471
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
472
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
473
|
+
for (const entry of entries) {
|
|
474
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
475
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
476
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
477
|
+
writeTransformed(entrySrc, entryDest);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (isSingleReleaseInitRoot) {
|
|
482
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
483
|
+
copySharedInto(path.join(destDir, "form"));
|
|
484
|
+
} else if (isSingleReleaseInitDialog) {
|
|
485
|
+
copySharedInto(destDir);
|
|
486
|
+
} else if (isSingleReleaseInitForm) {
|
|
487
|
+
copySharedInto(destDir);
|
|
488
|
+
}
|
|
489
|
+
} catch (e) {
|
|
490
|
+
console.warn(
|
|
491
|
+
"⚠️ Failed to materialize shared resolve-dispute files:",
|
|
492
|
+
e?.message || e
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
const isSingleReleaseInitRoot =
|
|
498
|
+
name === "escrows/single-release/update-escrow";
|
|
499
|
+
const isSingleReleaseInitDialog =
|
|
500
|
+
name === "escrows/single-release/update-escrow/dialog";
|
|
501
|
+
const isSingleReleaseInitForm =
|
|
502
|
+
name === "escrows/single-release/update-escrow/form";
|
|
503
|
+
|
|
504
|
+
const srcSharedDir = path.join(
|
|
505
|
+
TEMPLATES_DIR,
|
|
506
|
+
"escrows",
|
|
507
|
+
"single-release",
|
|
508
|
+
"update-escrow",
|
|
509
|
+
"shared"
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
function copySharedInto(targetDir) {
|
|
513
|
+
if (!fs.existsSync(srcSharedDir)) return;
|
|
514
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
515
|
+
for (const entry of entries) {
|
|
516
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
517
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
518
|
+
const entryDest = path.join(targetDir, entry.name);
|
|
519
|
+
writeTransformed(entrySrc, entryDest);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (isSingleReleaseInitRoot) {
|
|
524
|
+
copySharedInto(path.join(destDir, "dialog"));
|
|
525
|
+
copySharedInto(path.join(destDir, "form"));
|
|
526
|
+
} else if (isSingleReleaseInitDialog) {
|
|
527
|
+
copySharedInto(destDir);
|
|
528
|
+
} else if (isSingleReleaseInitForm) {
|
|
529
|
+
copySharedInto(destDir);
|
|
530
|
+
}
|
|
531
|
+
} catch (e) {
|
|
532
|
+
console.warn(
|
|
533
|
+
"⚠️ Failed to materialize shared update-escrow files:",
|
|
534
|
+
e?.message || e
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// If adding the whole single-release bundle, materialize all shared files
|
|
539
|
+
try {
|
|
540
|
+
if (name === "escrows/single-release") {
|
|
541
|
+
const modules = [
|
|
542
|
+
"initialize-escrow",
|
|
543
|
+
"approve-milestone",
|
|
544
|
+
"change-milestone-status",
|
|
545
|
+
"fund-escrow",
|
|
546
|
+
"resolve-dispute",
|
|
547
|
+
"update-escrow",
|
|
548
|
+
];
|
|
549
|
+
|
|
550
|
+
for (const mod of modules) {
|
|
551
|
+
const srcSharedDir = path.join(
|
|
552
|
+
TEMPLATES_DIR,
|
|
553
|
+
"escrows",
|
|
554
|
+
"single-release",
|
|
555
|
+
mod,
|
|
556
|
+
"shared"
|
|
557
|
+
);
|
|
558
|
+
if (!fs.existsSync(srcSharedDir)) continue;
|
|
559
|
+
|
|
560
|
+
const targets = [
|
|
561
|
+
path.join(destDir, mod, "dialog"),
|
|
562
|
+
path.join(destDir, mod, "form"),
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
566
|
+
for (const entry of entries) {
|
|
567
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
568
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
569
|
+
for (const t of targets) {
|
|
570
|
+
const entryDest = path.join(t, entry.name);
|
|
571
|
+
writeTransformed(entrySrc, entryDest);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
} catch (e) {
|
|
577
|
+
console.warn(
|
|
578
|
+
"⚠️ Failed to materialize shared files for single-release bundle:",
|
|
579
|
+
e?.message || e
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// If adding the root escrows bundle, also materialize single-release shared files
|
|
584
|
+
try {
|
|
585
|
+
if (name === "escrows") {
|
|
586
|
+
const modules = [
|
|
587
|
+
"initialize-escrow",
|
|
588
|
+
"approve-milestone",
|
|
589
|
+
"change-milestone-status",
|
|
590
|
+
"fund-escrow",
|
|
591
|
+
"resolve-dispute",
|
|
592
|
+
"update-escrow",
|
|
593
|
+
];
|
|
594
|
+
|
|
595
|
+
const baseTarget = path.join(destDir, "single-release");
|
|
596
|
+
for (const mod of modules) {
|
|
597
|
+
const srcSharedDir = path.join(
|
|
598
|
+
TEMPLATES_DIR,
|
|
599
|
+
"escrows",
|
|
600
|
+
"single-release",
|
|
601
|
+
mod,
|
|
602
|
+
"shared"
|
|
603
|
+
);
|
|
604
|
+
if (!fs.existsSync(srcSharedDir)) continue;
|
|
605
|
+
|
|
606
|
+
const targets = [
|
|
607
|
+
path.join(baseTarget, mod, "dialog"),
|
|
608
|
+
path.join(baseTarget, mod, "form"),
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
const entries = fs.readdirSync(srcSharedDir, { withFileTypes: true });
|
|
612
|
+
for (const entry of entries) {
|
|
613
|
+
if (!/\.(tsx?|jsx?)$/i.test(entry.name)) continue;
|
|
614
|
+
const entrySrc = path.join(srcSharedDir, entry.name);
|
|
615
|
+
for (const t of targets) {
|
|
616
|
+
const entryDest = path.join(t, entry.name);
|
|
617
|
+
writeTransformed(entrySrc, entryDest);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch (e) {
|
|
623
|
+
console.warn(
|
|
624
|
+
"⚠️ Failed to materialize shared files for escrows root:",
|
|
625
|
+
e?.message || e
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
} else if (fs.existsSync(srcFile)) {
|
|
629
|
+
fs.mkdirSync(outRoot, { recursive: true });
|
|
630
|
+
const destFile = path.join(outRoot, name + ".tsx");
|
|
631
|
+
writeTransformed(srcFile, destFile);
|
|
632
|
+
} else {
|
|
633
|
+
console.error(`❌ The template "${name}" does not exist`);
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (shouldInstall && fs.existsSync(GLOBAL_DEPS_FILE)) {
|
|
638
|
+
const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
|
|
639
|
+
installDeps(meta);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function copySharedDetailsInto(targetRelativeDir, { uiBase } = {}) {
|
|
644
|
+
const srcDir = path.join(TEMPLATES_DIR, "escrows", "details");
|
|
645
|
+
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
646
|
+
const destDir = path.join(outRoot, targetRelativeDir);
|
|
647
|
+
const config = loadConfig();
|
|
648
|
+
const effectiveUiBase = uiBase || config.uiBase || "@/components/ui";
|
|
649
|
+
|
|
650
|
+
if (!fs.existsSync(srcDir)) return;
|
|
651
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
652
|
+
|
|
653
|
+
function writeTransformed(srcPath, destPath) {
|
|
654
|
+
const raw = fs.readFileSync(srcPath, "utf8");
|
|
655
|
+
const transformed = raw.replaceAll("__UI_BASE__", effectiveUiBase);
|
|
656
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
657
|
+
fs.writeFileSync(destPath, transformed, "utf8");
|
|
658
|
+
console.log(`✅ ${path.relative(PROJECT_ROOT, destPath)} created`);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const stack = [""];
|
|
662
|
+
while (stack.length) {
|
|
663
|
+
const rel = stack.pop();
|
|
664
|
+
const current = path.join(srcDir, rel);
|
|
665
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
666
|
+
for (const entry of entries) {
|
|
667
|
+
const entryRel = path.join(rel, entry.name);
|
|
668
|
+
const entrySrc = path.join(srcDir, entryRel);
|
|
669
|
+
const entryDest = path.join(destDir, entryRel);
|
|
670
|
+
if (entry.isDirectory()) {
|
|
671
|
+
stack.push(entryRel);
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (/\.(tsx?|jsx?)$/i.test(entry.name)) {
|
|
675
|
+
writeTransformed(entrySrc, entryDest);
|
|
676
|
+
} else {
|
|
677
|
+
fs.mkdirSync(path.dirname(entryDest), { recursive: true });
|
|
678
|
+
fs.copyFileSync(entrySrc, entryDest);
|
|
679
|
+
console.log(`✅ ${path.relative(PROJECT_ROOT, entryDest)} created`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function copySharedRoleSignerHooks(kind = "both") {
|
|
686
|
+
const outRoot = path.join(PROJECT_ROOT, "src", "components", "tw-blocks");
|
|
687
|
+
|
|
688
|
+
const mappings = [];
|
|
689
|
+
if (kind === "both" || kind === "role") {
|
|
690
|
+
mappings.push({
|
|
691
|
+
src: path.join(
|
|
692
|
+
TEMPLATES_DIR,
|
|
693
|
+
"escrows",
|
|
694
|
+
"escrows-by-role",
|
|
695
|
+
"useEscrowsByRole.shared.ts"
|
|
696
|
+
),
|
|
697
|
+
dest: path.join(
|
|
698
|
+
outRoot,
|
|
699
|
+
"escrows",
|
|
700
|
+
"escrows-by-role",
|
|
701
|
+
"useEscrowsByRole.shared.ts"
|
|
702
|
+
),
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
if (kind === "both" || kind === "signer") {
|
|
706
|
+
mappings.push({
|
|
707
|
+
src: path.join(
|
|
708
|
+
TEMPLATES_DIR,
|
|
709
|
+
"escrows",
|
|
710
|
+
"escrows-by-signer",
|
|
711
|
+
"useEscrowsBySigner.shared.ts"
|
|
712
|
+
),
|
|
713
|
+
dest: path.join(
|
|
714
|
+
outRoot,
|
|
715
|
+
"escrows",
|
|
716
|
+
"escrows-by-signer",
|
|
717
|
+
"useEscrowsBySigner.shared.ts"
|
|
718
|
+
),
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
for (const { src, dest } of mappings) {
|
|
723
|
+
if (!fs.existsSync(src)) continue;
|
|
724
|
+
const raw = fs.readFileSync(src, "utf8");
|
|
725
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
726
|
+
fs.writeFileSync(dest, raw, "utf8");
|
|
727
|
+
console.log(`✅ ${path.relative(PROJECT_ROOT, dest)} created`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
function findLayoutFile() {
|
|
732
|
+
const candidates = [
|
|
733
|
+
path.join(PROJECT_ROOT, "app", "layout.tsx"),
|
|
734
|
+
path.join(PROJECT_ROOT, "app", "layout.ts"),
|
|
735
|
+
path.join(PROJECT_ROOT, "app", "layout.jsx"),
|
|
736
|
+
path.join(PROJECT_ROOT, "app", "layout.js"),
|
|
737
|
+
path.join(PROJECT_ROOT, "src", "app", "layout.tsx"),
|
|
738
|
+
path.join(PROJECT_ROOT, "src", "app", "layout.ts"),
|
|
739
|
+
path.join(PROJECT_ROOT, "src", "app", "layout.jsx"),
|
|
740
|
+
path.join(PROJECT_ROOT, "src", "app", "layout.js"),
|
|
741
|
+
];
|
|
742
|
+
return candidates.find((p) => fs.existsSync(p)) || null;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function injectProvidersIntoLayout(
|
|
746
|
+
layoutPath,
|
|
747
|
+
{ reactQuery = false, trustless = false, wallet = false, escrow = false } = {}
|
|
748
|
+
) {
|
|
749
|
+
try {
|
|
750
|
+
let content = fs.readFileSync(layoutPath, "utf8");
|
|
751
|
+
|
|
752
|
+
const importRQ =
|
|
753
|
+
'import { ReactQueryClientProvider } from "@/components/tw-blocks/providers/ReactQueryClientProvider";\n';
|
|
754
|
+
const importTW =
|
|
755
|
+
'import { TrustlessWorkProvider } from "@/components/tw-blocks/providers/TrustlessWork";\n';
|
|
756
|
+
const importEscrow =
|
|
757
|
+
'import { EscrowProvider } from "@/components/tw-blocks/providers/EscrowProvider";\n';
|
|
758
|
+
const importWallet =
|
|
759
|
+
'import { WalletProvider } from "@/components/tw-blocks/wallet-kit/WalletProvider";\n';
|
|
760
|
+
const commentText =
|
|
761
|
+
"// Use these imports to wrap your application (<ReactQueryClientProvider>, <TrustlessWorkProvider>, <WalletProvider> y <EscrowProvider>)\n";
|
|
762
|
+
|
|
763
|
+
const needImport = (name) =>
|
|
764
|
+
!new RegExp(
|
|
765
|
+
`import\\s+[^;]*${name}[^;]*from\\s+['\"][^'\"]+['\"];?`
|
|
766
|
+
).test(content);
|
|
767
|
+
|
|
768
|
+
let importsToAdd = "";
|
|
769
|
+
if (reactQuery && needImport("ReactQueryClientProvider"))
|
|
770
|
+
importsToAdd += importRQ;
|
|
771
|
+
if (trustless && needImport("TrustlessWorkProvider"))
|
|
772
|
+
importsToAdd += importTW;
|
|
773
|
+
if (wallet && needImport("WalletProvider")) importsToAdd += importWallet;
|
|
774
|
+
if (escrow && needImport("EscrowProvider")) importsToAdd += importEscrow;
|
|
775
|
+
|
|
776
|
+
if (importsToAdd) {
|
|
777
|
+
const importStmtRegex = /^import.*;\s*$/gm;
|
|
778
|
+
let last = null;
|
|
779
|
+
for (const m of content.matchAll(importStmtRegex)) last = m;
|
|
780
|
+
if (last) {
|
|
781
|
+
const idx = last.index + last[0].length;
|
|
782
|
+
content =
|
|
783
|
+
content.slice(0, idx) +
|
|
784
|
+
"\n" +
|
|
785
|
+
importsToAdd +
|
|
786
|
+
commentText +
|
|
787
|
+
content.slice(idx);
|
|
788
|
+
} else {
|
|
789
|
+
content = importsToAdd + commentText + content;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const hasTag = (tag) => new RegExp(`<${tag}[\\s>]`).test(content);
|
|
794
|
+
const wrapInside = (containerTag, newTag) => {
|
|
795
|
+
const open = content.match(new RegExp(`<${containerTag}(\\s[^>]*)?>`));
|
|
796
|
+
if (!open) return false;
|
|
797
|
+
const openIdx = open.index + open[0].length;
|
|
798
|
+
const closeIdx = content.indexOf(`</${containerTag}>`, openIdx);
|
|
799
|
+
if (closeIdx === -1) return false;
|
|
800
|
+
content =
|
|
801
|
+
content.slice(0, openIdx) +
|
|
802
|
+
`\n<${newTag}>\n` +
|
|
803
|
+
content.slice(openIdx, closeIdx) +
|
|
804
|
+
`\n</${newTag}>\n` +
|
|
805
|
+
content.slice(closeIdx);
|
|
806
|
+
return true;
|
|
807
|
+
};
|
|
808
|
+
|
|
809
|
+
const ensureTag = (tag) => {
|
|
810
|
+
if (hasTag(tag)) return;
|
|
811
|
+
const bodyOpen = content.match(/<body[^>]*>/);
|
|
812
|
+
const bodyCloseIdx = content.lastIndexOf("</body>");
|
|
813
|
+
if (!bodyOpen || bodyCloseIdx === -1) return;
|
|
814
|
+
const bodyOpenIdx = bodyOpen.index + bodyOpen[0].length;
|
|
815
|
+
if (tag === "TrustlessWorkProvider") {
|
|
816
|
+
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
817
|
+
}
|
|
818
|
+
if (tag === "WalletProvider") {
|
|
819
|
+
if (wrapInside("TrustlessWorkProvider", tag)) return;
|
|
820
|
+
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
821
|
+
}
|
|
822
|
+
if (tag === "EscrowProvider") {
|
|
823
|
+
if (wrapInside("WalletProvider", tag)) return;
|
|
824
|
+
if (wrapInside("TrustlessWorkProvider", tag)) return;
|
|
825
|
+
if (wrapInside("ReactQueryClientProvider", tag)) return;
|
|
826
|
+
}
|
|
827
|
+
content =
|
|
828
|
+
content.slice(0, bodyOpenIdx) +
|
|
829
|
+
`\n<${tag}>\n` +
|
|
830
|
+
content.slice(bodyOpenIdx, bodyCloseIdx) +
|
|
831
|
+
`\n</${tag}>\n` +
|
|
832
|
+
content.slice(bodyCloseIdx);
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
if (reactQuery) ensureTag("ReactQueryClientProvider");
|
|
836
|
+
if (trustless) ensureTag("TrustlessWorkProvider");
|
|
837
|
+
if (wallet) ensureTag("WalletProvider");
|
|
838
|
+
if (escrow) ensureTag("EscrowProvider");
|
|
839
|
+
|
|
840
|
+
fs.writeFileSync(layoutPath, content, "utf8");
|
|
841
|
+
logCheck(
|
|
842
|
+
`Updated ${path.relative(PROJECT_ROOT, layoutPath)} with providers`
|
|
843
|
+
);
|
|
844
|
+
} catch (e) {
|
|
845
|
+
console.error("❌ Failed to update layout with providers:", e.message);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (args[0] === "init") {
|
|
850
|
+
console.log("\n▶ Setting up shadcn/ui components...");
|
|
851
|
+
const doInit = await promptYesNo("Run shadcn init now?", true);
|
|
852
|
+
if (doInit) {
|
|
853
|
+
run("npx", ["shadcn@latest", "init"]);
|
|
854
|
+
} else {
|
|
855
|
+
console.log("\x1b[90m– Skipped shadcn init\x1b[0m");
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
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)?",
|
|
860
|
+
true
|
|
861
|
+
);
|
|
862
|
+
if (addShadcn) {
|
|
863
|
+
await withSpinner("Installing shadcn/ui components", async () => {
|
|
864
|
+
await runAsync("npx", [
|
|
865
|
+
"shadcn@latest",
|
|
866
|
+
"add",
|
|
867
|
+
"button",
|
|
868
|
+
"input",
|
|
869
|
+
"form",
|
|
870
|
+
"card",
|
|
871
|
+
"sonner",
|
|
872
|
+
"checkbox",
|
|
873
|
+
"dialog",
|
|
874
|
+
"textarea",
|
|
875
|
+
"sonner",
|
|
876
|
+
"select",
|
|
877
|
+
"table",
|
|
878
|
+
"calendar",
|
|
879
|
+
"popover",
|
|
880
|
+
"separator",
|
|
881
|
+
"calendar-05",
|
|
882
|
+
"badge",
|
|
883
|
+
"sheet",
|
|
884
|
+
"tabs",
|
|
885
|
+
"avatar",
|
|
886
|
+
]);
|
|
887
|
+
});
|
|
888
|
+
} else {
|
|
889
|
+
console.log("\x1b[90m– Skipped adding shadcn components\x1b[0m");
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (!fs.existsSync(GLOBAL_DEPS_FILE)) {
|
|
893
|
+
console.error("❌ deps.json not found in templates/");
|
|
894
|
+
process.exit(1);
|
|
895
|
+
}
|
|
896
|
+
const meta = JSON.parse(fs.readFileSync(GLOBAL_DEPS_FILE, "utf8"));
|
|
897
|
+
const installLibs = await promptYesNo(
|
|
898
|
+
"Install (react-hook-form, @tanstack/react-query, @tanstack/react-query-devtools, @trustless-work/escrow, @hookform/resolvers, axios, @creit.tech/stellar-wallets-kit, react-day-picker & zod) dependencies now?",
|
|
899
|
+
true
|
|
900
|
+
);
|
|
901
|
+
if (installLibs) {
|
|
902
|
+
await withSpinner("Installing required dependencies", async () => {
|
|
903
|
+
installDeps(meta);
|
|
904
|
+
});
|
|
905
|
+
} else {
|
|
906
|
+
console.log("\x1b[90m– Skipped installing required dependencies\x1b[0m");
|
|
907
|
+
}
|
|
908
|
+
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
909
|
+
if (!fs.existsSync(cfgPath)) {
|
|
910
|
+
fs.writeFileSync(
|
|
911
|
+
cfgPath,
|
|
912
|
+
JSON.stringify({ uiBase: "@/components/ui" }, null, 2)
|
|
913
|
+
);
|
|
914
|
+
console.log(
|
|
915
|
+
`\x1b[32m✔\x1b[0m Created ${path.relative(
|
|
916
|
+
PROJECT_ROOT,
|
|
917
|
+
cfgPath
|
|
918
|
+
)} with default uiBase`
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
console.log("\x1b[32m✔\x1b[0m shadcn/ui components step completed");
|
|
922
|
+
|
|
923
|
+
const wantProviders = await promptYesNo(
|
|
924
|
+
"Install TanStack Query and Trustless Work providers and wrap app/layout with them?",
|
|
925
|
+
true
|
|
926
|
+
);
|
|
927
|
+
if (wantProviders) {
|
|
928
|
+
await withSpinner("Installing providers", async () => {
|
|
929
|
+
copyTemplate("providers");
|
|
930
|
+
});
|
|
931
|
+
const layoutPath = findLayoutFile();
|
|
932
|
+
if (layoutPath) {
|
|
933
|
+
await withSpinner("Updating app/layout with providers", async () => {
|
|
934
|
+
injectProvidersIntoLayout(layoutPath, {
|
|
935
|
+
reactQuery: true,
|
|
936
|
+
trustless: true,
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
} else {
|
|
940
|
+
console.warn(
|
|
941
|
+
"⚠️ Could not find app/layout file. Skipped automatic wiring."
|
|
942
|
+
);
|
|
943
|
+
}
|
|
944
|
+
} else {
|
|
945
|
+
console.log("\x1b[90m– Skipped installing providers\x1b[0m");
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
printBannerTRUSTLESSWORK();
|
|
949
|
+
console.log("\n\nResources");
|
|
950
|
+
console.log("- " + oscHyperlink("Website", "https://trustlesswork.com"));
|
|
951
|
+
console.log(
|
|
952
|
+
"- " + oscHyperlink("Documentation", "https://docs.trustlesswork.com")
|
|
953
|
+
);
|
|
954
|
+
console.log("- " + oscHyperlink("Demo", "https://demo.trustlesswork.com"));
|
|
955
|
+
console.log(
|
|
956
|
+
"- " + oscHyperlink("Backoffice", "https://dapp.trustlesswork.com")
|
|
957
|
+
);
|
|
958
|
+
console.log(
|
|
959
|
+
"- " + oscHyperlink("GitHub", "https://github.com/trustless-work")
|
|
960
|
+
);
|
|
961
|
+
console.log(
|
|
962
|
+
"- " + oscHyperlink("Escrow Viewer", "https://viewer.trustlesswork.com")
|
|
963
|
+
);
|
|
964
|
+
console.log(
|
|
965
|
+
"- " + oscHyperlink("Telegram", "https://t.me/+kmr8tGegxLU0NTA5")
|
|
966
|
+
);
|
|
967
|
+
console.log(
|
|
968
|
+
"- " +
|
|
969
|
+
oscHyperlink(
|
|
970
|
+
"LinkedIn",
|
|
971
|
+
"https://www.linkedin.com/company/trustlesswork/posts/?feedView=all"
|
|
972
|
+
)
|
|
973
|
+
);
|
|
974
|
+
console.log("- " + oscHyperlink("X", "https://x.com/TrustlessWork"));
|
|
975
|
+
} else if (args[0] === "add" && args[1]) {
|
|
976
|
+
const flags = parseFlags(args.slice(2));
|
|
977
|
+
const cfgPath = path.join(PROJECT_ROOT, ".twblocks.json");
|
|
978
|
+
if (!fs.existsSync(cfgPath)) {
|
|
979
|
+
console.error(
|
|
980
|
+
"❌ Missing initial setup. Run 'trustless-work init' first to install dependencies and create .twblocks.json (uiBase)."
|
|
981
|
+
);
|
|
982
|
+
console.error(
|
|
983
|
+
" After init, re-run: trustless-work add " +
|
|
984
|
+
args[1] +
|
|
985
|
+
(flags.uiBase ? ' --ui-base "' + flags.uiBase + '"' : "")
|
|
986
|
+
);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
copyTemplate(args[1], {
|
|
990
|
+
uiBase: flags.uiBase,
|
|
991
|
+
shouldInstall: !!flags.install,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
// Post-add wiring for specific templates
|
|
995
|
+
const layoutPath = findLayoutFile();
|
|
996
|
+
if (layoutPath) {
|
|
997
|
+
if (args[1] === "wallet-kit" || args[1].startsWith("wallet-kit/")) {
|
|
998
|
+
injectProvidersIntoLayout(layoutPath, { wallet: true });
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Copy shared details into role/signer targets when applicable
|
|
1003
|
+
try {
|
|
1004
|
+
if (args[1] === "escrows") {
|
|
1005
|
+
copySharedDetailsInto("escrows/escrows-by-role/details", {
|
|
1006
|
+
uiBase: flags.uiBase,
|
|
1007
|
+
});
|
|
1008
|
+
copySharedDetailsInto("escrows/escrows-by-signer/details", {
|
|
1009
|
+
uiBase: flags.uiBase,
|
|
1010
|
+
});
|
|
1011
|
+
copySharedRoleSignerHooks("both");
|
|
1012
|
+
}
|
|
1013
|
+
if (
|
|
1014
|
+
args[1] === "escrows/escrows-by-role" ||
|
|
1015
|
+
args[1].startsWith("escrows/escrows-by-role/")
|
|
1016
|
+
) {
|
|
1017
|
+
copySharedDetailsInto("escrows/escrows-by-role/details", {
|
|
1018
|
+
uiBase: flags.uiBase,
|
|
1019
|
+
});
|
|
1020
|
+
copySharedRoleSignerHooks("role");
|
|
1021
|
+
}
|
|
1022
|
+
if (
|
|
1023
|
+
args[1] === "escrows/escrows-by-signer" ||
|
|
1024
|
+
args[1].startsWith("escrows/escrows-by-signer/")
|
|
1025
|
+
) {
|
|
1026
|
+
copySharedDetailsInto("escrows/escrows-by-signer/details", {
|
|
1027
|
+
uiBase: flags.uiBase,
|
|
1028
|
+
});
|
|
1029
|
+
copySharedRoleSignerHooks("signer");
|
|
1030
|
+
}
|
|
1031
|
+
} catch (e) {
|
|
1032
|
+
console.warn("⚠️ Failed to copy shared details:", e?.message || e);
|
|
1033
|
+
}
|
|
1034
|
+
} else {
|
|
1035
|
+
console.log(`
|
|
1036
|
+
|
|
1037
|
+
Usage:
|
|
1038
|
+
|
|
1039
|
+
trustless-work init
|
|
1040
|
+
trustless-work add <template> [--install]
|
|
1041
|
+
|
|
1042
|
+
Options:
|
|
1043
|
+
|
|
1044
|
+
--ui-base <path> Base import path to your shadcn/ui components (default: "@/components/ui")
|
|
1045
|
+
--install, -i Also install dependencies (normally use 'init' once instead)
|
|
1046
|
+
|
|
1047
|
+
Examples:
|
|
1048
|
+
|
|
1049
|
+
--- Get started ---
|
|
1050
|
+
trustless-work init
|
|
1051
|
+
|
|
1052
|
+
--- Providers ---
|
|
1053
|
+
trustless-work add providers
|
|
1054
|
+
|
|
1055
|
+
--- Wallet-kit ---
|
|
1056
|
+
trustless-work add wallet-kit
|
|
1057
|
+
|
|
1058
|
+
--- Handle-errors ---
|
|
1059
|
+
trustless-work add handle-errors
|
|
1060
|
+
|
|
1061
|
+
--- Helpers ---
|
|
1062
|
+
trustless-work add helpers
|
|
1063
|
+
|
|
1064
|
+
--- Tanstack ---
|
|
1065
|
+
trustless-work add tanstack
|
|
1066
|
+
|
|
1067
|
+
--- Escrows ---
|
|
1068
|
+
trustless-work add escrows
|
|
1069
|
+
|
|
1070
|
+
--- Escrows by role ---
|
|
1071
|
+
trustless-work add escrows/escrows-by-role
|
|
1072
|
+
trustless-work add escrows/escrows-by-role/table
|
|
1073
|
+
trustless-work add escrows/escrows-by-role/cards
|
|
1074
|
+
|
|
1075
|
+
--- Escrows by signer ---
|
|
1076
|
+
trustless-work add escrows/escrows-by-signer
|
|
1077
|
+
trustless-work add escrows/escrows-by-signer/table
|
|
1078
|
+
trustless-work add escrows/escrows-by-signer/cards
|
|
1079
|
+
|
|
1080
|
+
--- Escrow details (optional standalone) ---
|
|
1081
|
+
trustless-work add escrows/details
|
|
1082
|
+
|
|
1083
|
+
----------------------
|
|
1084
|
+
--- SINGLE-RELEASE ---
|
|
1085
|
+
trustless-work add escrows/single-release
|
|
1086
|
+
|
|
1087
|
+
--- Initialize escrow ---
|
|
1088
|
+
- trustless-work add escrows/single-release/initialize-escrow
|
|
1089
|
+
- trustless-work add escrows/single-release/initialize-escrow/form
|
|
1090
|
+
- trustless-work add escrows/single-release/initialize-escrow/dialog
|
|
1091
|
+
|
|
1092
|
+
--- Approve milestone ---
|
|
1093
|
+
- trustless-work add escrows/single-release/approve-milestone
|
|
1094
|
+
- trustless-work add escrows/single-release/approve-milestone/form
|
|
1095
|
+
- trustless-work add escrows/single-release/approve-milestone/button
|
|
1096
|
+
- trustless-work add escrows/single-release/approve-milestone/dialog
|
|
1097
|
+
|
|
1098
|
+
--- Change milestone status ---
|
|
1099
|
+
- trustless-work add escrows/single-release/change-milestone-status
|
|
1100
|
+
- trustless-work add escrows/single-release/change-milestone-status/form
|
|
1101
|
+
- trustless-work add escrows/single-release/change-milestone-status/button
|
|
1102
|
+
- trustless-work add escrows/single-release/change-milestone-status/dialog
|
|
1103
|
+
|
|
1104
|
+
--- Fund escrow ---
|
|
1105
|
+
- trustless-work add escrows/single-release/fund-escrow
|
|
1106
|
+
- trustless-work add escrows/single-release/fund-escrow/form
|
|
1107
|
+
- trustless-work add escrows/single-release/fund-escrow/button
|
|
1108
|
+
- trustless-work add escrows/single-release/fund-escrow/dialog
|
|
1109
|
+
|
|
1110
|
+
--- Resolve dispute ---
|
|
1111
|
+
- trustless-work add escrows/single-release/resolve-dispute
|
|
1112
|
+
- trustless-work add escrows/single-release/resolve-dispute/form
|
|
1113
|
+
- trustless-work add escrows/single-release/resolve-dispute/button
|
|
1114
|
+
- trustless-work add escrows/single-release/resolve-dispute/dialog
|
|
1115
|
+
|
|
1116
|
+
--- Update escrow ---
|
|
1117
|
+
- trustless-work add escrows/single-release/update-escrow
|
|
1118
|
+
- trustless-work add escrows/single-release/update-escrow/form
|
|
1119
|
+
- trustless-work add escrows/single-release/update-escrow/dialog
|
|
1120
|
+
|
|
1121
|
+
--- Release escrow ---
|
|
1122
|
+
- trustless-work add escrows/single-release/release-escrow
|
|
1123
|
+
- trustless-work add escrows/single-release/release-escrow/button
|
|
1124
|
+
|
|
1125
|
+
--- Dispute escrow ---
|
|
1126
|
+
- trustless-work add escrows/single-release/dispute-escrow
|
|
1127
|
+
- trustless-work add escrows/single-release/dispute-escrow/button
|
|
1128
|
+
`);
|
|
1129
|
+
}
|