@camox/cli 0.44.0 → 0.45.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/README.md +129 -46
- package/dist/api-DNIQC1ea.mjs +104 -0
- package/dist/api-DeE66CPa.mjs +2 -0
- package/dist/{auth-core-4Jdf5q-u.mjs → auth-core-xLeorUUV.mjs} +35 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +633 -764
- package/dist/lib/auth-core.d.mts +12 -8
- package/dist/lib/auth-core.mjs +1 -2
- package/dist/local-dns-DQIT1ucr.mjs +22 -0
- package/package.json +23 -19
- package/dist/api-COfB7AKI.mjs +0 -82
- package/dist/api-LuNKejJE.mjs +0 -3
- package/templates/default/.env.example +0 -2
- package/templates/default/components.json +0 -25
- package/templates/default/package.json +0 -46
- package/templates/default/scripts/README.md +0 -35
- package/templates/default/scripts/production-smoke.mjs +0 -124
- package/templates/default/src/blocks/faq.tsx +0 -63
- package/templates/default/src/blocks/footer.tsx +0 -61
- package/templates/default/src/blocks/hero.tsx +0 -92
- package/templates/default/src/blocks/navbar.tsx +0 -110
- package/templates/default/src/blocks/youtube-video.tsx +0 -158
- package/templates/default/src/components/ui/accordion.tsx +0 -70
- package/templates/default/src/components/ui/button.tsx +0 -58
- package/templates/default/src/document.ts +0 -7
- package/templates/default/src/layouts/default.tsx +0 -88
- package/templates/default/src/lib/utils.ts +0 -6
- package/templates/default/src/styles.css +0 -162
- package/templates/default/tsconfig.json +0 -25
- package/templates/default/vite.config.ts +0 -25
package/dist/lib/auth-core.d.mts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
|
|
2
2
|
//#region src/lib/auth-core.d.ts
|
|
3
|
-
interface AuthToken {
|
|
3
|
+
export interface AuthToken {
|
|
4
4
|
token: string;
|
|
5
5
|
name: string;
|
|
6
6
|
email: string;
|
|
7
7
|
}
|
|
8
|
-
declare function normalizeUrl(url: string): string;
|
|
8
|
+
export declare function normalizeUrl(url: string): string;
|
|
9
|
+
export declare function isLoopbackUrl(url: URL): boolean;
|
|
10
|
+
export declare function createPreviewSignInUrl(destination: string, target: {
|
|
11
|
+
projectUuid: string;
|
|
12
|
+
apiUrl: string;
|
|
13
|
+
}, authToken: string, signal?: AbortSignal): Promise<string>;
|
|
9
14
|
/** Look up stored credentials for a specific Camox authentication backend. */
|
|
10
|
-
declare function readAuthTokenForUrl(authenticationUrl: string, cwd?: string): AuthToken | null;
|
|
11
|
-
declare function writeAuthTokenForUrl(authenticationUrl: string, token: AuthToken, cwd?: string): void;
|
|
12
|
-
declare function removeAuthTokenForUrl(authenticationUrl: string, cwd?: string): void;
|
|
13
|
-
declare function verifyOneTimeToken(apiUrl: string, token: string): Promise<AuthToken>;
|
|
14
|
-
//#endregion
|
|
15
|
-
export { AuthToken, normalizeUrl, readAuthTokenForUrl, removeAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
|
|
15
|
+
export declare function readAuthTokenForUrl(authenticationUrl: string, cwd?: string): AuthToken | null;
|
|
16
|
+
export declare function writeAuthTokenForUrl(authenticationUrl: string, token: AuthToken, cwd?: string): void;
|
|
17
|
+
export declare function removeAuthTokenForUrl(authenticationUrl: string, cwd?: string): void;
|
|
18
|
+
export declare function verifyOneTimeToken(apiUrl: string, token: string): Promise<AuthToken>;
|
|
19
|
+
//#endregion
|
package/dist/lib/auth-core.mjs
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
export { normalizeUrl, readAuthTokenForUrl, removeAuthTokenForUrl, verifyOneTimeToken, writeAuthTokenForUrl };
|
|
2
|
+
export { t as createPreviewSignInUrl, n as isLoopbackUrl, r as normalizeUrl, i as readAuthTokenForUrl, a as removeAuthTokenForUrl, o as verifyOneTimeToken, s as writeAuthTokenForUrl } from "../auth-core-xLeorUUV.mjs";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
3
|
+
import dns from "node:dns";
|
|
4
|
+
//#region src/lib/local-dns.ts
|
|
5
|
+
const selectedHosts = /* @__PURE__ */ new Set();
|
|
6
|
+
let installed = false;
|
|
7
|
+
/** Resolve explicitly selected local backends without depending on OS wildcard DNS. */
|
|
8
|
+
function installLocalhostDns(hostname) {
|
|
9
|
+
if (!hostname.endsWith(".localhost")) return;
|
|
10
|
+
selectedHosts.add(hostname);
|
|
11
|
+
if (installed) return;
|
|
12
|
+
installed = true;
|
|
13
|
+
const handler = { apply(target, receiver, args) {
|
|
14
|
+
if (selectedHosts.has(args[0])) args[0] = "127.0.0.1";
|
|
15
|
+
return Reflect.apply(target, receiver, args);
|
|
16
|
+
} };
|
|
17
|
+
dns.lookup = new Proxy(dns.lookup, handler);
|
|
18
|
+
dns.promises.lookup = new Proxy(dns.promises.lookup, handler);
|
|
19
|
+
syncBuiltinESMExports();
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { installLocalhostDns as t };
|
package/package.json
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camox/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"camox": "./dist/index.mjs"
|
|
7
7
|
},
|
|
8
8
|
"files": [
|
|
9
|
-
"dist"
|
|
10
|
-
"templates"
|
|
9
|
+
"dist"
|
|
11
10
|
],
|
|
12
11
|
"type": "module",
|
|
13
12
|
"main": "./dist/index.mjs",
|
|
@@ -19,30 +18,35 @@
|
|
|
19
18
|
}
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
|
-
"@clack/prompts": "^
|
|
23
|
-
"@optique/core": "^1.
|
|
24
|
-
"@optique/run": "^1.
|
|
25
|
-
"@orpc/client": "^1.
|
|
26
|
-
"@orpc/server": "^1.
|
|
27
|
-
"
|
|
28
|
-
"zod": "^4.4.3"
|
|
21
|
+
"@clack/prompts": "^1.8.1",
|
|
22
|
+
"@optique/core": "^1.3.6",
|
|
23
|
+
"@optique/run": "^1.3.6",
|
|
24
|
+
"@orpc/client": "^1.15.7",
|
|
25
|
+
"@orpc/server": "^1.15.7",
|
|
26
|
+
"zod": "^4.6.7"
|
|
29
27
|
},
|
|
30
28
|
"devDependencies": {
|
|
31
29
|
"@types/node": "^24.12.4",
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"@camox/api-contract": "0.44.0"
|
|
30
|
+
"vite-plus": "1.3.0",
|
|
31
|
+
"@camox/api-contract": "0.45.0"
|
|
35
32
|
},
|
|
36
33
|
"nx": {
|
|
37
34
|
"tags": [
|
|
38
35
|
"type:pkg"
|
|
39
|
-
]
|
|
36
|
+
],
|
|
37
|
+
"targets": {
|
|
38
|
+
"test": {
|
|
39
|
+
"dependsOn": [
|
|
40
|
+
"build"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
40
44
|
},
|
|
41
45
|
"scripts": {
|
|
42
|
-
"build": "
|
|
43
|
-
"dev": "
|
|
44
|
-
"
|
|
45
|
-
"test": "
|
|
46
|
-
"
|
|
46
|
+
"build": "vp pack",
|
|
47
|
+
"dev": "vp pack --watch",
|
|
48
|
+
"setup:template-artifacts": "bash scripts/setup-template-artifacts.sh",
|
|
49
|
+
"test": "node --test test/*.test.mjs",
|
|
50
|
+
"test:network": "node --test test/network/*.test.mjs"
|
|
47
51
|
}
|
|
48
52
|
}
|
package/dist/api-COfB7AKI.mjs
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { createORPCClient } from "@orpc/client";
|
|
3
|
-
import { RPCLink } from "@orpc/client/fetch";
|
|
4
|
-
//#region src/lib/api.ts
|
|
5
|
-
const CAMOX_API_URL = process.env.CAMOX_API_URL || "https://api.camox.dev";
|
|
6
|
-
function authHeaders(token) {
|
|
7
|
-
return {
|
|
8
|
-
Authorization: `Bearer ${token}`,
|
|
9
|
-
"Content-Type": "application/json"
|
|
10
|
-
};
|
|
11
|
-
}
|
|
12
|
-
function createRpcClient(token, apiUrl = CAMOX_API_URL, environmentName, disableTelemetry) {
|
|
13
|
-
const headers = {
|
|
14
|
-
Authorization: `Bearer ${token}`,
|
|
15
|
-
"x-camox-client": "cli"
|
|
16
|
-
};
|
|
17
|
-
if (environmentName) headers["x-environment-name"] = environmentName;
|
|
18
|
-
if (disableTelemetry) headers["x-camox-telemetry-disabled"] = "1";
|
|
19
|
-
return createORPCClient(new RPCLink({
|
|
20
|
-
url: `${apiUrl}/rpc`,
|
|
21
|
-
headers
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
async function verifySession(token) {
|
|
25
|
-
return (await fetch(`${CAMOX_API_URL}/api/auth/get-session`, {
|
|
26
|
-
method: "GET",
|
|
27
|
-
headers: authHeaders(token)
|
|
28
|
-
})).ok;
|
|
29
|
-
}
|
|
30
|
-
async function listOrganizations(token) {
|
|
31
|
-
const res = await fetch(`${CAMOX_API_URL}/api/auth/organization/list`, {
|
|
32
|
-
method: "GET",
|
|
33
|
-
headers: authHeaders(token)
|
|
34
|
-
});
|
|
35
|
-
if (!res.ok) throw new Error(`Failed to list organizations: ${res.status}`);
|
|
36
|
-
return res.json();
|
|
37
|
-
}
|
|
38
|
-
async function createOrganization(token, name, slug) {
|
|
39
|
-
const res = await fetch(`${CAMOX_API_URL}/api/auth/organization/create`, {
|
|
40
|
-
method: "POST",
|
|
41
|
-
headers: authHeaders(token),
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
name,
|
|
44
|
-
slug
|
|
45
|
-
})
|
|
46
|
-
});
|
|
47
|
-
if (!res.ok) {
|
|
48
|
-
const text = await res.text();
|
|
49
|
-
throw new Error(`Failed to create organization: ${res.status} ${text}`);
|
|
50
|
-
}
|
|
51
|
-
return res.json();
|
|
52
|
-
}
|
|
53
|
-
async function setActiveOrganization(token, organizationId) {
|
|
54
|
-
const res = await fetch(`${CAMOX_API_URL}/api/auth/organization/set-active`, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: authHeaders(token),
|
|
57
|
-
body: JSON.stringify({ organizationId })
|
|
58
|
-
});
|
|
59
|
-
if (!res.ok) throw new Error(`Failed to set active organization: ${res.status}`);
|
|
60
|
-
}
|
|
61
|
-
async function checkSlugAvailability(token, slug) {
|
|
62
|
-
return createRpcClient(token).projects.checkSlugAvailability({ slug });
|
|
63
|
-
}
|
|
64
|
-
async function createProject(token, name, slug, organizationId) {
|
|
65
|
-
return await createRpcClient(token).projects.create({
|
|
66
|
-
name,
|
|
67
|
-
slug,
|
|
68
|
-
organizationId
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async function getProjectBySlug(token, slug, apiUrl) {
|
|
72
|
-
return createRpcClient(token, apiUrl).projects.getBySlug({ slug });
|
|
73
|
-
}
|
|
74
|
-
async function callTool(params) {
|
|
75
|
-
return await createRpcClient(params.token, params.apiUrl, params.environmentName, params.disableTelemetry).agent.callTool({
|
|
76
|
-
projectId: params.projectId,
|
|
77
|
-
name: params.name,
|
|
78
|
-
arguments: params.args
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
//#endregion
|
|
82
|
-
export { getProjectBySlug as a, verifySession as c, createProject as i, checkSlugAvailability as n, listOrganizations as o, createOrganization as r, setActiveOrganization as s, callTool as t };
|
package/dist/api-LuNKejJE.mjs
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
-
"style": "base-vega",
|
|
4
|
-
"rsc": false,
|
|
5
|
-
"tsx": true,
|
|
6
|
-
"tailwind": {
|
|
7
|
-
"config": "",
|
|
8
|
-
"css": "src/styles.css",
|
|
9
|
-
"baseColor": "zinc",
|
|
10
|
-
"cssVariables": true,
|
|
11
|
-
"prefix": ""
|
|
12
|
-
},
|
|
13
|
-
"iconLibrary": "lucide",
|
|
14
|
-
"aliases": {
|
|
15
|
-
"components": "@/components",
|
|
16
|
-
"utils": "@/lib/utils",
|
|
17
|
-
"ui": "@/components/ui",
|
|
18
|
-
"lib": "@/lib",
|
|
19
|
-
"hooks": "@/hooks"
|
|
20
|
-
},
|
|
21
|
-
"rtl": false,
|
|
22
|
-
"menuColor": "default",
|
|
23
|
-
"menuAccent": "subtle",
|
|
24
|
-
"registries": {}
|
|
25
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@camox/template-default",
|
|
3
|
-
"version": "0.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "module",
|
|
7
|
-
"scripts": {
|
|
8
|
-
"dev": "vp dev --port 7400",
|
|
9
|
-
"start": "node .output/server/index.mjs",
|
|
10
|
-
"build": "vp build",
|
|
11
|
-
"test:production": "node scripts/production-smoke.mjs",
|
|
12
|
-
"release": "camox release",
|
|
13
|
-
"serve": "vp preview",
|
|
14
|
-
"lint": "vp lint",
|
|
15
|
-
"check": "vp check"
|
|
16
|
-
},
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"@base-ui/react": "^1.4.1",
|
|
19
|
-
"@fontsource-variable/inter": "^5.2.8",
|
|
20
|
-
"@fontsource-variable/noto-serif": "^5.2.9",
|
|
21
|
-
"@tailwindcss/vite": "^4.3.0",
|
|
22
|
-
"@tanstack/react-query": "^5.100.11",
|
|
23
|
-
"camox": "workspace:*",
|
|
24
|
-
"class-variance-authority": "^0.7.1",
|
|
25
|
-
"clsx": "^2.1.1",
|
|
26
|
-
"lucide-react": "^0.476.0",
|
|
27
|
-
"nitro": "3.0.260311-beta",
|
|
28
|
-
"react": "^19.2.6",
|
|
29
|
-
"react-dom": "^19.2.6",
|
|
30
|
-
"shadcn": "^4.7.0",
|
|
31
|
-
"tailwind-merge": "^3.6.0",
|
|
32
|
-
"tailwindcss": "^4.3.0"
|
|
33
|
-
},
|
|
34
|
-
"devDependencies": {
|
|
35
|
-
"@babel/core": "^7.29.0",
|
|
36
|
-
"@rolldown/plugin-babel": "^0.2.3",
|
|
37
|
-
"@types/babel__core": "^7.20.5",
|
|
38
|
-
"@types/react": "^19.0.8",
|
|
39
|
-
"@types/react-dom": "^19.2.3",
|
|
40
|
-
"@typescript/native-preview": "7.0.0-dev.20260519.1",
|
|
41
|
-
"@vitejs/plugin-react": "^6.0.2",
|
|
42
|
-
"babel-plugin-react-compiler": "^1.0.0",
|
|
43
|
-
"tw-animate-css": "^1.4.0",
|
|
44
|
-
"vite-plus": "^0.1.21"
|
|
45
|
-
}
|
|
46
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# Production runtime smoke test
|
|
2
|
-
|
|
3
|
-
From the repository root:
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
pnpm exec nx run camox:build
|
|
7
|
-
pnpm --filter @camox/template-default test:production
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
The test builds a temporary copy of the starter using Nitro's Node middleware
|
|
11
|
-
preset, moves only `.output` into an isolated deployment, removes build-time
|
|
12
|
-
`node_modules` links, and serves a real HTTP request. It asserts that React/Base UI
|
|
13
|
-
can render HTML and Camox's OG renderer can produce a PNG. No Camox API, real auth,
|
|
14
|
-
application-level dependency tracing overrides, or direct Takumi dependency is
|
|
15
|
-
needed. A temporary home directory supplies fake build authentication without
|
|
16
|
-
modifying the developer's credentials. CI runs this test after building the SDK.
|
|
17
|
-
|
|
18
|
-
## Issue #106 investigation
|
|
19
|
-
|
|
20
|
-
- **Takumi:** the original `createRequire(import.meta.url).resolve(...)` survives
|
|
21
|
-
bundling, but the WASM package/asset is absent from the deployment. The isolated
|
|
22
|
-
smoke test reproduced `Cannot find module '@takumi-rs/wasm/takumi_wasm_bg.wasm'`
|
|
23
|
-
before the fix. Camox now uses a static module import so Nitro's WASM loader
|
|
24
|
-
owns asset inclusion, including when dependencies are fully bundled. The
|
|
25
|
-
server-only guard in `createLayout` prevents this import reaching client builds.
|
|
26
|
-
- **tslib:** a package containing only `package.json` and `tslib.es6.mjs`, as
|
|
27
|
-
reported, reproduces Node's missing `tslib/modules/index.js` error: Node selects
|
|
28
|
-
that entry from the package exports. The complete upstream package is valid;
|
|
29
|
-
the partial traced copy is not. The current starter build did not independently
|
|
30
|
-
reproduce that partial copy. Camox now adds `tslib` to Nitro's `noExternals`,
|
|
31
|
-
keeping its existing ESM alias and removing reliance on partial package tracing.
|
|
32
|
-
Plugin tests cover preservation of user options, including `noExternals: true`.
|
|
33
|
-
|
|
34
|
-
These are SDK integration fixes, not starter workarounds. Applications should not
|
|
35
|
-
need to add Takumi as a direct dependency or configure `traceDeps`/`noExternals`.
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
|
|
4
|
-
import { createRequire } from "node:module";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { dirname, join, resolve } from "node:path";
|
|
7
|
-
import { fileURLToPath } from "node:url";
|
|
8
|
-
|
|
9
|
-
const appRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
|
-
const temp = await mkdtemp(join(tmpdir(), "camox-production-"));
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const root = join(temp, "app");
|
|
14
|
-
const home = join(temp, "home");
|
|
15
|
-
await mkdir(join(home, ".camox"), { recursive: true });
|
|
16
|
-
await writeFile(
|
|
17
|
-
join(home, ".camox/auth.json"),
|
|
18
|
-
JSON.stringify({
|
|
19
|
-
"http://localhost:3274": { token: "smoke", name: "Smoke", email: "smoke@example.com" },
|
|
20
|
-
}),
|
|
21
|
-
);
|
|
22
|
-
await mkdir(root);
|
|
23
|
-
await mkdir(join(temp, "node_modules"));
|
|
24
|
-
// Nitro resolves its builder from the project root. Match Vite+'s own Vite
|
|
25
|
-
// dependency without relying on workspace hoisting in this temporary app.
|
|
26
|
-
const vitePackage = createRequire(import.meta.resolve("vite-plus")).resolve("vite/package.json");
|
|
27
|
-
await symlink(dirname(vitePackage), join(temp, "node_modules/vite"), "dir");
|
|
28
|
-
for (const file of ["src", "vite.config.ts", "tsconfig.json", "package.json"]) {
|
|
29
|
-
await cp(join(appRoot, file), join(root, file), { recursive: true });
|
|
30
|
-
}
|
|
31
|
-
await symlink(join(appRoot, "node_modules"), join(root, "node_modules"), "dir");
|
|
32
|
-
const configPath = join(root, "vite.config.ts");
|
|
33
|
-
await writeFile(
|
|
34
|
-
configPath,
|
|
35
|
-
(await readFile(configPath, "utf8")).replace(
|
|
36
|
-
"nitro()",
|
|
37
|
-
'nitro({ routes: { "/smoke": "./routes/smoke.ts" } })',
|
|
38
|
-
),
|
|
39
|
-
);
|
|
40
|
-
await mkdir(join(root, "routes"), { recursive: true });
|
|
41
|
-
await writeFile(
|
|
42
|
-
join(root, "routes/smoke.ts"),
|
|
43
|
-
`import { Dialog } from "@base-ui/react/dialog";
|
|
44
|
-
import { createElement } from "react";
|
|
45
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
46
|
-
import { ImageResponse } from "camox/_internal/imageResponse";
|
|
47
|
-
|
|
48
|
-
export default async () => {
|
|
49
|
-
const element = createElement("div", { style: { color: "black", background: "white" } }, "Production smoke");
|
|
50
|
-
const image = new ImageResponse(element, { width: 320, height: 160 });
|
|
51
|
-
const bytes = new Uint8Array(await image.arrayBuffer());
|
|
52
|
-
const html = renderToStaticMarkup(createElement(Dialog.Root, null,
|
|
53
|
-
createElement(Dialog.Trigger, null, "Production smoke")));
|
|
54
|
-
return Response.json({ html, png: Array.from(bytes.slice(0, 8)) });
|
|
55
|
-
};
|
|
56
|
-
`,
|
|
57
|
-
);
|
|
58
|
-
const build = spawnSync(
|
|
59
|
-
process.execPath,
|
|
60
|
-
[
|
|
61
|
-
"--input-type=module",
|
|
62
|
-
"-e",
|
|
63
|
-
`
|
|
64
|
-
const { createBuilder } = await import(${JSON.stringify(import.meta.resolve("vite-plus"))});
|
|
65
|
-
const builder = await createBuilder({ root: ${JSON.stringify(root)} });
|
|
66
|
-
await builder.buildApp();
|
|
67
|
-
`,
|
|
68
|
-
],
|
|
69
|
-
{
|
|
70
|
-
cwd: root,
|
|
71
|
-
env: {
|
|
72
|
-
...process.env,
|
|
73
|
-
HOME: home,
|
|
74
|
-
CAMOX_INTERNAL_RELEASE: "0",
|
|
75
|
-
NITRO_PRESET: "node-middleware",
|
|
76
|
-
},
|
|
77
|
-
encoding: "utf8",
|
|
78
|
-
timeout: 180_000,
|
|
79
|
-
killSignal: "SIGKILL",
|
|
80
|
-
},
|
|
81
|
-
);
|
|
82
|
-
assert.equal(build.status, 0, `${build.stdout}\n${build.stderr}`);
|
|
83
|
-
|
|
84
|
-
// Move only the deployment artifact away from all workspace node_modules.
|
|
85
|
-
const deployment = join(temp, "deployment");
|
|
86
|
-
await cp(join(root, ".output"), deployment, { recursive: true });
|
|
87
|
-
await rm(root, { recursive: true, force: true });
|
|
88
|
-
await rm(join(temp, "node_modules"), { recursive: true, force: true });
|
|
89
|
-
const runtime = spawnSync(
|
|
90
|
-
process.execPath,
|
|
91
|
-
[
|
|
92
|
-
"--input-type=module",
|
|
93
|
-
"-e",
|
|
94
|
-
`
|
|
95
|
-
import assert from "node:assert/strict";
|
|
96
|
-
import { createServer } from "node:http";
|
|
97
|
-
const { middleware } = await import("./server/index.mjs");
|
|
98
|
-
const server = createServer(middleware);
|
|
99
|
-
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
|
|
100
|
-
try {
|
|
101
|
-
const response = await fetch("http://127.0.0.1:" + server.address().port + "/smoke");
|
|
102
|
-
assert.equal(response.status, 200, await response.clone().text());
|
|
103
|
-
const result = await response.json();
|
|
104
|
-
assert.match(result.html, /Production smoke/);
|
|
105
|
-
assert.deepEqual(result.png, [137, 80, 78, 71, 13, 10, 26, 10]);
|
|
106
|
-
} finally {
|
|
107
|
-
server.closeAllConnections();
|
|
108
|
-
server.close();
|
|
109
|
-
}
|
|
110
|
-
`,
|
|
111
|
-
],
|
|
112
|
-
{
|
|
113
|
-
cwd: deployment,
|
|
114
|
-
env: { ...process.env, NODE_PATH: "", NODE_ENV: "production" },
|
|
115
|
-
encoding: "utf8",
|
|
116
|
-
timeout: 30_000,
|
|
117
|
-
killSignal: "SIGKILL",
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
assert.equal(runtime.status, 0, `${runtime.stdout}\n${runtime.stderr}`);
|
|
121
|
-
console.log("Production server rendered HTML and a Takumi PNG from an isolated deployment.");
|
|
122
|
-
} finally {
|
|
123
|
-
await rm(temp, { recursive: true, force: true });
|
|
124
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { Type, createBlock } from "camox/createBlock";
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
Accordion,
|
|
5
|
-
AccordionContent,
|
|
6
|
-
AccordionItem,
|
|
7
|
-
AccordionTrigger,
|
|
8
|
-
} from "@/components/ui/accordion";
|
|
9
|
-
|
|
10
|
-
const faq = createBlock({
|
|
11
|
-
id: "faq",
|
|
12
|
-
title: "FAQ",
|
|
13
|
-
description:
|
|
14
|
-
"Use this block to answer common questions about the product, pricing, or company. Place it near the bottom of a page to address objections before a conversion section.",
|
|
15
|
-
content: {
|
|
16
|
-
items: Type.Repeater({
|
|
17
|
-
content: {
|
|
18
|
-
question: Type.String({
|
|
19
|
-
default: "How do I make this website my own?",
|
|
20
|
-
title: "Question",
|
|
21
|
-
}),
|
|
22
|
-
answer: Type.String({
|
|
23
|
-
default:
|
|
24
|
-
"Tell your coding agent what you're building, who it's for, and the style you want. Your agent has access to the Camox skill and will use it to create your own blocks, update your content, and manage your pages.",
|
|
25
|
-
title: "Answer",
|
|
26
|
-
}),
|
|
27
|
-
},
|
|
28
|
-
minItems: 3,
|
|
29
|
-
maxItems: Infinity,
|
|
30
|
-
title: "Questions",
|
|
31
|
-
toMarkdown: (c) => [`Q: ${c.question}`, `A: ${c.answer}`],
|
|
32
|
-
}),
|
|
33
|
-
},
|
|
34
|
-
component: FaqComponent,
|
|
35
|
-
toMarkdown: (c) => [c.items],
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
function FaqComponent() {
|
|
39
|
-
return (
|
|
40
|
-
<section className="py-12 sm:py-16 md:py-20">
|
|
41
|
-
<div className="mx-auto max-w-2xl px-4">
|
|
42
|
-
<Accordion>
|
|
43
|
-
<faq.Repeater name="items">
|
|
44
|
-
{(item, index) => (
|
|
45
|
-
<AccordionItem value={index}>
|
|
46
|
-
<AccordionTrigger className="items-center text-lg">
|
|
47
|
-
<item.Field name="question">{(props) => <span {...props} />}</item.Field>
|
|
48
|
-
</AccordionTrigger>
|
|
49
|
-
<AccordionContent>
|
|
50
|
-
<item.Field name="answer">
|
|
51
|
-
{(props) => <p {...props} className="text-muted-foreground text-base" />}
|
|
52
|
-
</item.Field>
|
|
53
|
-
</AccordionContent>
|
|
54
|
-
</AccordionItem>
|
|
55
|
-
)}
|
|
56
|
-
</faq.Repeater>
|
|
57
|
-
</Accordion>
|
|
58
|
-
</div>
|
|
59
|
-
</section>
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export { faq as block };
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { Type, createBlock } from "camox/createBlock";
|
|
2
|
-
import { Link } from "camox/navigation";
|
|
3
|
-
|
|
4
|
-
const footer = createBlock({
|
|
5
|
-
id: "footer",
|
|
6
|
-
title: "Footer",
|
|
7
|
-
layoutOnly: true,
|
|
8
|
-
synced: true,
|
|
9
|
-
description: "A footer at the bottom of a page with a site name and navigation links.",
|
|
10
|
-
content: {
|
|
11
|
-
title: Type.String({ default: "{{projectName}}" }),
|
|
12
|
-
links: Type.Repeater({
|
|
13
|
-
content: {
|
|
14
|
-
link: Type.Link({
|
|
15
|
-
default: { text: "Footer link", href: "#", newTab: false },
|
|
16
|
-
title: "Link",
|
|
17
|
-
}),
|
|
18
|
-
},
|
|
19
|
-
minItems: 2,
|
|
20
|
-
maxItems: 12,
|
|
21
|
-
title: "Links",
|
|
22
|
-
toMarkdown: (c) => [c.link],
|
|
23
|
-
}),
|
|
24
|
-
},
|
|
25
|
-
component: FooterComponent,
|
|
26
|
-
toMarkdown: (c) => [c.title, c.links],
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
function FooterComponent() {
|
|
30
|
-
return (
|
|
31
|
-
<footer className="dark bg-background border-border border-t py-4">
|
|
32
|
-
<div className="container mx-auto px-4">
|
|
33
|
-
<div className="flex flex-col gap-4 sm:flex-row sm:flex-wrap sm:items-center sm:justify-between sm:gap-x-6 sm:gap-y-2">
|
|
34
|
-
<div className="flex items-center gap-2">
|
|
35
|
-
<footer.Field name="title">
|
|
36
|
-
{(props) => <div {...props} className="text-foreground text-sm font-bold" />}
|
|
37
|
-
</footer.Field>
|
|
38
|
-
<div className="text-muted-foreground text-sm">© {new Date().getFullYear()}</div>
|
|
39
|
-
</div>
|
|
40
|
-
|
|
41
|
-
<div className="flex flex-col items-start gap-4 sm:ml-auto sm:flex-row sm:flex-wrap sm:items-center sm:justify-end">
|
|
42
|
-
<footer.Repeater name="links">
|
|
43
|
-
{(linkItem) => (
|
|
44
|
-
<linkItem.Link name="link">
|
|
45
|
-
{(props) => (
|
|
46
|
-
<Link
|
|
47
|
-
{...props}
|
|
48
|
-
className="text-muted-foreground hover:text-foreground text-sm transition-colors"
|
|
49
|
-
/>
|
|
50
|
-
)}
|
|
51
|
-
</linkItem.Link>
|
|
52
|
-
)}
|
|
53
|
-
</footer.Repeater>
|
|
54
|
-
</div>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</footer>
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export { footer as block };
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { Type, createBlock } from "camox/createBlock";
|
|
2
|
-
import { Link } from "camox/navigation";
|
|
3
|
-
|
|
4
|
-
import { Button } from "@/components/ui/button";
|
|
5
|
-
import { cn } from "@/lib/utils";
|
|
6
|
-
|
|
7
|
-
const hero = createBlock({
|
|
8
|
-
id: "hero",
|
|
9
|
-
title: "Hero",
|
|
10
|
-
description:
|
|
11
|
-
"Use this block as the main landing section at the top of a page. It should capture attention immediately with a clear value proposition.",
|
|
12
|
-
content: {
|
|
13
|
-
title: Type.String({
|
|
14
|
-
default: "Let's get going on {{projectName}}",
|
|
15
|
-
title: "Title",
|
|
16
|
-
}),
|
|
17
|
-
description: Type.String({
|
|
18
|
-
default: "Press ⌘+Enter to edit this page.",
|
|
19
|
-
maxLength: 280,
|
|
20
|
-
title: "Description",
|
|
21
|
-
}),
|
|
22
|
-
cta: Type.Link({
|
|
23
|
-
default: { text: "Get started", href: "/", newTab: false },
|
|
24
|
-
title: "CTA",
|
|
25
|
-
}),
|
|
26
|
-
illustration: Type.Image({
|
|
27
|
-
title: "Illustration",
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
settings: {
|
|
31
|
-
withIllustration: Type.Boolean({
|
|
32
|
-
default: true,
|
|
33
|
-
title: "With illustration",
|
|
34
|
-
}),
|
|
35
|
-
theme: Type.Enum({
|
|
36
|
-
default: "dark",
|
|
37
|
-
options: { light: "Light", dark: "Dark" },
|
|
38
|
-
title: "Theme",
|
|
39
|
-
}),
|
|
40
|
-
},
|
|
41
|
-
component: HeroComponent,
|
|
42
|
-
toMarkdown: (c, s) => [`# ${c.title}`, c.description, s.withIllustration(c.illustration), c.cta],
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
function HeroComponent() {
|
|
46
|
-
const withIllustration = hero.useSetting("withIllustration");
|
|
47
|
-
const theme = hero.useSetting("theme");
|
|
48
|
-
|
|
49
|
-
return (
|
|
50
|
-
<section
|
|
51
|
-
className={cn(
|
|
52
|
-
theme === "dark" ? "dark" : "light",
|
|
53
|
-
"bg-background py-16 sm:py-24 md:py-28",
|
|
54
|
-
!withIllustration && "flex flex-col items-center justify-center",
|
|
55
|
-
)}
|
|
56
|
-
>
|
|
57
|
-
<div className="container mx-auto">
|
|
58
|
-
<div
|
|
59
|
-
className={cn(
|
|
60
|
-
withIllustration
|
|
61
|
-
? "grid items-center gap-12 lg:grid-cols-[1fr_auto]"
|
|
62
|
-
: "mx-auto max-w-3xl text-center",
|
|
63
|
-
)}
|
|
64
|
-
>
|
|
65
|
-
<div className={cn(withIllustration && "text-left")}>
|
|
66
|
-
<hero.Field name="title">
|
|
67
|
-
{(props) => (
|
|
68
|
-
<h1
|
|
69
|
-
{...props}
|
|
70
|
-
className="text-foreground mb-6 text-4xl font-bold tracking-tight sm:text-6xl"
|
|
71
|
-
/>
|
|
72
|
-
)}
|
|
73
|
-
</hero.Field>
|
|
74
|
-
<hero.Field name="description">
|
|
75
|
-
{(props) => <p {...props} className="text-muted-foreground mb-10 text-xl" />}
|
|
76
|
-
</hero.Field>
|
|
77
|
-
<hero.Link name="cta">
|
|
78
|
-
{(props) => <Button size="lg" nativeButton={false} render={<Link {...props} />} />}
|
|
79
|
-
</hero.Link>
|
|
80
|
-
</div>
|
|
81
|
-
{withIllustration && (
|
|
82
|
-
<hero.Image name="illustration">
|
|
83
|
-
{(props) => <img {...props} className="h-auto w-full max-w-md rounded-lg" />}
|
|
84
|
-
</hero.Image>
|
|
85
|
-
)}
|
|
86
|
-
</div>
|
|
87
|
-
</div>
|
|
88
|
-
</section>
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export { hero as block };
|