@donezone/cli 0.1.1 → 0.1.3
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 +189 -14
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -13,20 +13,14 @@ program
|
|
|
13
13
|
.description("Bundle Done contracts defined in done.json")
|
|
14
14
|
.option("-j, --contracts <path>", "Path to done.json")
|
|
15
15
|
.option("-n, --name <name...>", "Filter contract names")
|
|
16
|
-
.action(() => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
Output compiled js artifacts to <cwd>/dist folder.
|
|
26
|
-
|
|
27
|
-
If cwd is a contract workspace, build all contract packages in workspace.
|
|
28
|
-
|
|
29
|
-
*/
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
try {
|
|
18
|
+
await handleBuild(options);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
30
24
|
});
|
|
31
25
|
program
|
|
32
26
|
.command("dev")
|
|
@@ -81,6 +75,50 @@ async function handleInit(rawName, options) {
|
|
|
81
75
|
}
|
|
82
76
|
await scaffoldWorkspace(rawName, options);
|
|
83
77
|
}
|
|
78
|
+
async function handleBuild(options) {
|
|
79
|
+
const manifestInfo = await resolveManifest(options.contracts);
|
|
80
|
+
const contracts = manifestInfo.manifest.contracts ?? [];
|
|
81
|
+
if (contracts.length === 0) {
|
|
82
|
+
throw new Error(`No contracts defined in ${path.relative(process.cwd(), manifestInfo.manifestPath) || manifestInfo.manifestPath}`);
|
|
83
|
+
}
|
|
84
|
+
const filters = (options.name ?? [])
|
|
85
|
+
.flatMap((pattern) => pattern.split(","))
|
|
86
|
+
.map((pattern) => pattern.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
const matchers = filters.map(createGlobMatcher);
|
|
89
|
+
const selected = matchers.length === 0
|
|
90
|
+
? contracts
|
|
91
|
+
: contracts.filter((contract) => matchesContract(contract, manifestInfo.manifestDir, matchers));
|
|
92
|
+
if (selected.length === 0) {
|
|
93
|
+
throw new Error(`No contracts matched filters: ${filters.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
console.log(`Building ${selected.length} contract${selected.length === 1 ? "" : "s"} from ${path.relative(process.cwd(), manifestInfo.manifestPath) || manifestInfo.manifestPath}`);
|
|
96
|
+
for (const contract of selected) {
|
|
97
|
+
if (!contract.entry) {
|
|
98
|
+
throw new Error(`Contract '${contract.name}' is missing an 'entry' path in ${manifestInfo.manifestPath}`);
|
|
99
|
+
}
|
|
100
|
+
if (!contract.outFile) {
|
|
101
|
+
throw new Error(`Contract '${contract.name}' is missing an 'outFile' path in ${manifestInfo.manifestPath}`);
|
|
102
|
+
}
|
|
103
|
+
const entryPath = path.resolve(manifestInfo.manifestDir, contract.entry);
|
|
104
|
+
const outFilePath = path.resolve(manifestInfo.manifestDir, contract.outFile);
|
|
105
|
+
await ensureEntryExists(entryPath, contract.name);
|
|
106
|
+
await fs.mkdir(path.dirname(outFilePath), { recursive: true });
|
|
107
|
+
const relEntry = path.relative(process.cwd(), entryPath) || entryPath;
|
|
108
|
+
const relOutFile = path.relative(process.cwd(), outFilePath) || outFilePath;
|
|
109
|
+
console.log(`- bundling ${contract.name} (${relEntry})`);
|
|
110
|
+
const startedAt = Date.now();
|
|
111
|
+
try {
|
|
112
|
+
await runBunBuild(entryPath, outFilePath);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error(`[error] failed to build ${contract.name}`);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
const duration = Date.now() - startedAt;
|
|
119
|
+
console.log(` [done] ${contract.name} -> ${relOutFile} (${formatDuration(duration)})`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
84
122
|
async function detectWorkspace(root) {
|
|
85
123
|
const manifestPath = path.join(root, "done.json");
|
|
86
124
|
const configPath = path.join(root, "done.config.json");
|
|
@@ -95,6 +133,50 @@ async function detectWorkspace(root) {
|
|
|
95
133
|
const config = JSON.parse(configRaw);
|
|
96
134
|
return { root, manifestPath, configPath, manifest, config };
|
|
97
135
|
}
|
|
136
|
+
async function resolveManifest(contractsPathOverride) {
|
|
137
|
+
if (contractsPathOverride) {
|
|
138
|
+
const manifestPath = path.resolve(process.cwd(), contractsPathOverride);
|
|
139
|
+
const manifest = await readManifest(manifestPath);
|
|
140
|
+
return { manifestPath, manifestDir: path.dirname(manifestPath), manifest };
|
|
141
|
+
}
|
|
142
|
+
const workspace = await findWorkspace(process.cwd());
|
|
143
|
+
if (!workspace) {
|
|
144
|
+
throw new Error("Unable to find done.json. Run inside a Done workspace or pass --contracts <path>.");
|
|
145
|
+
}
|
|
146
|
+
return { manifestPath: workspace.manifestPath, manifestDir: path.dirname(workspace.manifestPath), manifest: workspace.manifest };
|
|
147
|
+
}
|
|
148
|
+
async function findWorkspace(startDir) {
|
|
149
|
+
let current = path.resolve(startDir);
|
|
150
|
+
while (true) {
|
|
151
|
+
const workspace = await detectWorkspace(current);
|
|
152
|
+
if (workspace) {
|
|
153
|
+
return workspace;
|
|
154
|
+
}
|
|
155
|
+
const parent = path.dirname(current);
|
|
156
|
+
if (parent === current) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
current = parent;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
async function readManifest(manifestPath) {
|
|
163
|
+
let raw;
|
|
164
|
+
try {
|
|
165
|
+
raw = await fs.readFile(manifestPath, "utf8");
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
if (error.code === "ENOENT") {
|
|
169
|
+
throw new Error(`Could not find ${manifestPath}. Pass a valid --contracts path or run from a Done workspace.`);
|
|
170
|
+
}
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(raw);
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
throw new Error(`Failed to parse ${manifestPath}: ${error.message}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
98
180
|
async function scaffoldWorkspace(rawName, options) {
|
|
99
181
|
const slug = toSlug(rawName);
|
|
100
182
|
if (!slug) {
|
|
@@ -302,6 +384,99 @@ function normalizeJsonPath(relativePath) {
|
|
|
302
384
|
const normalized = relativePath.split(path.sep).join("/");
|
|
303
385
|
return normalized.startsWith("./") || normalized.startsWith("../") ? normalized : `./${normalized}`;
|
|
304
386
|
}
|
|
387
|
+
function matchesContract(contract, manifestDir, matchers) {
|
|
388
|
+
const candidates = [];
|
|
389
|
+
if (contract.name) {
|
|
390
|
+
candidates.push(normalizeMatchValue(contract.name));
|
|
391
|
+
}
|
|
392
|
+
if (contract.entry) {
|
|
393
|
+
candidates.push(normalizeMatchValue(contract.entry));
|
|
394
|
+
candidates.push(normalizeMatchValue(path.resolve(manifestDir, contract.entry)));
|
|
395
|
+
}
|
|
396
|
+
if (contract.outFile) {
|
|
397
|
+
candidates.push(normalizeMatchValue(contract.outFile));
|
|
398
|
+
candidates.push(normalizeMatchValue(path.resolve(manifestDir, contract.outFile)));
|
|
399
|
+
}
|
|
400
|
+
return matchers.some((matcher) => candidates.some((candidate) => matcher(candidate)));
|
|
401
|
+
}
|
|
402
|
+
function createGlobMatcher(pattern) {
|
|
403
|
+
const normalized = normalizeMatchValue(pattern);
|
|
404
|
+
const hasWildcards = /[\*\?]/.test(normalized);
|
|
405
|
+
if (!hasWildcards) {
|
|
406
|
+
return (candidate) => normalizeMatchValue(candidate) === normalized;
|
|
407
|
+
}
|
|
408
|
+
const escaped = escapeRegExp(normalized)
|
|
409
|
+
.replace(/\\\*/g, ".*")
|
|
410
|
+
.replace(/\\\?/g, ".");
|
|
411
|
+
const regex = new RegExp(`^${escaped}$`);
|
|
412
|
+
return (candidate) => regex.test(normalizeMatchValue(candidate));
|
|
413
|
+
}
|
|
414
|
+
function normalizeMatchValue(value) {
|
|
415
|
+
if (!value) {
|
|
416
|
+
return "";
|
|
417
|
+
}
|
|
418
|
+
let normalized = value.replace(/\\/g, "/");
|
|
419
|
+
if (normalized.startsWith("./")) {
|
|
420
|
+
normalized = normalized.slice(2);
|
|
421
|
+
}
|
|
422
|
+
return normalized;
|
|
423
|
+
}
|
|
424
|
+
function escapeRegExp(value) {
|
|
425
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
426
|
+
}
|
|
427
|
+
async function ensureEntryExists(entryPath, contractName) {
|
|
428
|
+
try {
|
|
429
|
+
const stats = await fs.stat(entryPath);
|
|
430
|
+
if (!stats.isFile()) {
|
|
431
|
+
const relPath = path.relative(process.cwd(), entryPath) || entryPath;
|
|
432
|
+
throw new Error(`Entry path for contract '${contractName}' is not a file (${relPath})`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
if (error.code === "ENOENT") {
|
|
437
|
+
const rel = path.relative(process.cwd(), entryPath) || entryPath;
|
|
438
|
+
throw new Error(`Entry file for contract '${contractName}' not found (${rel})`);
|
|
439
|
+
}
|
|
440
|
+
throw error;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async function runBunBuild(entryPath, outFilePath) {
|
|
444
|
+
const bun = getBunRuntime();
|
|
445
|
+
const result = await bun.build({
|
|
446
|
+
entrypoints: [entryPath],
|
|
447
|
+
target: "bun",
|
|
448
|
+
format: "esm",
|
|
449
|
+
splitting: false,
|
|
450
|
+
sourcemap: "none",
|
|
451
|
+
external: ["bun:*", "node:*"],
|
|
452
|
+
define: {
|
|
453
|
+
"process.env.NODE_ENV": '"production"',
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
if (!result.success) {
|
|
457
|
+
const logs = (result.logs ?? []).map((log) => log?.message ?? String(log));
|
|
458
|
+
throw new Error(logs.join("\n") || "Bun.build failed");
|
|
459
|
+
}
|
|
460
|
+
const [output] = result.outputs;
|
|
461
|
+
if (!output) {
|
|
462
|
+
throw new Error("Bun.build did not emit an output file");
|
|
463
|
+
}
|
|
464
|
+
const bundledSource = await output.text();
|
|
465
|
+
await fs.writeFile(outFilePath, bundledSource);
|
|
466
|
+
}
|
|
467
|
+
function getBunRuntime() {
|
|
468
|
+
const bun = globalThis.Bun;
|
|
469
|
+
if (!bun || typeof bun.build !== "function") {
|
|
470
|
+
throw new Error("Bun runtime unavailable. Rebuild the done CLI using Bun to embed the runtime.");
|
|
471
|
+
}
|
|
472
|
+
return bun;
|
|
473
|
+
}
|
|
474
|
+
function formatDuration(ms) {
|
|
475
|
+
if (ms < 1000) {
|
|
476
|
+
return `${ms}ms`;
|
|
477
|
+
}
|
|
478
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
479
|
+
}
|
|
305
480
|
function toSlug(input) {
|
|
306
481
|
return input
|
|
307
482
|
.trim()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@donezone/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc -p tsconfig.json",
|
|
14
|
-
"clean": "rimraf dist"
|
|
14
|
+
"clean": "rimraf dist",
|
|
15
|
+
"test": "bun test"
|
|
15
16
|
},
|
|
16
17
|
"dependencies": {
|
|
17
18
|
"@donezone/core": "^0.1.0",
|