@mozole/cli 1.3.2 → 1.4.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/CHANGELOG.md +22 -0
- package/README.md +54 -156
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/adopt.d.ts +2 -1
- package/dist/commands/adopt.d.ts.map +1 -1
- package/dist/commands/adopt.js +460 -81
- package/dist/commands/adopt.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +39 -16
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +22 -11
- package/dist/commands/new.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +18 -12
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/stats.d.ts.map +1 -1
- package/dist/commands/stats.js +6 -1
- package/dist/commands/stats.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +12 -8
- package/dist/commands/verify.js.map +1 -1
- package/dist/config/project.d.ts +3 -3
- package/dist/config/project.d.ts.map +1 -1
- package/dist/config/project.js +11 -12
- package/dist/config/project.js.map +1 -1
- package/dist/governor/fingerprint.d.ts.map +1 -1
- package/dist/governor/fingerprint.js +2 -0
- package/dist/governor/fingerprint.js.map +1 -1
- package/dist/governor/hook.d.ts +1 -0
- package/dist/governor/hook.d.ts.map +1 -1
- package/dist/governor/hook.js +18 -9
- package/dist/governor/hook.js.map +1 -1
- package/dist/platform/process.d.ts.map +1 -1
- package/dist/platform/process.js +4 -0
- package/dist/platform/process.js.map +1 -1
- package/dist/qa/runner.js +1 -1
- package/dist/qa/runner.js.map +1 -1
- package/dist/scaffold/adoption.d.ts +17 -0
- package/dist/scaffold/adoption.d.ts.map +1 -0
- package/dist/scaffold/adoption.js +279 -0
- package/dist/scaffold/adoption.js.map +1 -0
- package/dist/scaffold/backend.d.ts.map +1 -1
- package/dist/scaffold/backend.js +57 -11
- package/dist/scaffold/backend.js.map +1 -1
- package/dist/scaffold/design.d.ts +4 -0
- package/dist/scaffold/design.d.ts.map +1 -0
- package/dist/scaffold/design.js +50 -0
- package/dist/scaffold/design.js.map +1 -0
- package/dist/scaffold/legacy.d.ts +4 -0
- package/dist/scaffold/legacy.d.ts.map +1 -0
- package/dist/scaffold/legacy.js +139 -0
- package/dist/scaffold/legacy.js.map +1 -0
- package/dist/scaffold/routes.d.ts +3 -0
- package/dist/scaffold/routes.d.ts.map +1 -0
- package/dist/scaffold/routes.js +37 -0
- package/dist/scaffold/routes.js.map +1 -0
- package/dist/scaffold/standalone.d.ts +7 -0
- package/dist/scaffold/standalone.d.ts.map +1 -0
- package/dist/scaffold/standalone.js +294 -0
- package/dist/scaffold/standalone.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +82 -26
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scaffold/write.d.ts.map +1 -1
- package/dist/scaffold/write.js +19 -5
- package/dist/scaffold/write.js.map +1 -1
- package/dist/verify/runner.d.ts.map +1 -1
- package/dist/verify/runner.js +2 -4
- package/dist/verify/runner.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/docs/agent-audit.md +24 -0
- package/package.json +4 -2
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/** Exact pre-1.4 templates, used only to recognize and migrate generator-owned text. */
|
|
2
|
+
export const legacyInstructions = {
|
|
3
|
+
"AGENTS.md": "# Project guide\n\n## Commands\n\n- `npm run dev` \u2014 local app\n- `npm run verify` \u2014 lint, typecheck, tests, production build\n- `npm run qa` \u2014 deterministic browser QA (no screenshots)\n- `npm run qa:interactive` \u2014 adds safe interaction probes\n- `npm run qa:visual` \u2014 explicit screenshot review\n- `mozole cce install` \u2014 install persistent CCE and required uv runtime\n- `mozole ui` \u2014 full-screen terminal dashboard\n\n## Architecture\n\nReact Router Framework Mode lives in `app/`; routes are in `app/routes.ts`; static output is `build/client/`. Mozole settings are in `.mozole/`; Antigravity integration is in `.agents/`; deeper decisions belong in `docs/`.\n\n## Mandatory CCE contract\n\nRepository reads are forbidden by default. Allowed retrieval order:\n\n1. CCE `context_search`\n2. additional CCE search, `expand_chunk`, or `related_context`\n3. bounded line-range read only when CCE output is insufficient\n4. full-file read only after explicit escalation\n\nEditing a file is NOT sufficient justification for reading it in full. If CCE is unavailable, stop and report the blocker instead of falling back to repository reads. Prefer localized patches and inspect `git diff`. Keep narration and successful logs concise. Routine browser validation uses screenshot-free `mozole qa`; visual QA requires an explicit user request or confirmed deterministic failure. Stop after requested work, `mozole verify`, and relevant routine QA pass.\n",
|
|
4
|
+
"CLAUDE.md": "@AGENTS.md\n\n## Claude Code CCE contract\n\nRepository reads are forbidden by default. Use `context_search`, then additional CCE retrieval. Read may use a bounded line range only when CCE is insufficient; a full-file Read requires explicit escalation. Editing is not justification. If CCE is unavailable, report the blocker; do not fall back.\n",
|
|
5
|
+
"docs/design-tokens.md": "# Design tokens\n\nComplete this contract before writing UI code. Replace every TODO custom-property value in `app/styles/global.css` with a deliberate project-specific choice.\n\n## Required decisions\n\n- `--color-bg`: primary page background.\n- `--color-fg`: primary text and foreground.\n- `--color-accent`: interactive and emphasis color.\n- `--font-display`: heading/display family and fallbacks.\n- `--font-body`: body/interface family and fallbacks.\n- Record one paragraph explaining visual direction, typography, palette, spacing, and layout before generating markup for each new page.\n\n## Banned default patterns\n\nDo not use these as automatic defaults:\n\n- Warm cream background with terracotta accent palette.\n- Identical border-radius and soft grey box-shadow on every card.\n- Uppercase, tracked-out eyebrow labels.\n- En- or em-dash-joined meta labels.\n- Arrow glyphs appended to button text.\n",
|
|
6
|
+
};
|
|
7
|
+
export function legacyBackendTemplates(name) {
|
|
8
|
+
return {
|
|
9
|
+
"api/index.php": `<?php
|
|
10
|
+
declare(strict_types=1);
|
|
11
|
+
|
|
12
|
+
require_once __DIR__ . '/config.php';
|
|
13
|
+
|
|
14
|
+
header('Content-Type: application/json; charset=utf-8');
|
|
15
|
+
header('X-Content-Type-Options: nosniff');
|
|
16
|
+
header('X-Frame-Options: DENY');
|
|
17
|
+
|
|
18
|
+
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
|
19
|
+
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?? '/';
|
|
20
|
+
|
|
21
|
+
// Normalize URI to match route path (handles both /api/path and direct /path on dev server)
|
|
22
|
+
$basePrefix = '/api';
|
|
23
|
+
if (str_starts_with($uri, $basePrefix)) {
|
|
24
|
+
$route = '/' . ltrim(substr($uri, strlen($basePrefix)), '/');
|
|
25
|
+
} else {
|
|
26
|
+
$route = '/' . ltrim($uri, '/');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
switch ($route) {
|
|
30
|
+
case '/':
|
|
31
|
+
case '/health':
|
|
32
|
+
echo json_encode([
|
|
33
|
+
'status' => 'ok',
|
|
34
|
+
'app' => '${name}',
|
|
35
|
+
'runtime' => 'PHP ' . PHP_VERSION,
|
|
36
|
+
'timestamp' => time(),
|
|
37
|
+
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case '/info':
|
|
41
|
+
echo json_encode([
|
|
42
|
+
'name' => '${name}',
|
|
43
|
+
'backend' => 'PHP native API',
|
|
44
|
+
'endpoints' => [
|
|
45
|
+
'GET /api/health' => 'Service status check',
|
|
46
|
+
'GET /api/info' => 'Backend metadata',
|
|
47
|
+
],
|
|
48
|
+
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
49
|
+
break;
|
|
50
|
+
|
|
51
|
+
default:
|
|
52
|
+
http_response_code(404);
|
|
53
|
+
echo json_encode([
|
|
54
|
+
'error' => 'Endpoint not found',
|
|
55
|
+
'path' => $uri,
|
|
56
|
+
], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
`,
|
|
60
|
+
"api/config.php": `<?php
|
|
61
|
+
declare(strict_types=1);
|
|
62
|
+
|
|
63
|
+
function env(string $key, ?string $default = null): ?string {
|
|
64
|
+
$value = getenv($key);
|
|
65
|
+
return $value !== false ? $value : $default;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Environment configuration
|
|
69
|
+
define('APP_ENV', env('APP_ENV', 'development'));
|
|
70
|
+
define('APP_DEBUG', env('APP_DEBUG', 'true') === 'true');
|
|
71
|
+
|
|
72
|
+
// Optional database connection helper
|
|
73
|
+
function get_db_connection(): ?PDO {
|
|
74
|
+
static $pdo = null;
|
|
75
|
+
if ($pdo !== null) {
|
|
76
|
+
return $pdo;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
$dsn = env('DB_DSN');
|
|
80
|
+
if (!$dsn) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
$user = env('DB_USER', '');
|
|
85
|
+
$pass = env('DB_PASS', '');
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
$pdo = new PDO($dsn, $user, $pass, [
|
|
89
|
+
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
|
90
|
+
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
|
91
|
+
PDO::ATTR_EMULATE_PREPARES => false,
|
|
92
|
+
]);
|
|
93
|
+
return $pdo;
|
|
94
|
+
} catch (PDOException $e) {
|
|
95
|
+
if (APP_DEBUG) {
|
|
96
|
+
throw $e;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`,
|
|
102
|
+
".htaccess": `<IfModule mod_rewrite.c>
|
|
103
|
+
RewriteEngine On
|
|
104
|
+
RewriteBase /
|
|
105
|
+
|
|
106
|
+
# Route API requests to PHP front-controller
|
|
107
|
+
RewriteRule ^api(/.*)?$ api/index.php [QSA,L]
|
|
108
|
+
|
|
109
|
+
# Serve static assets directly if they exist
|
|
110
|
+
RewriteCond %{REQUEST_FILENAME} -f [OR]
|
|
111
|
+
RewriteCond %{REQUEST_FILENAME} -d
|
|
112
|
+
RewriteRule ^ - [L]
|
|
113
|
+
|
|
114
|
+
# Fallback to prerendered / client index for SPA routes
|
|
115
|
+
RewriteRule ^index\\.html$ - [L]
|
|
116
|
+
RewriteRule . /index.html [L]
|
|
117
|
+
</IfModule>
|
|
118
|
+
`,
|
|
119
|
+
"docs/backend-php.md": `# PHP Backend Architecture
|
|
120
|
+
|
|
121
|
+
This project combines a **static-first React + Vite frontend** with a lightweight, native **PHP backend API**.
|
|
122
|
+
|
|
123
|
+
## How It Works
|
|
124
|
+
|
|
125
|
+
### Local Development
|
|
126
|
+
- **Frontend (React + Vite)**: Runs via \`npm run dev\` (typically at \`http://127.0.0.1:5173\`).
|
|
127
|
+
- **Backend (PHP API)**: Runs via \`npm run dev:api\` (\`php -S 127.0.0.1:8000 -t api\`).
|
|
128
|
+
- **Proxy**: Vite's \`server.proxy\` automatically forwards all \`/api/*\` requests from the browser directly to the PHP dev server. Your React code calls \`fetch("/api/health")\` without dealing with CORS or port mismatches.
|
|
129
|
+
|
|
130
|
+
### Production Deployment (Apache / LiteSpeed / Nginx)
|
|
131
|
+
- **Zero Node.js runtime required**: Deploy the \`build/client/\` static files and the \`api/\` directory to any shared hosting or VPS.
|
|
132
|
+
- **Apache / LiteSpeed**: The provided \`.htaccess\` handles routing:
|
|
133
|
+
- All \`/api/*\` calls are routed directly to \`api/index.php\`.
|
|
134
|
+
- All static HTML, CSS, JS, and image assets are served directly by the web server at maximum speed.
|
|
135
|
+
- **Nginx**: Forward \`/api/\` to \`fastcgi_pass\` and serve \`try_files $uri $uri/ /index.html\`.
|
|
136
|
+
`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=legacy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacy.js","sourceRoot":"","sources":["../../src/scaffold/legacy.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,WAAW,EACT,48CAA48C;IAC98C,WAAW,EACT,2VAA2V;IAC7V,uBAAuB,EACrB,w5BAAw5B;CAC35B,CAAC;AAEF,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO;QACL,eAAe,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;wBAyBG,IAAI;;;;;;;;yBAQH,IAAI;;;;;;;;;;;;;;;;;CAiB5B;QAEG,gBAAgB,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyCrB;QAEG,WAAW,EAAE;;;;;;;;;;;;;;;;CAgBhB;QAEG,qBAAqB,EAAE;;;;;;;;;;;;;;;;;CAiB1B;KACE,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/scaffold/routes.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAqCrD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** Discover literal React Router route/prefix calls without evaluating project code. */
|
|
2
|
+
export function detectRoutes(source) {
|
|
3
|
+
const tokens = (source.match(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[a-zA-Z_$][\w$]*|[^\s]/g) ?? []).filter((token) => !token.startsWith("//") && !token.startsWith("/*"));
|
|
4
|
+
const routes = new Set(["/"]);
|
|
5
|
+
function scan(start, end, base) {
|
|
6
|
+
for (let i = start; i < end; i++) {
|
|
7
|
+
const name = tokens[i];
|
|
8
|
+
if (!["route", "prefix", "layout", "index"].includes(name ?? "") ||
|
|
9
|
+
tokens[i + 1] !== "(")
|
|
10
|
+
continue;
|
|
11
|
+
let depth = 1;
|
|
12
|
+
let close = i + 2;
|
|
13
|
+
for (; close < end; close++) {
|
|
14
|
+
if (tokens[close] === "(")
|
|
15
|
+
depth++;
|
|
16
|
+
if (tokens[close] === ")" && --depth === 0)
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
const literal = tokens[i + 2];
|
|
20
|
+
const value = literal && /^["']/.test(literal) ? literal.slice(1, -1) : undefined;
|
|
21
|
+
if (name === "layout")
|
|
22
|
+
scan(i + 2, close, base);
|
|
23
|
+
else if (name === "index")
|
|
24
|
+
routes.add(base || "/");
|
|
25
|
+
else if (value !== undefined && !/[:*?\\]/.test(value)) {
|
|
26
|
+
const joined = `${base}/${value}`.replace(/\/{2,}/g, "/").replace(/\/$/, "") || "/";
|
|
27
|
+
if (name === "route")
|
|
28
|
+
routes.add(joined);
|
|
29
|
+
scan(i + 3, close, joined);
|
|
30
|
+
}
|
|
31
|
+
i = close;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
scan(0, tokens.length, "");
|
|
35
|
+
return [...routes];
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/scaffold/routes.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,MAAM,GAAG,CACb,MAAM,CAAC,KAAK,CACV,yFAAyF,CAC1F,IAAI,EAAE,CACR,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,SAAS,IAAI,CAAC,KAAa,EAAE,GAAW,EAAE,IAAY;QACpD,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACvB,IACE,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC5D,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG;gBAErB,SAAS;YACX,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,IAAI,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC;gBAC5B,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG;oBAAE,KAAK,EAAE,CAAC;gBACnC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,EAAE,KAAK,KAAK,CAAC;oBAAE,MAAM;YACpD,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9B,MAAM,KAAK,GACT,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YACtE,IAAI,IAAI,KAAK,QAAQ;gBAAE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;iBAC3C,IAAI,IAAI,KAAK,OAAO;gBAAE,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC;iBAC9C,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;gBACvD,MAAM,MAAM,GACV,GAAG,IAAI,IAAI,KAAK,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;gBACvE,IAAI,IAAI,KAAK,OAAO;oBAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBACzC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;YAC7B,CAAC;YACD,CAAC,GAAG,KAAK,CAAC;QACZ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Standalone project tooling: no dependency on the generator at runtime. */
|
|
2
|
+
export declare const backendCheckScript = "import { spawnSync } from \"node:child_process\";\nimport { readdir } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nfunction php(args) {\n const result = spawnSync(\"php\", args, {\n encoding: \"utf8\",\n shell: false,\n timeout: 10000,\n });\n if (result.error || result.status !== 0)\n throw new Error(result.error?.message || result.stderr || result.stdout);\n return result.stdout.trim();\n}\nasync function lint(directory) {\n for (const entry of await readdir(directory, { withFileTypes: true })) {\n const file = path.join(directory, entry.name);\n if (entry.isDirectory()) await lint(file);\n else if (entry.isFile() && entry.name.endsWith(\".php\")) php([\"-l\", file]);\n }\n}\ntry {\n const version = Number(php([\"-r\", \"echo PHP_VERSION_ID;\"]));\n if (!Number.isInteger(version) || version < 80000)\n throw new Error(\"PHP 8.0 or newer is required.\");\n await lint(\"api\");\n console.log(\"PHP PASS\");\n} catch (error) {\n console.error(`PHP FAIL\\n${error.message}`);\n process.exitCode = 1;\n}\n";
|
|
3
|
+
export declare const deployScript = "import { cp, lstat, mkdir, rename, rm } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nconst target = path.resolve(\"deploy\");\nconst staging = `${target}.${process.pid}.tmp`;\nconst backup = `${target}.${process.pid}.backup`;\nlet backedUp = false;\ntry {\n await mkdir(staging);\n await cp(\"build/client\", staging, { recursive: true, errorOnExist: true });\n await cp(\"api\", path.join(staging, \"api\"), { recursive: true });\n await cp(\".htaccess\", path.join(staging, \".htaccess\"));\n try {\n await lstat(target);\n await rename(target, backup);\n backedUp = true;\n } catch (error) {\n if (error.code !== \"ENOENT\") throw error;\n }\n try {\n await rename(staging, target);\n } catch (error) {\n if (backedUp) await rename(backup, target);\n throw error;\n }\n if (backedUp) await rm(backup, { recursive: true });\n console.log(\"DEPLOY READY: deploy/\");\n} finally {\n await rm(staging, { recursive: true, force: true });\n}\n";
|
|
4
|
+
export declare const qaScript = "import { spawn, spawnSync } from \"node:child_process\";\nimport { mkdir, readFile } from \"node:fs/promises\";\nimport net from \"node:net\";\nimport { chromium } from \"playwright\";\n\nconst flags = process.argv.slice(2);\nif (\n flags.some((flag) => ![\"--visual\", \"--interactive\", \"--force\"].includes(flag))\n)\n throw new Error(\"Unknown QA option\");\nconst config = JSON.parse(await readFile(\".mozole/project.json\", \"utf8\"));\nconst socket = net.createServer();\nawait new Promise((resolve, reject) => {\n socket.once(\"error\", reject);\n socket.listen(0, \"127.0.0.1\", resolve);\n});\nconst port = socket.address().port;\nawait new Promise((resolve) => socket.close(resolve));\nconst configured = config.qa.server;\nconst npmCli = process.env.npm_execpath;\nif (configured.command === \"npm\" && !npmCli) {\n throw new Error(\n \"Run QA through npm run qa so npm can be resolved without a shell.\",\n );\n}\nconst command =\n configured.command === \"npm\" ? process.execPath : configured.command;\nconst args = [\n ...(configured.command === \"npm\" ? [npmCli] : []),\n ...configured.args,\n \"--port\",\n String(port),\n \"--strictPort\",\n];\nconst child = spawn(command, args, {\n shell: false,\n detached: process.platform !== \"win32\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n});\nlet serverLog = \"\";\nfor (const stream of [child.stdout, child.stderr])\n stream.on(\"data\", (data) => {\n serverLog = (serverLog + data).slice(-4000);\n });\nlet serverError;\nchild.on(\"error\", (error) => {\n serverError = error;\n});\nlet browser;\nlet stopping = false;\nasync function cleanup() {\n if (stopping) return;\n stopping = true;\n await browser?.close().catch(() => {});\n if (child.pid) {\n if (process.platform === \"win32\")\n spawnSync(\"taskkill\", [\"/pid\", String(child.pid), \"/T\", \"/F\"], {\n shell: false,\n stdio: \"ignore\",\n });\n else {\n try {\n process.kill(-child.pid, \"SIGTERM\");\n } catch {}\n }\n }\n}\nconst interrupted = () => {\n void cleanup().finally(() => process.exit(130));\n};\nprocess.once(\"SIGINT\", interrupted);\nprocess.once(\"SIGTERM\", interrupted);\nconst deadline = setTimeout(() => {\n console.error(\"QA timed out\");\n void cleanup().finally(() => process.exit(1));\n}, 120000);\nconst base = `http://127.0.0.1:${port}`;\ntry {\n let ready = false;\n for (let attempt = 0; attempt < 300; attempt++) {\n if (serverError || child.exitCode !== null)\n throw new Error(serverError?.message || serverLog);\n try {\n ready = (\n await fetch(base + config.qa.server.readinessPath, {\n signal: AbortSignal.timeout(1000),\n })\n ).ok;\n } catch {}\n if (ready) break;\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n if (!ready)\n throw new Error(`Development server did not become ready.\\n${serverLog}`);\n browser = await chromium.launch();\n const failures = [];\n for (const viewport of config.qa.viewports) {\n const page = await browser.newPage({ viewport });\n page.on(\"pageerror\", (error) => failures.push(error.message));\n page.on(\"console\", (message) => {\n if (message.type() === \"error\") failures.push(message.text());\n });\n page.on(\"requestfailed\", (request) => {\n if (request.failure()?.errorText !== \"net::ERR_ABORTED\")\n failures.push(request.url());\n });\n page.on(\"response\", (response) => {\n if (response.status() >= 400)\n failures.push(`${response.status()} ${response.url()}`);\n });\n for (const route of config.routes) {\n const response = await page.goto(base + route, {\n waitUntil: \"networkidle\",\n });\n if (!response?.ok()) failures.push(`${route}: HTTP failure`);\n const issues = await page.evaluate(() => {\n const found = [];\n if (!document.documentElement.lang) found.push(\"missing lang\");\n if (!document.title.trim()) found.push(\"missing title\");\n if (!document.querySelector(\"h1\")) found.push(\"missing h1\");\n if (document.documentElement.scrollWidth > innerWidth + 1)\n found.push(\"horizontal overflow\");\n for (const element of document.querySelectorAll(\n \"a, button, input:not([type=hidden]), select, textarea\",\n )) {\n if (!element.getClientRects().length) continue;\n const labelled = (element.getAttribute(\"aria-labelledby\") || \"\")\n .split(/\\s+/)\n .some((id) => document.getElementById(id)?.textContent?.trim());\n if (\n !element.textContent?.trim() &&\n !element.getAttribute(\"aria-label\") &&\n !labelled &&\n !element.labels?.length &&\n !element.getAttribute(\"title\") &&\n !element.getAttribute(\"alt\") &&\n !element.querySelector(\"img[alt]\")\n )\n found.push(\"unnamed control\");\n }\n return found;\n });\n failures.push(...issues.map((issue) => `${route}: ${issue}`));\n const links = await page\n .locator(\"a[href]\")\n .evaluateAll((anchors) => anchors.map((anchor) => anchor.href));\n for (const href of new Set(links)) {\n if (\n new URL(href).origin === base &&\n !(await page.request.get(href)).ok()\n )\n failures.push(`broken link: ${href}`);\n }\n if (flags.includes(\"--interactive\")) {\n await page.keyboard.press(\"Tab\");\n if (\n !(await page.evaluate(() => document.activeElement !== document.body))\n )\n failures.push(`${route}: keyboard focus missing`);\n }\n if (flags.includes(\"--visual\")) {\n await mkdir(\".mozole/qa\", { recursive: true });\n const name = route.replace(/[^a-z0-9_-]/gi, \"-\") || \"index\";\n await page.screenshot({\n path:\n \".mozole/qa/\" +\n viewport.width +\n \"x\" +\n viewport.height +\n \"-\" +\n name +\n \".png\",\n fullPage: true,\n });\n }\n }\n await page.close();\n }\n if (failures.length) throw new Error([...new Set(failures)].join(\"\\n\"));\n console.log(\n \"QA PASS: \" +\n config.routes.length +\n \" routes, \" +\n config.qa.viewports.length +\n \" viewports\",\n );\n} catch (error) {\n console.error(`QA FAIL\\n${error.message}`);\n process.exitCode = 1;\n} finally {\n clearTimeout(deadline);\n process.removeListener(\"SIGINT\", interrupted);\n process.removeListener(\"SIGTERM\", interrupted);\n await cleanup();\n}\n";
|
|
5
|
+
export declare function basicAgentGuide(backend: boolean): string;
|
|
6
|
+
export declare const extraVerifierScript = "import { spawnSync } from \"node:child_process\";\nimport { readFile } from \"node:fs/promises\";\n\nconst config = JSON.parse(await readFile(\".mozole/project.json\", \"utf8\"));\nfor (const check of config.verifiers ?? []) {\n if (\n typeof check.command !== \"string\" ||\n !Array.isArray(check.args) ||\n !check.args.every((arg) => typeof arg === \"string\")\n )\n throw new Error(\"Invalid extra verifier\");\n const npm = check.command === \"npm\";\n if (npm && !process.env.npm_execpath)\n throw new Error(\"Run verification through npm run verify\");\n const result = spawnSync(\n npm ? process.execPath : check.command,\n npm ? [process.env.npm_execpath, ...check.args] : check.args,\n { shell: false, stdio: \"inherit\", timeout: 600000 },\n );\n if (result.error || result.status !== 0) {\n console.error(\n \"Extra verifier failed:\",\n check.name,\n result.error?.message ?? result.status,\n );\n process.exit(result.status || 1);\n }\n}\n";
|
|
7
|
+
//# sourceMappingURL=standalone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../src/scaffold/standalone.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,eAAO,MAAM,kBAAkB,ijCA+B9B,CAAC;AAEF,eAAO,MAAM,YAAY,q+BA8BxB,CAAC;AAEF,eAAO,MAAM,QAAQ,qlNAmMpB,CAAC;AAEF,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAExD;AAED,eAAO,MAAM,mBAAmB,q/BA4B/B,CAAC"}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { designGuardrails } from "./design.js";
|
|
2
|
+
/** Standalone project tooling: no dependency on the generator at runtime. */
|
|
3
|
+
export const backendCheckScript = `import { spawnSync } from "node:child_process";
|
|
4
|
+
import { readdir } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
function php(args) {
|
|
8
|
+
const result = spawnSync("php", args, {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
shell: false,
|
|
11
|
+
timeout: 10000,
|
|
12
|
+
});
|
|
13
|
+
if (result.error || result.status !== 0)
|
|
14
|
+
throw new Error(result.error?.message || result.stderr || result.stdout);
|
|
15
|
+
return result.stdout.trim();
|
|
16
|
+
}
|
|
17
|
+
async function lint(directory) {
|
|
18
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
19
|
+
const file = path.join(directory, entry.name);
|
|
20
|
+
if (entry.isDirectory()) await lint(file);
|
|
21
|
+
else if (entry.isFile() && entry.name.endsWith(".php")) php(["-l", file]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const version = Number(php(["-r", "echo PHP_VERSION_ID;"]));
|
|
26
|
+
if (!Number.isInteger(version) || version < 80000)
|
|
27
|
+
throw new Error("PHP 8.0 or newer is required.");
|
|
28
|
+
await lint("api");
|
|
29
|
+
console.log("PHP PASS");
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(\`PHP FAIL\\n\${error.message}\`);
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
}
|
|
34
|
+
`;
|
|
35
|
+
export const deployScript = `import { cp, lstat, mkdir, rename, rm } from "node:fs/promises";
|
|
36
|
+
import path from "node:path";
|
|
37
|
+
|
|
38
|
+
const target = path.resolve("deploy");
|
|
39
|
+
const staging = \`\${target}.\${process.pid}.tmp\`;
|
|
40
|
+
const backup = \`\${target}.\${process.pid}.backup\`;
|
|
41
|
+
let backedUp = false;
|
|
42
|
+
try {
|
|
43
|
+
await mkdir(staging);
|
|
44
|
+
await cp("build/client", staging, { recursive: true, errorOnExist: true });
|
|
45
|
+
await cp("api", path.join(staging, "api"), { recursive: true });
|
|
46
|
+
await cp(".htaccess", path.join(staging, ".htaccess"));
|
|
47
|
+
try {
|
|
48
|
+
await lstat(target);
|
|
49
|
+
await rename(target, backup);
|
|
50
|
+
backedUp = true;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code !== "ENOENT") throw error;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
await rename(staging, target);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
if (backedUp) await rename(backup, target);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
if (backedUp) await rm(backup, { recursive: true });
|
|
61
|
+
console.log("DEPLOY READY: deploy/");
|
|
62
|
+
} finally {
|
|
63
|
+
await rm(staging, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
`;
|
|
66
|
+
export const qaScript = `import { spawn, spawnSync } from "node:child_process";
|
|
67
|
+
import { mkdir, readFile } from "node:fs/promises";
|
|
68
|
+
import net from "node:net";
|
|
69
|
+
import { chromium } from "playwright";
|
|
70
|
+
|
|
71
|
+
const flags = process.argv.slice(2);
|
|
72
|
+
if (
|
|
73
|
+
flags.some((flag) => !["--visual", "--interactive", "--force"].includes(flag))
|
|
74
|
+
)
|
|
75
|
+
throw new Error("Unknown QA option");
|
|
76
|
+
const config = JSON.parse(await readFile(".mozole/project.json", "utf8"));
|
|
77
|
+
const socket = net.createServer();
|
|
78
|
+
await new Promise((resolve, reject) => {
|
|
79
|
+
socket.once("error", reject);
|
|
80
|
+
socket.listen(0, "127.0.0.1", resolve);
|
|
81
|
+
});
|
|
82
|
+
const port = socket.address().port;
|
|
83
|
+
await new Promise((resolve) => socket.close(resolve));
|
|
84
|
+
const configured = config.qa.server;
|
|
85
|
+
const npmCli = process.env.npm_execpath;
|
|
86
|
+
if (configured.command === "npm" && !npmCli) {
|
|
87
|
+
throw new Error(
|
|
88
|
+
"Run QA through npm run qa so npm can be resolved without a shell.",
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
const command =
|
|
92
|
+
configured.command === "npm" ? process.execPath : configured.command;
|
|
93
|
+
const args = [
|
|
94
|
+
...(configured.command === "npm" ? [npmCli] : []),
|
|
95
|
+
...configured.args,
|
|
96
|
+
"--port",
|
|
97
|
+
String(port),
|
|
98
|
+
"--strictPort",
|
|
99
|
+
];
|
|
100
|
+
const child = spawn(command, args, {
|
|
101
|
+
shell: false,
|
|
102
|
+
detached: process.platform !== "win32",
|
|
103
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
104
|
+
});
|
|
105
|
+
let serverLog = "";
|
|
106
|
+
for (const stream of [child.stdout, child.stderr])
|
|
107
|
+
stream.on("data", (data) => {
|
|
108
|
+
serverLog = (serverLog + data).slice(-4000);
|
|
109
|
+
});
|
|
110
|
+
let serverError;
|
|
111
|
+
child.on("error", (error) => {
|
|
112
|
+
serverError = error;
|
|
113
|
+
});
|
|
114
|
+
let browser;
|
|
115
|
+
let stopping = false;
|
|
116
|
+
async function cleanup() {
|
|
117
|
+
if (stopping) return;
|
|
118
|
+
stopping = true;
|
|
119
|
+
await browser?.close().catch(() => {});
|
|
120
|
+
if (child.pid) {
|
|
121
|
+
if (process.platform === "win32")
|
|
122
|
+
spawnSync("taskkill", ["/pid", String(child.pid), "/T", "/F"], {
|
|
123
|
+
shell: false,
|
|
124
|
+
stdio: "ignore",
|
|
125
|
+
});
|
|
126
|
+
else {
|
|
127
|
+
try {
|
|
128
|
+
process.kill(-child.pid, "SIGTERM");
|
|
129
|
+
} catch {}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const interrupted = () => {
|
|
134
|
+
void cleanup().finally(() => process.exit(130));
|
|
135
|
+
};
|
|
136
|
+
process.once("SIGINT", interrupted);
|
|
137
|
+
process.once("SIGTERM", interrupted);
|
|
138
|
+
const deadline = setTimeout(() => {
|
|
139
|
+
console.error("QA timed out");
|
|
140
|
+
void cleanup().finally(() => process.exit(1));
|
|
141
|
+
}, 120000);
|
|
142
|
+
const base = \`http://127.0.0.1:\${port}\`;
|
|
143
|
+
try {
|
|
144
|
+
let ready = false;
|
|
145
|
+
for (let attempt = 0; attempt < 300; attempt++) {
|
|
146
|
+
if (serverError || child.exitCode !== null)
|
|
147
|
+
throw new Error(serverError?.message || serverLog);
|
|
148
|
+
try {
|
|
149
|
+
ready = (
|
|
150
|
+
await fetch(base + config.qa.server.readinessPath, {
|
|
151
|
+
signal: AbortSignal.timeout(1000),
|
|
152
|
+
})
|
|
153
|
+
).ok;
|
|
154
|
+
} catch {}
|
|
155
|
+
if (ready) break;
|
|
156
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
157
|
+
}
|
|
158
|
+
if (!ready)
|
|
159
|
+
throw new Error(\`Development server did not become ready.\\n\${serverLog}\`);
|
|
160
|
+
browser = await chromium.launch();
|
|
161
|
+
const failures = [];
|
|
162
|
+
for (const viewport of config.qa.viewports) {
|
|
163
|
+
const page = await browser.newPage({ viewport });
|
|
164
|
+
page.on("pageerror", (error) => failures.push(error.message));
|
|
165
|
+
page.on("console", (message) => {
|
|
166
|
+
if (message.type() === "error") failures.push(message.text());
|
|
167
|
+
});
|
|
168
|
+
page.on("requestfailed", (request) => {
|
|
169
|
+
if (request.failure()?.errorText !== "net::ERR_ABORTED")
|
|
170
|
+
failures.push(request.url());
|
|
171
|
+
});
|
|
172
|
+
page.on("response", (response) => {
|
|
173
|
+
if (response.status() >= 400)
|
|
174
|
+
failures.push(\`\${response.status()} \${response.url()}\`);
|
|
175
|
+
});
|
|
176
|
+
for (const route of config.routes) {
|
|
177
|
+
const response = await page.goto(base + route, {
|
|
178
|
+
waitUntil: "networkidle",
|
|
179
|
+
});
|
|
180
|
+
if (!response?.ok()) failures.push(\`\${route}: HTTP failure\`);
|
|
181
|
+
const issues = await page.evaluate(() => {
|
|
182
|
+
const found = [];
|
|
183
|
+
if (!document.documentElement.lang) found.push("missing lang");
|
|
184
|
+
if (!document.title.trim()) found.push("missing title");
|
|
185
|
+
if (!document.querySelector("h1")) found.push("missing h1");
|
|
186
|
+
if (document.documentElement.scrollWidth > innerWidth + 1)
|
|
187
|
+
found.push("horizontal overflow");
|
|
188
|
+
for (const element of document.querySelectorAll(
|
|
189
|
+
"a, button, input:not([type=hidden]), select, textarea",
|
|
190
|
+
)) {
|
|
191
|
+
if (!element.getClientRects().length) continue;
|
|
192
|
+
const labelled = (element.getAttribute("aria-labelledby") || "")
|
|
193
|
+
.split(/\\s+/)
|
|
194
|
+
.some((id) => document.getElementById(id)?.textContent?.trim());
|
|
195
|
+
if (
|
|
196
|
+
!element.textContent?.trim() &&
|
|
197
|
+
!element.getAttribute("aria-label") &&
|
|
198
|
+
!labelled &&
|
|
199
|
+
!element.labels?.length &&
|
|
200
|
+
!element.getAttribute("title") &&
|
|
201
|
+
!element.getAttribute("alt") &&
|
|
202
|
+
!element.querySelector("img[alt]")
|
|
203
|
+
)
|
|
204
|
+
found.push("unnamed control");
|
|
205
|
+
}
|
|
206
|
+
return found;
|
|
207
|
+
});
|
|
208
|
+
failures.push(...issues.map((issue) => \`\${route}: \${issue}\`));
|
|
209
|
+
const links = await page
|
|
210
|
+
.locator("a[href]")
|
|
211
|
+
.evaluateAll((anchors) => anchors.map((anchor) => anchor.href));
|
|
212
|
+
for (const href of new Set(links)) {
|
|
213
|
+
if (
|
|
214
|
+
new URL(href).origin === base &&
|
|
215
|
+
!(await page.request.get(href)).ok()
|
|
216
|
+
)
|
|
217
|
+
failures.push(\`broken link: \${href}\`);
|
|
218
|
+
}
|
|
219
|
+
if (flags.includes("--interactive")) {
|
|
220
|
+
await page.keyboard.press("Tab");
|
|
221
|
+
if (
|
|
222
|
+
!(await page.evaluate(() => document.activeElement !== document.body))
|
|
223
|
+
)
|
|
224
|
+
failures.push(\`\${route}: keyboard focus missing\`);
|
|
225
|
+
}
|
|
226
|
+
if (flags.includes("--visual")) {
|
|
227
|
+
await mkdir(".mozole/qa", { recursive: true });
|
|
228
|
+
const name = route.replace(/[^a-z0-9_-]/gi, "-") || "index";
|
|
229
|
+
await page.screenshot({
|
|
230
|
+
path:
|
|
231
|
+
".mozole/qa/" +
|
|
232
|
+
viewport.width +
|
|
233
|
+
"x" +
|
|
234
|
+
viewport.height +
|
|
235
|
+
"-" +
|
|
236
|
+
name +
|
|
237
|
+
".png",
|
|
238
|
+
fullPage: true,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
await page.close();
|
|
243
|
+
}
|
|
244
|
+
if (failures.length) throw new Error([...new Set(failures)].join("\\n"));
|
|
245
|
+
console.log(
|
|
246
|
+
"QA PASS: " +
|
|
247
|
+
config.routes.length +
|
|
248
|
+
" routes, " +
|
|
249
|
+
config.qa.viewports.length +
|
|
250
|
+
" viewports",
|
|
251
|
+
);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error(\`QA FAIL\\n\${error.message}\`);
|
|
254
|
+
process.exitCode = 1;
|
|
255
|
+
} finally {
|
|
256
|
+
clearTimeout(deadline);
|
|
257
|
+
process.removeListener("SIGINT", interrupted);
|
|
258
|
+
process.removeListener("SIGTERM", interrupted);
|
|
259
|
+
await cleanup();
|
|
260
|
+
}
|
|
261
|
+
`;
|
|
262
|
+
export function basicAgentGuide(backend) {
|
|
263
|
+
return `# Project guide\n\n## Commands\n\n- \`npm run dev\` — frontend development server\n- \`npm run verify\` — lint, typecheck, tests, production build\n- \`npm run qa\` — browser checks without screenshots\n- \`npm run qa:interactive\` — keyboard checks\n- \`npm run qa:visual\` — explicitly requested screenshots\n${backend ? "- `npm run dev:api` — PHP API in a separate terminal (PHP 8.0+)\n- `npm run check:php` — PHP version and syntax checks\n- `npm run build:deploy` — assemble deploy/ for Apache or LiteSpeed\n" : ""}\n## Architecture\n\nReact Router and Vite source is in \`app/\`; routes are declared in \`app/routes.ts\`. Production HTML and assets are in \`build/client/\`.${backend ? " PHP endpoints are in `api/`; the frontend uses relative `/api/` URLs through Vite's proxy. See `docs/backend-php.md`." : ""}\n\n## Working rules\n\nPreserve user changes. Inspect relevant code before editing. Run verification and relevant browser checks before handoff. Routine QA never captures screenshots; use visual QA only when requested. Report failed checks and remaining limitations.\n\n${designGuardrails}`;
|
|
264
|
+
}
|
|
265
|
+
export const extraVerifierScript = `import { spawnSync } from "node:child_process";
|
|
266
|
+
import { readFile } from "node:fs/promises";
|
|
267
|
+
|
|
268
|
+
const config = JSON.parse(await readFile(".mozole/project.json", "utf8"));
|
|
269
|
+
for (const check of config.verifiers ?? []) {
|
|
270
|
+
if (
|
|
271
|
+
typeof check.command !== "string" ||
|
|
272
|
+
!Array.isArray(check.args) ||
|
|
273
|
+
!check.args.every((arg) => typeof arg === "string")
|
|
274
|
+
)
|
|
275
|
+
throw new Error("Invalid extra verifier");
|
|
276
|
+
const npm = check.command === "npm";
|
|
277
|
+
if (npm && !process.env.npm_execpath)
|
|
278
|
+
throw new Error("Run verification through npm run verify");
|
|
279
|
+
const result = spawnSync(
|
|
280
|
+
npm ? process.execPath : check.command,
|
|
281
|
+
npm ? [process.env.npm_execpath, ...check.args] : check.args,
|
|
282
|
+
{ shell: false, stdio: "inherit", timeout: 600000 },
|
|
283
|
+
);
|
|
284
|
+
if (result.error || result.status !== 0) {
|
|
285
|
+
console.error(
|
|
286
|
+
"Extra verifier failed:",
|
|
287
|
+
check.name,
|
|
288
|
+
result.error?.message ?? result.status,
|
|
289
|
+
);
|
|
290
|
+
process.exit(result.status || 1);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
`;
|
|
294
|
+
//# sourceMappingURL=standalone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../src/scaffold/standalone.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BjC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B3B,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmMvB,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC9C,OAAO,0TAA0T,OAAO,CAAC,CAAC,CAAC,+LAA+L,CAAC,CAAC,CAAC,EAAE,mKAAmK,OAAO,CAAC,CAAC,CAAC,wHAAwH,CAAC,CAAC,CAAC,EAAE,kRAAkR,gBAAgB,EAAE,CAAC;AAChmC,CAAC;AAED,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4BlC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/scaffold/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../src/scaffold/templates.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAczD,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC/B;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,sBAA2B,GACnC,YAAY,CAiDd;AAID,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,sBAA2B,GACnC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0RxB"}
|