@madojs/mado 0.10.1 → 0.11.1
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/AGENTS.md +24 -26
- package/CHANGELOG.md +95 -0
- package/README.md +22 -47
- package/TODO.md +52 -48
- package/dist/src/component.d.ts +2 -1
- package/dist/src/component.js +5 -2
- package/dist/src/component.js.map +1 -1
- package/dist/src/each.d.ts +1 -1
- package/dist/src/each.js +1 -1
- package/dist/src/each.js.map +1 -1
- package/dist/src/html/bindings.js +3 -3
- package/dist/src/html/bindings.js.map +1 -1
- package/dist/src/index.d.ts +11 -6
- package/dist/src/index.js +5 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/lazy.d.ts +1 -1
- package/dist/src/lazy.js +1 -1
- package/dist/src/lazy.js.map +1 -1
- package/dist/src/page.d.ts +17 -21
- package/dist/src/page.js +7 -12
- package/dist/src/page.js.map +1 -1
- package/dist/src/router/manifest.d.ts +1 -1
- package/dist/src/router/manifest.js +21 -13
- package/dist/src/router/manifest.js.map +1 -1
- package/dist/src/router/match.d.ts +2 -2
- package/dist/src/router/match.js +3 -3
- package/dist/src/router/match.js.map +1 -1
- package/dist/src/router/navigation.js +1 -1
- package/dist/src/router/navigation.js.map +1 -1
- package/dist/src/vite/index.d.ts +10 -0
- package/dist/src/vite/index.js +33 -0
- package/dist/src/vite/index.js.map +1 -0
- package/docs/en/00-the-mado-way.md +25 -12
- package/docs/en/01-routing.md +90 -142
- package/docs/en/02-project-layout.md +59 -53
- package/docs/en/03-static-bake.md +5 -6
- package/docs/en/05-why-mado.md +6 -6
- package/docs/en/06-for-backenders.md +18 -22
- package/docs/en/08-llm-zero-history-test.md +9 -14
- package/docs/en/09-shadow-vs-light-dom.md +28 -36
- package/docs/en/10-app-architecture.md +158 -96
- package/docs/en/11-layouts.md +22 -24
- package/docs/en/12-auth-and-api.md +89 -182
- package/docs/en/13-deployment.md +18 -22
- package/docs/en/14-testing.md +4 -4
- package/docs/en/16-bake-cookbook.md +11 -12
- package/docs/en/18-api-freeze-map.md +6 -4
- package/docs/en/20-v1-stability.md +1 -1
- package/docs/fr/00-the-mado-way.md +55 -90
- package/docs/fr/01-routing.md +70 -152
- package/docs/fr/02-project-layout.md +61 -42
- package/docs/fr/03-static-bake.md +1 -1
- package/docs/fr/05-why-mado.md +6 -6
- package/docs/fr/06-for-backenders.md +7 -7
- package/docs/fr/08-llm-zero-history-test.md +21 -48
- package/docs/fr/09-shadow-vs-light-dom.md +43 -162
- package/docs/fr/10-app-architecture.md +110 -33
- package/docs/fr/11-layouts.md +24 -12
- package/docs/fr/12-auth-and-api.md +63 -22
- package/docs/fr/13-deployment.md +7 -10
- package/docs/fr/14-testing.md +1 -1
- package/docs/fr/16-bake-cookbook.md +2 -2
- package/docs/fr/18-api-freeze-map.md +1 -1
- package/docs/fr/20-v1-stability.md +1 -1
- package/docs/recipes/nginx/README.md +13 -0
- package/docs/ru/00-the-mado-way.md +53 -75
- package/docs/ru/01-routing.md +68 -143
- package/docs/ru/02-project-layout.md +61 -41
- package/docs/ru/03-static-bake.md +2 -2
- package/docs/ru/05-why-mado.md +6 -6
- package/docs/ru/06-for-backenders.md +7 -7
- package/docs/ru/08-llm-zero-history-test.md +9 -14
- package/docs/ru/09-shadow-vs-light-dom.md +43 -178
- package/docs/ru/10-app-architecture.md +115 -63
- package/docs/ru/11-layouts.md +24 -24
- package/docs/ru/12-auth-and-api.md +57 -35
- package/docs/ru/13-deployment.md +7 -11
- package/docs/ru/14-testing.md +1 -1
- package/docs/ru/16-bake-cookbook.md +12 -6
- package/docs/ru/18-api-freeze-map.md +5 -3
- package/docs/ru/20-v1-stability.md +1 -1
- package/docs/uk/00-the-mado-way.md +70 -44
- package/docs/uk/01-routing.md +41 -47
- package/docs/uk/02-project-layout.md +68 -41
- package/docs/uk/03-static-bake.md +1 -2
- package/docs/uk/06-for-backenders.md +3 -3
- package/docs/uk/08-llm-zero-history-test.md +22 -24
- package/docs/uk/09-shadow-vs-light-dom.md +37 -86
- package/docs/uk/10-app-architecture.md +72 -31
- package/docs/uk/11-layouts.md +25 -12
- package/docs/uk/12-auth-and-api.md +58 -22
- package/docs/uk/13-deployment.md +4 -3
- package/docs/uk/14-testing.md +1 -1
- package/docs/uk/18-api-freeze-map.md +1 -1
- package/docs/uk/20-v1-stability.md +1 -1
- package/llms.txt +14 -15
- package/package.json +18 -11
- package/scripts/_config.mjs +15 -161
- package/scripts/bake.mjs +74 -63
- package/scripts/cli/generate.mjs +348 -0
- package/scripts/cli/help.mjs +27 -0
- package/scripts/cli/index.mjs +79 -0
- package/scripts/cli/init.mjs +153 -0
- package/scripts/cli/release.mjs +152 -0
- package/scripts/cli/run.mjs +96 -0
- package/scripts/cli.mjs +2 -621
- package/scripts/package-smoke.mjs +4 -1
- package/scripts/preview.mjs +13 -37
- package/scripts/size-budget.mjs +5 -2
- package/scripts/vite.default.mjs +11 -0
- package/starters/default/.editorconfig +12 -0
- package/starters/default/README.md +74 -0
- package/starters/default/eslint.config.mjs +256 -0
- package/starters/default/index.html +13 -0
- package/starters/default/package.json +30 -0
- package/starters/default/public/favicon.svg +4 -0
- package/starters/default/src/app.routes.ts +39 -0
- package/starters/default/src/layouts/app-shell.layout.ts +35 -0
- package/starters/default/src/layouts/auth-shell.layout.ts +17 -0
- package/starters/default/src/main.ts +16 -0
- package/starters/default/src/modules/auth/_contracts/auth-api.types.ts +17 -0
- package/starters/default/src/modules/auth/auth.connector.ts +45 -0
- package/starters/default/src/modules/auth/auth.guard.ts +22 -0
- package/starters/default/src/modules/auth/auth.public.ts +9 -0
- package/starters/default/src/modules/auth/auth.routes.ts +8 -0
- package/starters/default/src/modules/auth/auth.service.ts +71 -0
- package/starters/default/src/modules/auth/auth.types.ts +15 -0
- package/starters/default/src/modules/auth/login.page.ts +62 -0
- package/starters/default/src/modules/billing/_contracts/stripe.types.ts +17 -0
- package/starters/default/src/modules/billing/api/stripe.connector.ts +71 -0
- package/starters/default/src/modules/billing/billing.public.ts +5 -0
- package/starters/default/src/modules/billing/billing.routes.ts +9 -0
- package/starters/default/src/modules/billing/billing.types.ts +15 -0
- package/starters/default/src/modules/billing/components/invoice-status-badge.component.ts +43 -0
- package/starters/default/src/modules/billing/data/invoices.resource.ts +35 -0
- package/starters/default/src/modules/billing/pages/invoice-detail.page.ts +70 -0
- package/starters/default/src/modules/billing/pages/invoices-list.page.ts +73 -0
- package/starters/default/src/modules/home/home.page.ts +34 -0
- package/starters/default/src/modules/home/not-found.page.ts +11 -0
- package/starters/default/src/shared/http/http-client.ts +86 -0
- package/starters/default/src/shared/http/http-error.ts +37 -0
- package/starters/default/src/shared/http/interceptors.ts +59 -0
- package/starters/default/src/shared/lib/format-date.ts +19 -0
- package/starters/default/src/shared/styles/content.css +70 -0
- package/starters/default/src/shared/styles/reset.css +32 -0
- package/starters/default/src/shared/styles/shell.css +57 -0
- package/starters/default/src/shared/styles/tokens.css +44 -0
- package/starters/default/src/shared/ui/x-button.component.ts +49 -0
- package/starters/default/src/shared/ui/x-spinner.component.ts +22 -0
- package/starters/default/src/styles.d.ts +1 -0
- package/starters/default/src/vite-env.d.ts +1 -0
- package/starters/default/tsconfig.json +24 -0
- package/starters/default/vite.config.ts +9 -0
- package/MADO_V1_PLAN.md +0 -179
- package/ROADMAP.md +0 -178
- package/dist/src/html.d.ts +0 -18
- package/dist/src/html.js +0 -17
- package/dist/src/html.js.map +0 -1
- package/dist/src/router.d.ts +0 -13
- package/dist/src/router.js +0 -13
- package/dist/src/router.js.map +0 -1
- package/scripts/bundle.mjs +0 -212
- package/scripts/llm-zero-history-smoke.mjs +0 -93
- package/scripts/new.mjs +0 -80
- package/scripts/showcase-regression.mjs +0 -392
- package/server/serve.mjs +0 -455
- package/starters/admin/README.md +0 -63
- package/starters/admin/index.html +0 -28
- package/starters/admin/mado.config.json +0 -22
- package/starters/admin/package.json +0 -24
- package/starters/admin/public/favicon.svg +0 -4
- package/starters/admin/src/components/x-button.ts +0 -82
- package/starters/admin/src/components/x-input.ts +0 -105
- package/starters/admin/src/layouts/app.ts +0 -101
- package/starters/admin/src/layouts/auth.ts +0 -41
- package/starters/admin/src/lib/api.ts +0 -184
- package/starters/admin/src/lib/auth.ts +0 -83
- package/starters/admin/src/main.ts +0 -15
- package/starters/admin/src/pages/admin/dashboard.ts +0 -48
- package/starters/admin/src/pages/admin/order-detail.ts +0 -80
- package/starters/admin/src/pages/admin/orders.ts +0 -117
- package/starters/admin/src/pages/home.ts +0 -34
- package/starters/admin/src/pages/login.ts +0 -70
- package/starters/admin/src/pages/not-found.ts +0 -12
- package/starters/admin/src/routes.ts +0 -40
- package/starters/admin/src/styles/global.ts +0 -86
- package/starters/admin/tsconfig.json +0 -15
- package/starters/crud/README.md +0 -33
- package/starters/crud/index.html +0 -28
- package/starters/crud/mado.config.json +0 -20
- package/starters/crud/package.json +0 -24
- package/starters/crud/src/components/app-shell.ts +0 -56
- package/starters/crud/src/components/ticket-detail.ts +0 -33
- package/starters/crud/src/components/ticket-form.ts +0 -69
- package/starters/crud/src/components/ticket-list.ts +0 -66
- package/starters/crud/src/lib/api.ts +0 -76
- package/starters/crud/src/main.ts +0 -9
- package/starters/crud/src/pages/home.ts +0 -34
- package/starters/crud/src/pages/not-found.ts +0 -12
- package/starters/crud/src/pages/ticket-detail.ts +0 -7
- package/starters/crud/src/pages/ticket-new.ts +0 -7
- package/starters/crud/src/pages/tickets.ts +0 -7
- package/starters/crud/src/routes.ts +0 -11
- package/starters/crud/src/styles/global.ts +0 -155
- package/starters/crud/tsconfig.json +0 -15
- package/starters/minimal/README.md +0 -21
- package/starters/minimal/index.html +0 -28
- package/starters/minimal/mado.config.json +0 -20
- package/starters/minimal/package.json +0 -24
- package/starters/minimal/src/components/app-counter.ts +0 -31
- package/starters/minimal/src/main.ts +0 -9
- package/starters/minimal/src/pages/home.ts +0 -35
- package/starters/minimal/src/pages/not-found.ts +0 -14
- package/starters/minimal/src/routes.ts +0 -8
- package/starters/minimal/src/styles/global.ts +0 -60
- package/starters/minimal/tsconfig.json +0 -15
- package/templates/page-detail.ts +0 -63
- package/templates/page-form.ts +0 -94
- package/templates/page-list.ts +0 -79
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { copyFile, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { brotliCompressSync, constants as zlibConst, gzipSync } from "node:zlib";
|
|
5
|
+
|
|
6
|
+
import { parseFlags } from "../_config.mjs";
|
|
7
|
+
import { runNodeBin, runNodeScript, runVite, writeIfMissing } from "./run.mjs";
|
|
8
|
+
|
|
9
|
+
export async function runRelease(ctx, rawArgs) {
|
|
10
|
+
const { flags: releaseFlags } = parseFlags(rawArgs);
|
|
11
|
+
const outDir = resolve(
|
|
12
|
+
ctx.projectRoot,
|
|
13
|
+
typeof releaseFlags.out === "string" ? releaseFlags.out : "out",
|
|
14
|
+
);
|
|
15
|
+
const bundledHtml = join(outDir, "index.html");
|
|
16
|
+
const bakedDir = join(outDir, "baked");
|
|
17
|
+
|
|
18
|
+
console.log(`[release] context: ${ctx.context}`);
|
|
19
|
+
console.log(`[release] artifact: ${outDir}`);
|
|
20
|
+
console.log("");
|
|
21
|
+
|
|
22
|
+
if (!releaseFlags["no-clean"]) {
|
|
23
|
+
if (existsSync(outDir)) {
|
|
24
|
+
await rm(outDir, { recursive: true, force: true });
|
|
25
|
+
console.log(`[release] cleaned ${outDir}`);
|
|
26
|
+
}
|
|
27
|
+
} else {
|
|
28
|
+
console.log("[release] --no-clean: keeping existing out/");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log("[release] step 1/5 typecheck");
|
|
32
|
+
await runNodeBin(ctx, "typescript/bin/tsc", ["--noEmit"]);
|
|
33
|
+
|
|
34
|
+
console.log("[release] step 2/5 vite build");
|
|
35
|
+
await runVite(ctx, ["build", "--outDir", outDir], { defaultConfig: true });
|
|
36
|
+
|
|
37
|
+
console.log("[release] step 3/5 bake");
|
|
38
|
+
if (releaseFlags["keep-bake-dir"]) {
|
|
39
|
+
await runNodeScript(ctx, "scripts/bake.mjs", [
|
|
40
|
+
...rawArgs.filter((a) => a !== "--keep-bake-dir"),
|
|
41
|
+
"--template",
|
|
42
|
+
bundledHtml,
|
|
43
|
+
"--out",
|
|
44
|
+
bakedDir,
|
|
45
|
+
]);
|
|
46
|
+
const promoted = await promoteBakedHtml(bakedDir, outDir);
|
|
47
|
+
if (promoted.html > 0) {
|
|
48
|
+
console.log(`[release] promoted ${promoted.html} baked HTML page(s) into out/`);
|
|
49
|
+
}
|
|
50
|
+
if (promoted.sitemap) {
|
|
51
|
+
console.log(`[release] copied sitemap.xml -> ${join(outDir, "sitemap.xml")}`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
await runNodeScript(ctx, "scripts/bake.mjs", [
|
|
55
|
+
...rawArgs,
|
|
56
|
+
"--template",
|
|
57
|
+
bundledHtml,
|
|
58
|
+
"--out",
|
|
59
|
+
outDir,
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
console.log("[release] step 4/5 precompress assets");
|
|
64
|
+
await precompressOut(outDir);
|
|
65
|
+
|
|
66
|
+
console.log("[release] step 5/5 CDN config");
|
|
67
|
+
await writeIfMissing(join(outDir, "_redirects"), "/* /index.html 200\n", "[release] ");
|
|
68
|
+
await writeIfMissing(
|
|
69
|
+
join(outDir, "_headers"),
|
|
70
|
+
[
|
|
71
|
+
"/assets/*",
|
|
72
|
+
" Cache-Control: public, max-age=31536000, immutable",
|
|
73
|
+
"",
|
|
74
|
+
"/*.html",
|
|
75
|
+
" Cache-Control: no-cache, must-revalidate",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
"[release] ",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
console.log("");
|
|
82
|
+
console.log(`[release] done. Deploy artifact: ${outDir}`);
|
|
83
|
+
console.log("[release] try: mado preview");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function promoteBakedHtml(bakedDir, outDir) {
|
|
87
|
+
if (!existsSync(bakedDir)) return { html: 0, sitemap: false };
|
|
88
|
+
|
|
89
|
+
let html = 0;
|
|
90
|
+
|
|
91
|
+
async function walk(dir, rel = "") {
|
|
92
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
93
|
+
const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
|
|
94
|
+
const source = join(dir, entry.name);
|
|
95
|
+
if (entry.isDirectory()) {
|
|
96
|
+
await walk(source, nextRel);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (!entry.isFile() || !entry.name.endsWith(".html")) continue;
|
|
100
|
+
const target = join(outDir, nextRel);
|
|
101
|
+
await mkdir(dirname(target), { recursive: true });
|
|
102
|
+
await copyFile(source, target);
|
|
103
|
+
html++;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await walk(bakedDir);
|
|
108
|
+
|
|
109
|
+
const bakedSitemap = join(bakedDir, "sitemap.xml");
|
|
110
|
+
const rootSitemap = join(outDir, "sitemap.xml");
|
|
111
|
+
let sitemap = false;
|
|
112
|
+
if (existsSync(bakedSitemap)) {
|
|
113
|
+
await copyFile(bakedSitemap, rootSitemap);
|
|
114
|
+
sitemap = true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { html, sitemap };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function precompressOut(outDir) {
|
|
121
|
+
if (!existsSync(outDir)) return;
|
|
122
|
+
const files = await listCompressibleFiles(outDir);
|
|
123
|
+
let count = 0;
|
|
124
|
+
for (const file of files) {
|
|
125
|
+
const buf = await readFile(file);
|
|
126
|
+
await writeFile(`${file}.gz`, gzipSync(buf, { level: 9 }));
|
|
127
|
+
await writeFile(
|
|
128
|
+
`${file}.br`,
|
|
129
|
+
brotliCompressSync(buf, {
|
|
130
|
+
params: { [zlibConst.BROTLI_PARAM_QUALITY]: 11 },
|
|
131
|
+
}),
|
|
132
|
+
);
|
|
133
|
+
count++;
|
|
134
|
+
}
|
|
135
|
+
console.log(`[release] compressed ${count} file(s)`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function listCompressibleFiles(dir) {
|
|
139
|
+
const out = [];
|
|
140
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
141
|
+
const file = join(dir, entry.name);
|
|
142
|
+
if (entry.isDirectory()) {
|
|
143
|
+
out.push(...await listCompressibleFiles(file));
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!entry.isFile()) continue;
|
|
147
|
+
if (/\.(js|css|html|json|svg)$/.test(entry.name) && !/\.(gz|br)$/.test(entry.name)) {
|
|
148
|
+
out.push(file);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
export async function run(cmd, args, options = {}) {
|
|
7
|
+
const child = spawn(cmd, args, {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
stdio: "inherit",
|
|
10
|
+
env: options.env ?? process.env,
|
|
11
|
+
shell: options.shell ?? false,
|
|
12
|
+
});
|
|
13
|
+
const code = await new Promise((resolveExit) => {
|
|
14
|
+
child.on("exit", (status) => resolveExit(status ?? 1));
|
|
15
|
+
});
|
|
16
|
+
if (code !== 0) process.exit(code);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function runNodeBin(ctx, bin, args) {
|
|
20
|
+
await run(process.execPath, [resolveBin(ctx, bin), ...args], {
|
|
21
|
+
cwd: ctx.projectRoot,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function runNodeScript(ctx, script, args) {
|
|
26
|
+
await run(process.execPath, [join(ctx.packageRoot, script), ...args], {
|
|
27
|
+
cwd: ctx.projectRoot,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runVite(ctx, args, { defaultConfig } = {}) {
|
|
32
|
+
const viteArgs = [...args];
|
|
33
|
+
if (defaultConfig && !hasFlag(viteArgs, "--config") && !hasFlag(viteArgs, "-c")) {
|
|
34
|
+
const configPath = join(ctx.projectRoot, "vite.config.ts");
|
|
35
|
+
if (!existsSync(configPath)) {
|
|
36
|
+
viteArgs.push("--config", join(ctx.packageRoot, "scripts", "vite.default.mjs"));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
await run(
|
|
40
|
+
process.execPath,
|
|
41
|
+
[resolvePackageBin(ctx, "vite", "bin/vite.js"), ...viteArgs],
|
|
42
|
+
{ cwd: ctx.projectRoot },
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolvePackageBin(ctx, pkg, binPath) {
|
|
47
|
+
const projectPath = join(ctx.projectRoot, "node_modules", pkg, binPath);
|
|
48
|
+
if (existsSync(projectPath)) return projectPath;
|
|
49
|
+
const packagePath = join(ctx.packageRoot, "node_modules", pkg, binPath);
|
|
50
|
+
if (existsSync(packagePath)) return packagePath;
|
|
51
|
+
console.error(`[mado] missing ${pkg}. Install it in this project: npm i -D ${pkg}`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function resolveBin(ctx, bin) {
|
|
56
|
+
const projectPath = join(ctx.projectRoot, "node_modules", bin);
|
|
57
|
+
if (existsSync(projectPath)) return projectPath;
|
|
58
|
+
const packagePath = join(ctx.packageRoot, "node_modules", bin);
|
|
59
|
+
if (!existsSync(packagePath)) {
|
|
60
|
+
console.error(`[mado] missing ${bin}. Run npm install first.`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
return packagePath;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function writeIfMissing(path, content, logPrefix = "[mado]") {
|
|
67
|
+
if (existsSync(path)) return;
|
|
68
|
+
await writeFile(path, content);
|
|
69
|
+
console.log(`${logPrefix} wrote ${path}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function listTestFiles(projectRoot) {
|
|
73
|
+
const dir = join(projectRoot, "test");
|
|
74
|
+
const files = [];
|
|
75
|
+
async function walk(current, prefix = "test") {
|
|
76
|
+
for (const entry of await readdir(current, { withFileTypes: true })) {
|
|
77
|
+
const rel = join(prefix, entry.name);
|
|
78
|
+
const full = join(current, entry.name);
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
await walk(full, rel);
|
|
81
|
+
} else if (entry.isFile() && entry.name.endsWith(".test.mjs")) {
|
|
82
|
+
files.push(rel);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
await walk(dir);
|
|
87
|
+
return files.sort();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function readJson(path) {
|
|
91
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function hasFlag(args, long, short) {
|
|
95
|
+
return args.some((arg) => arg === long || (short && arg === short) || arg.startsWith(`${long}=`));
|
|
96
|
+
}
|