digdesign 0.1.1 → 0.3.0
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/dist/index.js +384 -49
- package/package.json +1 -1
- package/dist/index 2.js +0 -2895
- package/dist/index 3.js +0 -2941
package/dist/index.js
CHANGED
|
@@ -53,6 +53,60 @@ async function writeConfig(config, cwd = process.cwd()) {
|
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// src/lib/errors.ts
|
|
57
|
+
var DigError = class extends Error {
|
|
58
|
+
constructor(message, options) {
|
|
59
|
+
super(message, options);
|
|
60
|
+
this.name = "DigError";
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/lib/prompt.ts
|
|
65
|
+
import * as readline from "readline/promises";
|
|
66
|
+
import { stdin, stdout } from "process";
|
|
67
|
+
async function askLine(question) {
|
|
68
|
+
const rl = readline.createInterface({ input: stdin, output: stdout });
|
|
69
|
+
try {
|
|
70
|
+
return await rl.question(question);
|
|
71
|
+
} catch {
|
|
72
|
+
console.log();
|
|
73
|
+
return "";
|
|
74
|
+
} finally {
|
|
75
|
+
rl.close();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function confirm(question, defaultValue) {
|
|
79
|
+
const suffix = defaultValue ? "(Y/n)" : "(y/N)";
|
|
80
|
+
const raw = await askLine(`${question} ${suffix} `);
|
|
81
|
+
const answer = raw.trim().toLowerCase();
|
|
82
|
+
if (!answer) return defaultValue;
|
|
83
|
+
return answer === "y" || answer === "yes";
|
|
84
|
+
}
|
|
85
|
+
async function ask(question, defaultValue) {
|
|
86
|
+
const raw = await askLine(`${question} (${defaultValue}) `);
|
|
87
|
+
return raw.trim() || defaultValue;
|
|
88
|
+
}
|
|
89
|
+
async function multiSelect(question, options) {
|
|
90
|
+
if (options.length === 0) return [];
|
|
91
|
+
const width = String(options.length).length;
|
|
92
|
+
for (const [index, option] of options.entries()) {
|
|
93
|
+
const number = String(index + 1).padStart(width);
|
|
94
|
+
console.log(` ${number}) ${option.label}`);
|
|
95
|
+
}
|
|
96
|
+
const raw = await askLine(`${question} `);
|
|
97
|
+
return parseSelection(raw, options);
|
|
98
|
+
}
|
|
99
|
+
function parseSelection(raw, options) {
|
|
100
|
+
const picked = /* @__PURE__ */ new Set();
|
|
101
|
+
for (const token of raw.split(/[\s,،]+/).filter(Boolean)) {
|
|
102
|
+
const index = Number(token) - 1;
|
|
103
|
+
if (Number.isInteger(index) && index >= 0 && index < options.length) {
|
|
104
|
+
picked.add(options[index].value);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return [...picked];
|
|
108
|
+
}
|
|
109
|
+
|
|
56
110
|
// src/lib/hash.ts
|
|
57
111
|
import { createHash } from "crypto";
|
|
58
112
|
function hashContent(content) {
|
|
@@ -125,31 +179,62 @@ function installArgs(pm, deps, dev) {
|
|
|
125
179
|
const devFlag = dev ? pm === "npm" ? "--save-dev" : "-D" : null;
|
|
126
180
|
return devFlag ? [verb, devFlag, ...deps] : [verb, ...deps];
|
|
127
181
|
}
|
|
182
|
+
var PEER_CONFLICT_FLAG = {
|
|
183
|
+
npm: "--legacy-peer-deps",
|
|
184
|
+
pnpm: "--strict-peer-dependencies=false"
|
|
185
|
+
};
|
|
186
|
+
function installFailure(pm, args, code, stderr) {
|
|
187
|
+
const command = `${pm} ${args.join(" ")}`;
|
|
188
|
+
const lines = [`\u0646\u0635\u0628\u0650 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627 \u0634\u06A9\u0633\u062A \u062E\u0648\u0631\u062F (${command}\u060C \u06A9\u062F ${code ?? "?"}).`];
|
|
189
|
+
if (/ERESOLVE|peer dep|ERR_PNPM_PEER_DEP_ISSUES/i.test(stderr)) {
|
|
190
|
+
const flag = PEER_CONFLICT_FLAG[pm];
|
|
191
|
+
lines.push(
|
|
192
|
+
" \u0639\u0644\u062A: \u062A\u0639\u0627\u0631\u0636\u0650 peer dependency \u062F\u0631 \u062F\u0631\u062E\u062A\u0650 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u0650 \u062E\u0648\u062F\u0650 \u067E\u0631\u0648\u0698\u0647\u060C \u0646\u0647 \u062F\u0631 \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A\u200C\u0647\u0627\u06CC \u062F\u06CC\u06AF."
|
|
193
|
+
);
|
|
194
|
+
lines.push(" \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u0646\u0648\u0634\u062A\u0647 \u0634\u062F\u0646\u062F\u061B \u0641\u0642\u0637 \u0646\u0635\u0628\u0650 \u067E\u06A9\u06CC\u062C\u200C\u0647\u0627 \u0645\u0627\u0646\u062F\u0647.");
|
|
195
|
+
lines.push(
|
|
196
|
+
flag ? ` \u062F\u0633\u062A\u06CC \u0627\u062C\u0631\u0627 \u06A9\u0646\u06CC\u062F: ${command} ${flag}` : ` \u0627\u0648\u0644 \u062F\u0631\u062E\u062A\u0650 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC \u0631\u0627 \u062F\u0631\u0633\u062A \u06A9\u0646\u06CC\u062F\u060C \u0628\u0639\u062F: ${command}`
|
|
197
|
+
);
|
|
198
|
+
} else {
|
|
199
|
+
lines.push(" \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u0646\u0648\u0634\u062A\u0647 \u0634\u062F\u0646\u062F\u061B \u0641\u0642\u0637 \u0646\u0635\u0628\u0650 \u067E\u06A9\u06CC\u062C\u200C\u0647\u0627 \u0645\u0627\u0646\u062F\u0647.");
|
|
200
|
+
lines.push(` \u062E\u0631\u0648\u062C\u06CC\u0650 \u06A9\u0627\u0645\u0644\u0650 ${pm} \u0628\u0627\u0644\u0627\u062A\u0631 \u0686\u0627\u067E \u0634\u062F\u0647. \u0628\u0631\u0627\u06CC \u062A\u0644\u0627\u0634 \u062F\u0648\u0628\u0627\u0631\u0647: ${command}`);
|
|
201
|
+
}
|
|
202
|
+
lines.push(" \u0628\u0631\u0627\u06CC \u0631\u062F \u06A9\u0631\u062F\u0646\u0650 \u0646\u0635\u0628\u0650 \u062E\u0648\u062F\u06A9\u0627\u0631 \u0627\u0632 \u0627\u06CC\u0646 \u0628\u0647 \u0628\u0639\u062F: dig add \u2026 --no-install");
|
|
203
|
+
return new DigError(lines.join("\n"));
|
|
204
|
+
}
|
|
128
205
|
function installDependencies(pm, deps, options = {}) {
|
|
129
206
|
if (!deps.length) return Promise.resolve();
|
|
130
207
|
const args = installArgs(pm, deps, Boolean(options.dev));
|
|
131
208
|
return new Promise((resolve, reject) => {
|
|
132
209
|
const child = spawn(pm, args, {
|
|
133
210
|
cwd: options.cwd ?? process.cwd(),
|
|
134
|
-
|
|
211
|
+
// stderr پایپ میشود تا هم مثل قبل بیکموکاست به کاربر برسد، هم بتوانیم
|
|
212
|
+
// علتِ شکست را از رویش تشخیص بدهیم. stdout همچنان inherit است چون
|
|
213
|
+
// نوارِ پیشرفتِ پکیجمنیجر باید زنده بماند.
|
|
214
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
135
215
|
shell: process.platform === "win32"
|
|
136
216
|
});
|
|
217
|
+
let stderr = "";
|
|
218
|
+
child.stderr?.on("data", (chunk) => {
|
|
219
|
+
const text = chunk.toString();
|
|
220
|
+
stderr += text;
|
|
221
|
+
process.stderr.write(text);
|
|
222
|
+
});
|
|
137
223
|
child.on("exit", (code) => {
|
|
138
224
|
if (code === 0) resolve();
|
|
139
|
-
else reject(
|
|
225
|
+
else reject(installFailure(pm, args, code, stderr));
|
|
226
|
+
});
|
|
227
|
+
child.on("error", (cause) => {
|
|
228
|
+
reject(
|
|
229
|
+
new DigError(
|
|
230
|
+
`\xAB${pm}\xBB \u0627\u062C\u0631\u0627 \u0646\u0634\u062F. \u0645\u0637\u0645\u0626\u0646 \u0634\u0648\u06CC\u062F \u0646\u0635\u0628 \u0627\u0633\u062A \u0648 \u062F\u0631 PATH \u0642\u0631\u0627\u0631 \u062F\u0627\u0631\u062F.`,
|
|
231
|
+
{ cause }
|
|
232
|
+
)
|
|
233
|
+
);
|
|
140
234
|
});
|
|
141
|
-
child.on("error", reject);
|
|
142
235
|
});
|
|
143
236
|
}
|
|
144
237
|
|
|
145
|
-
// src/lib/errors.ts
|
|
146
|
-
var DigError = class extends Error {
|
|
147
|
-
constructor(message, options) {
|
|
148
|
-
super(message, options);
|
|
149
|
-
this.name = "DigError";
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
238
|
// src/lib/registry.ts
|
|
154
239
|
function resolveUrl(nameOrUrl, registryUrl) {
|
|
155
240
|
if (/^https?:\/\//.test(nameOrUrl)) return nameOrUrl;
|
|
@@ -158,7 +243,8 @@ function resolveUrl(nameOrUrl, registryUrl) {
|
|
|
158
243
|
async function fetchFromRegistry(url, subject) {
|
|
159
244
|
let response;
|
|
160
245
|
try {
|
|
161
|
-
|
|
246
|
+
const token = process.env.DIG_TOKEN;
|
|
247
|
+
response = token ? await fetch(url, { headers: { Authorization: `Bearer ${token}` } }) : await fetch(url);
|
|
162
248
|
} catch (cause) {
|
|
163
249
|
throw new DigError(
|
|
164
250
|
`\u0628\u0647 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u0648\u0635\u0644 \u0646\u0634\u062F\u0645: ${url}
|
|
@@ -283,7 +369,10 @@ import path5 from "path";
|
|
|
283
369
|
function toPosixPath(relativePath) {
|
|
284
370
|
return relativePath.split(path5.sep).join("/");
|
|
285
371
|
}
|
|
286
|
-
function resolveTargetPath(filePath, aliases) {
|
|
372
|
+
function resolveTargetPath(filePath, aliases, options = {}) {
|
|
373
|
+
if (/(?:^|\/)app\/globals\.css$/.test(filePath)) {
|
|
374
|
+
return options.tailwindCss ?? "src/app/globals.css";
|
|
375
|
+
}
|
|
287
376
|
const match = filePath.match(/(?:^|\/)(components|lib|hooks)\/(.*)$/);
|
|
288
377
|
const cleaned = match ? `${match[1]}/${match[2]}` : filePath;
|
|
289
378
|
if (cleaned.startsWith("components/ui/")) {
|
|
@@ -310,11 +399,82 @@ async function pathExists(target) {
|
|
|
310
399
|
return false;
|
|
311
400
|
}
|
|
312
401
|
}
|
|
402
|
+
async function pickComponents(registryUrl) {
|
|
403
|
+
if (!process.stdin.isTTY) {
|
|
404
|
+
throw new DigError(
|
|
405
|
+
"\u0646\u0627\u0645 \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u0631\u0627 \u0628\u062F\u0647\u06CC\u062F: dig add button\n \u0628\u0631\u0627\u06CC \u062F\u06CC\u062F\u0646\u0650 \u0641\u0647\u0631\u0633\u062A: dig list"
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const index = await fetchRegistryIndex(registryUrl);
|
|
409
|
+
const choices = index.items.filter((item) => item.type === "registry:ui").map((item) => ({
|
|
410
|
+
value: item.name,
|
|
411
|
+
label: item.title ? `${item.name} \u2014 ${item.title}` : item.name
|
|
412
|
+
}));
|
|
413
|
+
console.log(`
|
|
414
|
+
${choices.length} \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u062F\u0631 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC:
|
|
415
|
+
`);
|
|
416
|
+
const picked = await multiSelect(
|
|
417
|
+
"\n\u0634\u0645\u0627\u0631\u0647\u200C\u0647\u0627 \u0631\u0627 \u0628\u0627 \u0641\u0627\u0635\u0644\u0647 \u0628\u062F\u0647\u06CC\u062F (\u062E\u0627\u0644\u06CC = \u0644\u063A\u0648):",
|
|
418
|
+
choices
|
|
419
|
+
);
|
|
420
|
+
if (picked.length === 0) {
|
|
421
|
+
console.log("\u0686\u06CC\u0632\u06CC \u0627\u0646\u062A\u062E\u0627\u0628 \u0646\u0634\u062F.");
|
|
422
|
+
return [];
|
|
423
|
+
}
|
|
424
|
+
return picked;
|
|
425
|
+
}
|
|
313
426
|
async function addCommand(components, options) {
|
|
314
427
|
const cwd = process.cwd();
|
|
315
428
|
const config = await configExists(cwd) ? await readConfig(cwd) : defaultConfig();
|
|
429
|
+
const names = components.length ? components : await pickComponents(config.registryUrl);
|
|
430
|
+
if (names.length === 0) return;
|
|
316
431
|
console.log(`\u062F\u0631\u06CC\u0627\u0641\u062A \u0627\u0632 ${config.registryUrl} \u2026`);
|
|
317
|
-
const items = await resolveRegistryTree(
|
|
432
|
+
const items = await resolveRegistryTree(names, config.registryUrl);
|
|
433
|
+
let overwrite = Boolean(options.overwrite);
|
|
434
|
+
if (!overwrite) {
|
|
435
|
+
const existing = [];
|
|
436
|
+
for (const item of items.values()) {
|
|
437
|
+
for (const file of item.files) {
|
|
438
|
+
const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, {
|
|
439
|
+
tailwindCss: config.tailwind.css
|
|
440
|
+
}));
|
|
441
|
+
if (await pathExists(target)) existing.push(path6.relative(cwd, target));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
if (existing.length) {
|
|
445
|
+
if (options.yes) {
|
|
446
|
+
overwrite = true;
|
|
447
|
+
} else if (process.stdin.isTTY) {
|
|
448
|
+
console.log(`
|
|
449
|
+
${existing.length} \u0641\u0627\u06CC\u0644 \u0627\u0632 \u0642\u0628\u0644 \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F:`);
|
|
450
|
+
for (const file of existing.slice(0, 10)) console.log(` ${file}`);
|
|
451
|
+
if (existing.length > 10) console.log(` \u2026 \u0648 ${existing.length - 10} \u0641\u0627\u06CC\u0644 \u062F\u06CC\u06AF\u0631`);
|
|
452
|
+
overwrite = await confirm("\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u0634\u0648\u0646\u062F\u061F", false);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (options.dryRun) {
|
|
457
|
+
const npmDeps = /* @__PURE__ */ new Set();
|
|
458
|
+
console.log("\n\u067E\u06CC\u0634\u200C\u0646\u0645\u0627\u06CC\u0634 (\u0647\u06CC\u0686 \u0641\u0627\u06CC\u0644\u06CC \u0646\u0648\u0634\u062A\u0647 \u0646\u0645\u06CC\u200C\u0634\u0648\u062F):\n");
|
|
459
|
+
for (const item of items.values()) {
|
|
460
|
+
for (const dep of item.dependencies ?? []) npmDeps.add(dep);
|
|
461
|
+
for (const file of item.files) {
|
|
462
|
+
const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, {
|
|
463
|
+
tailwindCss: config.tailwind.css
|
|
464
|
+
}));
|
|
465
|
+
const exists = await pathExists(target);
|
|
466
|
+
const mark = exists ? overwrite ? "~" : "-" : "+";
|
|
467
|
+
const note = exists ? overwrite ? " (\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC)" : " (\u0631\u062F \u0645\u06CC\u200C\u0634\u0648\u062F\u060C \u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F)" : "";
|
|
468
|
+
console.log(`${mark} ${path6.relative(cwd, target)}${note}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
console.log(`
|
|
472
|
+
${items.size} \u0622\u06CC\u062A\u0645 \u0627\u0632 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC.`);
|
|
473
|
+
if (npmDeps.size) {
|
|
474
|
+
console.log(`\u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627\u06CC npm: ${[...npmDeps].join(" ")}`);
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
318
478
|
const dependencies = /* @__PURE__ */ new Set();
|
|
319
479
|
const devDependencies = /* @__PURE__ */ new Set();
|
|
320
480
|
let written = 0;
|
|
@@ -328,8 +488,10 @@ async function addCommand(components, options) {
|
|
|
328
488
|
const writtenFiles = {};
|
|
329
489
|
const writtenContents = {};
|
|
330
490
|
for (const file of item.files) {
|
|
331
|
-
const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases
|
|
332
|
-
|
|
491
|
+
const target = path6.join(cwd, resolveTargetPath(file.path, config.aliases, {
|
|
492
|
+
tailwindCss: config.tailwind.css
|
|
493
|
+
}));
|
|
494
|
+
if (!overwrite && await pathExists(target)) {
|
|
333
495
|
console.log(`- \u0631\u062F \u0634\u062F (\u0648\u062C\u0648\u062F \u062F\u0627\u0631\u062F): ${path6.relative(cwd, target)}`);
|
|
334
496
|
skipped++;
|
|
335
497
|
continue;
|
|
@@ -383,6 +545,107 @@ ${written} \u0641\u0627\u06CC\u0644 \u0646\u0648\u0634\u062A\u0647 \u0634\u062F\
|
|
|
383
545
|
}
|
|
384
546
|
}
|
|
385
547
|
|
|
548
|
+
// src/lib/search.ts
|
|
549
|
+
var ARABIC_TO_PERSIAN = {
|
|
550
|
+
"\u064A": "\u06CC",
|
|
551
|
+
// ي → ی
|
|
552
|
+
"\u0649": "\u06CC",
|
|
553
|
+
// ى → ی
|
|
554
|
+
"\u0643": "\u06A9",
|
|
555
|
+
// ك → ک
|
|
556
|
+
"\u0629": "\u0647",
|
|
557
|
+
// ة → ه
|
|
558
|
+
"\u0623": "\u0627",
|
|
559
|
+
// أ → ا
|
|
560
|
+
"\u0625": "\u0627",
|
|
561
|
+
// إ → ا
|
|
562
|
+
"\u0622": "\u0627",
|
|
563
|
+
// آ → ا
|
|
564
|
+
"\u0624": "\u0648"
|
|
565
|
+
// ؤ → و
|
|
566
|
+
};
|
|
567
|
+
var DIACRITICS = /[ً-ْٰ]/g;
|
|
568
|
+
var INVISIBLE = /[-]/g;
|
|
569
|
+
function toSearchKey(value) {
|
|
570
|
+
return value.replace(/[يىكةأإآؤ]/g, (ch) => ARABIC_TO_PERSIAN[ch]).replace(DIACRITICS, "").replace(INVISIBLE, "").toLowerCase().replace(/[\s]+/g, "");
|
|
571
|
+
}
|
|
572
|
+
function scoreItem(item, query) {
|
|
573
|
+
const q = toSearchKey(query);
|
|
574
|
+
if (!q) return 0;
|
|
575
|
+
const name = toSearchKey(item.name);
|
|
576
|
+
const title = toSearchKey(item.title ?? "");
|
|
577
|
+
const description = toSearchKey(item.description ?? "");
|
|
578
|
+
if (name === q || title === q) return 100;
|
|
579
|
+
if (name.startsWith(q) || title.startsWith(q)) return 80;
|
|
580
|
+
if (name.includes(q) || title.includes(q)) return 60;
|
|
581
|
+
if (description.includes(q)) return 30;
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
584
|
+
function searchItems(items, query) {
|
|
585
|
+
return items.map((item) => ({ item, score: scoreItem(item, query) })).filter((entry) => entry.score > 0).sort(
|
|
586
|
+
(a, b) => b.score - a.score || a.item.name.localeCompare(b.item.name)
|
|
587
|
+
).map((entry) => entry.item);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/commands/list.ts
|
|
591
|
+
var TYPE_LABEL = {
|
|
592
|
+
"registry:ui": "\u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A",
|
|
593
|
+
"registry:lib": "\u0627\u0628\u0632\u0627\u0631",
|
|
594
|
+
"registry:theme": "\u062A\u0645"
|
|
595
|
+
};
|
|
596
|
+
async function loadItems(cwd) {
|
|
597
|
+
const config = await configExists(cwd) ? await readConfig(cwd) : defaultConfig();
|
|
598
|
+
const index = await fetchRegistryIndex(config.registryUrl);
|
|
599
|
+
return index.items;
|
|
600
|
+
}
|
|
601
|
+
function matchesType(item, type) {
|
|
602
|
+
if (!type) return true;
|
|
603
|
+
const wanted = type.startsWith("registry:") ? type : `registry:${type}`;
|
|
604
|
+
return item.type === wanted;
|
|
605
|
+
}
|
|
606
|
+
function printTable(items) {
|
|
607
|
+
if (items.length === 0) {
|
|
608
|
+
console.log("\u0686\u06CC\u0632\u06CC \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F.");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const width = Math.max(...items.map((item) => item.name.length));
|
|
612
|
+
const labelWidth = Math.max(
|
|
613
|
+
...items.map((item) => (TYPE_LABEL[item.type] ?? item.type).length)
|
|
614
|
+
);
|
|
615
|
+
for (const item of items) {
|
|
616
|
+
const label = (TYPE_LABEL[item.type] ?? item.type).padEnd(labelWidth);
|
|
617
|
+
const title = item.title ? ` \xB7 ${item.title}` : "";
|
|
618
|
+
console.log(`${item.name.padEnd(width)} ${label}${title}`);
|
|
619
|
+
}
|
|
620
|
+
console.log(`
|
|
621
|
+
${items.length} \u0622\u06CC\u062A\u0645. \u0628\u0631\u0627\u06CC \u0646\u0635\u0628: dig add <\u0646\u0627\u0645>`);
|
|
622
|
+
}
|
|
623
|
+
async function listCommand(options) {
|
|
624
|
+
const items = (await loadItems(process.cwd())).filter(
|
|
625
|
+
(item) => matchesType(item, options.type)
|
|
626
|
+
);
|
|
627
|
+
if (options.json) {
|
|
628
|
+
console.log(JSON.stringify(items, null, 2));
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
printTable(items);
|
|
632
|
+
}
|
|
633
|
+
async function searchCommand(query, options) {
|
|
634
|
+
const items = searchItems(await loadItems(process.cwd()), query);
|
|
635
|
+
if (options.json) {
|
|
636
|
+
console.log(JSON.stringify(items, null, 2));
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (items.length === 0) {
|
|
640
|
+
console.log(
|
|
641
|
+
`\u0686\u06CC\u0632\u06CC \u0628\u0631\u0627\u06CC \xAB${query}\xBB \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F.
|
|
642
|
+
\u0641\u0647\u0631\u0633\u062A \u06A9\u0627\u0645\u0644: dig list`
|
|
643
|
+
);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
printTable(items);
|
|
647
|
+
}
|
|
648
|
+
|
|
386
649
|
// src/lib/diff.ts
|
|
387
650
|
import { readFile as readFile4 } from "fs/promises";
|
|
388
651
|
import path7 from "path";
|
|
@@ -1290,7 +1553,7 @@ async function scanRepositoryForComponent(params) {
|
|
|
1290
1553
|
}
|
|
1291
1554
|
|
|
1292
1555
|
// src/lib/intelligence-export.ts
|
|
1293
|
-
var INTELLIGENCE_EXPORT_SCHEMA_VERSION = "
|
|
1556
|
+
var INTELLIGENCE_EXPORT_SCHEMA_VERSION = "v3";
|
|
1294
1557
|
function buildIntelligenceExport(params) {
|
|
1295
1558
|
const { registryIndexItems, installedComponents } = params;
|
|
1296
1559
|
const healthReport = buildHealthReport({
|
|
@@ -1317,7 +1580,8 @@ function buildIntelligenceExport(params) {
|
|
|
1317
1580
|
migrationPending: governance.impact.requiredMigration,
|
|
1318
1581
|
dependencyImpact: governance.impact.affectedComponents.length,
|
|
1319
1582
|
changeType: governance.changeType,
|
|
1320
|
-
replacement: lifecycle.replacement
|
|
1583
|
+
replacement: lifecycle.replacement,
|
|
1584
|
+
affectedComponentNames: governance.impact.affectedComponents
|
|
1321
1585
|
};
|
|
1322
1586
|
});
|
|
1323
1587
|
return {
|
|
@@ -1372,6 +1636,9 @@ function validateIntelligenceExport(data) {
|
|
|
1372
1636
|
if (typeof entry.changeType !== "string") {
|
|
1373
1637
|
errors.push(`components[${index}].changeType \u0628\u0627\u06CC\u062F \u0631\u0634\u062A\u0647 \u0628\u0627\u0634\u062F.`);
|
|
1374
1638
|
}
|
|
1639
|
+
if (entry.affectedComponentNames !== void 0 && (!Array.isArray(entry.affectedComponentNames) || !entry.affectedComponentNames.every((name) => typeof name === "string"))) {
|
|
1640
|
+
errors.push(`components[${index}].affectedComponentNames \u0628\u0627\u06CC\u062F \u0622\u0631\u0627\u06CC\u0647\u200C\u0627\u06CC \u0627\u0632 \u0631\u0634\u062A\u0647 \u0628\u0627\u0634\u062F.`);
|
|
1641
|
+
}
|
|
1375
1642
|
});
|
|
1376
1643
|
}
|
|
1377
1644
|
return errors.length === 0 ? { valid: true } : { valid: false, errors };
|
|
@@ -1681,28 +1948,77 @@ async function impactCommand(name, options) {
|
|
|
1681
1948
|
}
|
|
1682
1949
|
|
|
1683
1950
|
// src/commands/init.ts
|
|
1951
|
+
import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile5 } from "fs/promises";
|
|
1684
1952
|
import path10 from "path";
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const raw = await rl.question(`${question} ${suffix} `);
|
|
1693
|
-
rl.close();
|
|
1694
|
-
const answer = raw.trim().toLowerCase();
|
|
1695
|
-
if (!answer) return defaultValue;
|
|
1696
|
-
return answer === "y" || answer === "yes";
|
|
1953
|
+
var THEME_ITEM = "theme";
|
|
1954
|
+
async function readIfExists(target) {
|
|
1955
|
+
try {
|
|
1956
|
+
return await readFile7(target, "utf8");
|
|
1957
|
+
} catch {
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1697
1960
|
}
|
|
1698
|
-
async function
|
|
1699
|
-
const
|
|
1700
|
-
const
|
|
1701
|
-
|
|
1702
|
-
|
|
1961
|
+
async function writeTheme(config, cwd, options) {
|
|
1962
|
+
const item = await fetchRegistryItem(THEME_ITEM, config.registryUrl);
|
|
1963
|
+
const file = item.files[0];
|
|
1964
|
+
if (!file) {
|
|
1965
|
+
throw new DigError("\u0622\u06CC\u062A\u0645\u0650 \xABtheme\xBB \u062F\u0631 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u0641\u0627\u06CC\u0644\u06CC \u0646\u062F\u0627\u0631\u062F.");
|
|
1966
|
+
}
|
|
1967
|
+
const target = path10.join(cwd, config.tailwind.css);
|
|
1968
|
+
const existing = await readIfExists(target);
|
|
1969
|
+
const isBoilerplate = existing === null || existing.split("\n").filter((line) => line.trim() && !line.trim().startsWith("@import")).length === 0;
|
|
1970
|
+
if (!isBoilerplate && !options.yes) {
|
|
1971
|
+
console.log(`
|
|
1972
|
+
${config.tailwind.css} \u0627\u0632 \u0642\u0628\u0644 \u0645\u062D\u062A\u0648\u0627 \u062F\u0627\u0631\u062F.`);
|
|
1973
|
+
const overwrite = await confirm(
|
|
1974
|
+
"\u0628\u0627 \u062A\u0645\u0650 \u062F\u06CC\u06AF \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u0634\u0648\u062F\u061F (\u0646\u0633\u062E\u0647\u0654 \u0641\u0639\u0644\u06CC \u06A9\u0646\u0627\u0631\u0634 \u0628\u0647\u200C\u0639\u0646\u0648\u0627\u0646 .backup \u0645\u06CC\u200C\u0645\u0627\u0646\u062F)",
|
|
1975
|
+
false
|
|
1976
|
+
);
|
|
1977
|
+
if (!overwrite) {
|
|
1978
|
+
console.log(
|
|
1979
|
+
`- \u062A\u0645 \u0646\u0648\u0634\u062A\u0647 \u0646\u0634\u062F. \u0628\u0631\u0627\u06CC \u0646\u0648\u0634\u062A\u0646\u0634 \u0628\u0639\u062F\u0627\u064B: dig add ${THEME_ITEM} --overwrite`
|
|
1980
|
+
);
|
|
1981
|
+
return "skipped";
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
if (!isBoilerplate && existing !== null) {
|
|
1985
|
+
await writeFile5(`${target}.backup`, existing, "utf8");
|
|
1986
|
+
console.log(` \u0646\u0633\u062E\u0647\u0654 \u0642\u0628\u0644\u06CC \u062F\u0631 ${config.tailwind.css}.backup \u0646\u06AF\u0647 \u062F\u0627\u0634\u062A\u0647 \u0634\u062F.`);
|
|
1987
|
+
}
|
|
1988
|
+
await mkdir4(path10.dirname(target), { recursive: true });
|
|
1989
|
+
await writeFile5(target, file.content, "utf8");
|
|
1990
|
+
console.log(`\u2713 \u062A\u0645 \u062F\u0631 ${config.tailwind.css} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
|
|
1991
|
+
if (item.dependencies?.length) {
|
|
1992
|
+
const pm = await detectPackageManager(cwd);
|
|
1993
|
+
console.log(`
|
|
1994
|
+
\u0646\u0635\u0628 \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627\u06CC \u062A\u0645 \u0628\u0627 ${pm} \u2026`);
|
|
1995
|
+
await installDependencies(pm, item.dependencies, { cwd });
|
|
1996
|
+
}
|
|
1997
|
+
return "written";
|
|
1998
|
+
}
|
|
1999
|
+
async function applyRtl(cwd) {
|
|
2000
|
+
for (const candidate of ["src/app/layout.tsx", "app/layout.tsx"]) {
|
|
2001
|
+
const target = path10.join(cwd, candidate);
|
|
2002
|
+
const source = await readIfExists(target);
|
|
2003
|
+
if (source === null) continue;
|
|
2004
|
+
if (/<html[^>]*\sdir=/.test(source)) {
|
|
2005
|
+
console.log(`- ${candidate} \u0627\u0632 \u0642\u0628\u0644 dir \u062F\u0627\u0631\u062F\u061B \u062F\u0633\u062A \u0646\u062E\u0648\u0631\u062F.`);
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
const match = source.match(/<html\b([^>]*)>/);
|
|
2009
|
+
if (!match) break;
|
|
2010
|
+
const updated = source.replace(
|
|
2011
|
+
match[0],
|
|
2012
|
+
`<html${match[1]} dir="rtl">`.replace(/\s+dir="rtl">$/, ' dir="rtl">')
|
|
2013
|
+
);
|
|
2014
|
+
await writeFile5(target, updated, "utf8");
|
|
2015
|
+
console.log(`\u2713 dir="rtl" \u0628\u0647 ${candidate} \u0627\u0636\u0627\u0641\u0647 \u0634\u062F.`);
|
|
2016
|
+
return;
|
|
2017
|
+
}
|
|
2018
|
+
console.log(
|
|
2019
|
+
'- \u0686\u06CC\u062F\u0645\u0627\u0646\u0650 \u0631\u06CC\u0634\u0647 \u067E\u06CC\u062F\u0627 \u0646\u0634\u062F. \u062E\u0648\u062F\u062A\u0627\u0646 dir="rtl" \u0631\u0627 \u0631\u0648\u06CC \u062A\u06AF <html> \u0628\u06AF\u0630\u0627\u0631\u06CC\u062F.'
|
|
2020
|
+
);
|
|
1703
2021
|
}
|
|
1704
|
-
|
|
1705
|
-
// src/commands/init.ts
|
|
1706
2022
|
async function initCommand(options) {
|
|
1707
2023
|
const cwd = process.cwd();
|
|
1708
2024
|
const base = defaultConfig();
|
|
@@ -1726,10 +2042,22 @@ async function initCommand(options) {
|
|
|
1726
2042
|
};
|
|
1727
2043
|
await writeConfig(config, cwd);
|
|
1728
2044
|
console.log(`\u2713 ${path10.join(cwd, "dig.json")} \u0646\u0648\u0634\u062A\u0647 \u0634\u062F.`);
|
|
2045
|
+
const theme = await writeTheme(config, cwd, options);
|
|
2046
|
+
if (config.rtl) {
|
|
2047
|
+
await applyRtl(cwd);
|
|
2048
|
+
}
|
|
2049
|
+
console.log("\n\u0622\u0645\u0627\u062F\u0647 \u0627\u0633\u062A. \u0642\u062F\u0645 \u0628\u0639\u062F\u06CC:");
|
|
2050
|
+
console.log(" dig add button");
|
|
2051
|
+
if (theme === "skipped") {
|
|
2052
|
+
console.log(
|
|
2053
|
+
`
|
|
2054
|
+
\u26A0 \u0628\u062F\u0648\u0646 \u062A\u0645\u060C \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A\u200C\u0647\u0627 \u0646\u0635\u0628 \u0645\u06CC\u200C\u0634\u0648\u0646\u062F \u0648\u0644\u06CC \u0628\u06CC\u200C\u0638\u0627\u0647\u0631 \u0645\u06CC\u200C\u0645\u0627\u0646\u0646\u062F (\u0631\u0646\u06AF\u060C \u0634\u0639\u0627\u0639 \u0648 \u0633\u0627\u06CC\u0647 \u0627\u0632 \u062A\u0648\u06A9\u0646 \u0645\u06CC\u200C\u0622\u06CC\u0646\u062F).`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
1729
2057
|
}
|
|
1730
2058
|
|
|
1731
2059
|
// src/lib/migration-apply.ts
|
|
1732
|
-
import { readFile as
|
|
2060
|
+
import { readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
1733
2061
|
import path11 from "path";
|
|
1734
2062
|
|
|
1735
2063
|
// src/lib/migration-strategy.ts
|
|
@@ -1807,7 +2135,7 @@ function looksBinary2(buffer) {
|
|
|
1807
2135
|
async function readTextFileSafely2(target) {
|
|
1808
2136
|
let buffer;
|
|
1809
2137
|
try {
|
|
1810
|
-
buffer = await
|
|
2138
|
+
buffer = await readFile8(target);
|
|
1811
2139
|
} catch (error) {
|
|
1812
2140
|
if (error.code === "ENOENT") return void 0;
|
|
1813
2141
|
throw error;
|
|
@@ -1919,12 +2247,12 @@ async function planOrApplyMigrations(params) {
|
|
|
1919
2247
|
const written = [];
|
|
1920
2248
|
try {
|
|
1921
2249
|
for (const item of pending) {
|
|
1922
|
-
await
|
|
2250
|
+
await writeFile6(item.target, item.newContent, "utf8");
|
|
1923
2251
|
written.push(item);
|
|
1924
2252
|
}
|
|
1925
2253
|
} catch (error) {
|
|
1926
2254
|
for (const item of written) {
|
|
1927
|
-
await
|
|
2255
|
+
await writeFile6(item.target, item.originalContent, "utf8");
|
|
1928
2256
|
}
|
|
1929
2257
|
return {
|
|
1930
2258
|
name,
|
|
@@ -2535,7 +2863,7 @@ ${entry.migrationHint}`);
|
|
|
2535
2863
|
}
|
|
2536
2864
|
|
|
2537
2865
|
// src/lib/update.ts
|
|
2538
|
-
import { mkdir as
|
|
2866
|
+
import { mkdir as mkdir5, readFile as readFile9, rm as rm2, writeFile as writeFile7 } from "fs/promises";
|
|
2539
2867
|
import path12 from "path";
|
|
2540
2868
|
function safeFileIndexes(drift) {
|
|
2541
2869
|
const indexes = [];
|
|
@@ -2564,14 +2892,14 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun) {
|
|
|
2564
2892
|
newContent: registryFile.content,
|
|
2565
2893
|
newHash: registryFile.hash ?? hashContent(registryFile.content),
|
|
2566
2894
|
isNewFile,
|
|
2567
|
-
originalContent: isNewFile ? void 0 : await
|
|
2895
|
+
originalContent: isNewFile ? void 0 : await readFile9(target, "utf8")
|
|
2568
2896
|
});
|
|
2569
2897
|
}
|
|
2570
2898
|
const written = [];
|
|
2571
2899
|
try {
|
|
2572
2900
|
for (const item of pending) {
|
|
2573
|
-
if (item.isNewFile) await
|
|
2574
|
-
await
|
|
2901
|
+
if (item.isNewFile) await mkdir5(path12.dirname(item.target), { recursive: true });
|
|
2902
|
+
await writeFile7(item.target, item.newContent, "utf8");
|
|
2575
2903
|
written.push(item);
|
|
2576
2904
|
}
|
|
2577
2905
|
} catch (error) {
|
|
@@ -2579,7 +2907,7 @@ async function applySafeFiles(cwd, aliases, drift, registryItem, dryRun) {
|
|
|
2579
2907
|
if (item.isNewFile) {
|
|
2580
2908
|
await rm2(item.target, { force: true });
|
|
2581
2909
|
} else {
|
|
2582
|
-
await
|
|
2910
|
+
await writeFile7(item.target, item.originalContent, "utf8");
|
|
2583
2911
|
}
|
|
2584
2912
|
}
|
|
2585
2913
|
return { actions, writtenHashes: {}, rollbackError: error.message };
|
|
@@ -2867,11 +3195,18 @@ ${result.name}`);
|
|
|
2867
3195
|
}
|
|
2868
3196
|
}
|
|
2869
3197
|
|
|
3198
|
+
// src/lib/version.ts
|
|
3199
|
+
var CLI_VERSION = "0.3.0";
|
|
3200
|
+
|
|
2870
3201
|
// src/index.ts
|
|
2871
3202
|
var program = new Command();
|
|
2872
|
-
program.name("dig").description("CLI \u0631\u0633\u0645\u06CC \u062F\u06CC\u0632\u0627\u06CC\u0646 \u0633\u06CC\u0633\u062A\u0645 \u062F\u06CC\u06AF").version(
|
|
2873
|
-
program.command("init").description(
|
|
2874
|
-
|
|
3203
|
+
program.name("dig").description("CLI \u0631\u0633\u0645\u06CC \u062F\u06CC\u0632\u0627\u06CC\u0646 \u0633\u06CC\u0633\u062A\u0645 \u062F\u06CC\u06AF").version(CLI_VERSION);
|
|
3204
|
+
program.command("init").description(
|
|
3205
|
+
'\u0631\u0627\u0647\u200C\u0627\u0646\u062F\u0627\u0632\u06CC \u062F\u06CC\u06AF \u062F\u0631 \u067E\u0631\u0648\u0698\u0647: dig.json\u060C \u062A\u0645 \u0648 \u062A\u0648\u06A9\u0646\u200C\u0647\u0627\u060C \u0648 dir="rtl"'
|
|
3206
|
+
).option("--yes", "\u067E\u0627\u0633\u062E \u067E\u06CC\u0634\u200C\u0641\u0631\u0636 \u0628\u0647 \u0647\u0645\u0647\u0654 \u0633\u0648\u0627\u0644\u0627\u062A", false).action(initCommand);
|
|
3207
|
+
program.command("add").description("\u0627\u0641\u0632\u0648\u062F\u0646 \u06CC\u06A9 \u06CC\u0627 \u0686\u0646\u062F \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u0627\u0632 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u062F\u06CC\u06AF").argument("[components...]", "\u0646\u0627\u0645 \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A\u200C\u0647\u0627 (\u0645\u062B\u0644 button) \u06CC\u0627 \u0622\u062F\u0631\u0633 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC\u061B \u0628\u062F\u0648\u0646 \u0622\u0631\u06AF\u0648\u0645\u0627\u0646 \u0641\u0647\u0631\u0633\u062A \u0631\u0627 \u0646\u0634\u0627\u0646 \u0645\u06CC\u200C\u062F\u0647\u062F").option("--overwrite", "\u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0645\u0648\u062C\u0648\u062F", false).option("-y, --yes", "\u0628\u062F\u0648\u0646 \u067E\u0631\u0633\u0634\u060C \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0645\u0648\u062C\u0648\u062F \u0631\u0627 \u0628\u0627\u0632\u0646\u0648\u06CC\u0633\u06CC \u06A9\u0646", false).option("--dry-run", "\u0641\u0642\u0637 \u0646\u0634\u0627\u0646 \u0628\u062F\u0647 \u0686\u0647 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC\u06CC \u0646\u0648\u0634\u062A\u0647 \u0645\u06CC\u200C\u0634\u0648\u062F\u060C \u0686\u06CC\u0632\u06CC \u0646\u0646\u0648\u06CC\u0633", false).option("--no-install", "\u0641\u0642\u0637 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627 \u0631\u0627 \u0628\u0646\u0648\u06CC\u0633\u060C \u0648\u0627\u0628\u0633\u062A\u06AF\u06CC\u200C\u0647\u0627 \u0631\u0627 \u0646\u0635\u0628 \u0646\u06A9\u0646").action(addCommand);
|
|
3208
|
+
program.command("list").description("\u0641\u0647\u0631\u0633\u062A \u0622\u06CC\u062A\u0645\u200C\u0647\u0627\u06CC \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u062F\u06CC\u06AF").option("--type <type>", "\u0641\u06CC\u0644\u062A\u0631 \u0646\u0648\u0639: ui\u060C lib \u06CC\u0627 theme").option("--json", "\u062E\u0631\u0648\u062C\u06CC JSON \u0628\u0631\u0627\u06CC \u0627\u0633\u06A9\u0631\u06CC\u067E\u062A/\u0627\u0628\u0632\u0627\u0631", false).action(listCommand);
|
|
3209
|
+
program.command("search").description("\u062C\u0633\u062A\u062C\u0648\u06CC \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A \u062F\u0631 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC\u060C \u0628\u0627 \u0646\u0627\u0645 \u0641\u0627\u0631\u0633\u06CC \u06CC\u0627 \u0627\u0646\u06AF\u0644\u06CC\u0633\u06CC").argument("<query>", "\u0639\u0628\u0627\u0631\u062A \u062C\u0633\u062A\u062C\u0648").option("--json", "\u062E\u0631\u0648\u062C\u06CC JSON \u0628\u0631\u0627\u06CC \u0627\u0633\u06A9\u0631\u06CC\u067E\u062A/\u0627\u0628\u0632\u0627\u0631", false).action(searchCommand);
|
|
2875
3210
|
program.command("diff").description(
|
|
2876
3211
|
"\u0645\u0642\u0627\u06CC\u0633\u0647\u0654 \u0641\u0642\u0637\u200C\u062E\u0648\u0627\u0646\u062F\u0646\u06CC\u0650 \u0646\u0633\u062E\u0647\u0654 \u0646\u0635\u0628\u200C\u0634\u062F\u0647 \u0628\u0627 \u0631\u062C\u06CC\u0633\u062A\u0631\u06CC \u0648 \u0641\u0627\u06CC\u0644\u200C\u0647\u0627\u06CC \u0645\u062D\u0644\u06CC (\u0628\u062F\u0648\u0646 \u062A\u063A\u06CC\u06CC\u0631 \u0641\u0627\u06CC\u0644)"
|
|
2877
3212
|
).argument("<components...>", "\u0646\u0627\u0645 \u06A9\u0627\u0645\u067E\u0648\u0646\u0646\u062A\u200C\u0647\u0627 (\u0645\u062B\u0644 button)").option("--json", "\u062E\u0631\u0648\u062C\u06CC JSON \u0628\u0631\u0627\u06CC \u0627\u0633\u06A9\u0631\u06CC\u067E\u062A/\u0627\u0628\u0632\u0627\u0631", false).option(
|