@wenathlan/cobaia 0.0.0-stage → 2.0.158
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/App.tsx +52 -0
- package/Dockerfile +48 -0
- package/_headers +4 -0
- package/_redirects +1 -0
- package/adsconfig.ts +59 -0
- package/biome.jsonc +134 -0
- package/bun.lock +2451 -0
- package/capacitor.config.ts +51 -0
- package/checkoutlinks.ts +80 -0
- package/cli.ts +114 -0
- package/cobaia.csproj +22 -0
- package/cobaia.gemspec +14 -0
- package/cobaia.java +16 -0
- package/content.ts +425 -0
- package/db.ts +219 -0
- package/devthink.toml +5 -0
- package/docs/features.md +58 -0
- package/docs/monetization.md +61 -0
- package/familyurl.ts +22 -0
- package/favicon.svg +1 -0
- package/icon.svg +1 -0
- package/index.html +26 -0
- package/labels.ts +291 -0
- package/manifest.webmanifest +56 -0
- package/mime.types +12 -0
- package/netlify.toml +2 -0
- package/package.json +207 -4
- package/pnpm-workspace.yaml +101 -0
- package/pnpmboot.package-lock.json +157 -0
- package/pnpmboot.package.json +6 -0
- package/pom.xml +29 -0
- package/posts.ts +54 -0
- package/robots.txt +90 -0
- package/roster.ts +29 -0
- package/sandboxmodality.ts +138 -0
- package/schema.prisma +31 -0
- package/sitemap.xml +42 -0
- package/sol/Sol.tsx +91 -0
- package/sol/apps/apps.tsx +66 -0
- package/sol/blog/blog.tsx +86 -0
- package/sol/home/home.tsx +140 -0
- package/sol/intro/intro.tsx +187 -0
- package/sol/legal/legal.tsx +76 -0
- package/sol/notfound/notfound.tsx +32 -0
- package/sol/pricing/pricing.tsx +98 -0
- package/sol/shell/Shell.tsx +161 -0
- package/sol/sol.css +1951 -0
- package/sol/support/support.tsx +102 -0
- package/sol/wingets/IntroFooter.tsx +69 -0
- package/sol/wingets/IntroPlans.tsx +93 -0
- package/sol/wingets/IntroSplash.tsx +105 -0
- package/sol/wingets/ShellAdsslot.tsx +47 -0
- package/sol/wingets/ShellBrand.tsx +86 -0
- package/sol/wingets/index.ts +18 -0
- package/tests/adsconfig.test.ts +51 -0
- package/tests/checkoutlinks.test.ts +73 -0
- package/tests/familyurl.test.ts +40 -0
- package/tests/posts.test.ts +76 -0
- package/tests/sandboxmodality.test.ts +77 -0
- package/tsconfig.json +22 -0
- package/tsconfig.node.json +22 -0
- package/vercel.json +3 -0
- package/vite.config.ts +44 -0
- package/vitest.config.ts +42 -0
- package/wrangler.toml +7 -0
- package/README.md +0 -3
package/App.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* App — the global anchor (layer 1 of the anchor architecture) and the only
|
|
3
|
+
* TSX outside the cobaia theme. cobaia is the family site: the front door
|
|
4
|
+
* of the DevThink family — the pricing, the support page, the app directory
|
|
5
|
+
* and the update blog for the ten deployable applications. The site is a
|
|
6
|
+
* plain CONTENT surface: no onboarding, no entry gate — the home renders
|
|
7
|
+
* directly and every page is public; the intro (/intro) is the one bare
|
|
8
|
+
* presentation page the family grammar gives every app, mounted full-bleed
|
|
9
|
+
* under the family fullscreen law. The root manages the theme as a single
|
|
10
|
+
* file: it imports only the theme anchor (sol/Sol.tsx — the file named
|
|
11
|
+
* after the theme folder, beside sol/sol.css), the theme stylesheet and the
|
|
12
|
+
* root logics, wraps the anchor in the wouter router (the subpath the pages
|
|
13
|
+
* deploy owns answers through the declared base, the same mount grammar
|
|
14
|
+
* every sibling carries) and renders the theme into the root element —
|
|
15
|
+
* the site never booted without this mount (the blank cobaia defect).
|
|
16
|
+
* Every page and component lives behind the anchor chain (App → Sol → the
|
|
17
|
+
* page anchors → the loose components); nothing is imported directly.
|
|
18
|
+
*/
|
|
19
|
+
import { createRoot } from "react-dom/client";
|
|
20
|
+
import { Router as WouterRouter } from "wouter";
|
|
21
|
+
import Sol from "./sol/Sol";
|
|
22
|
+
import "@fontsource-variable/geist-mono";
|
|
23
|
+
import "@fontsource-variable/inter";
|
|
24
|
+
import "./sol/sol.css";
|
|
25
|
+
import { seedLabels } from "./labels";
|
|
26
|
+
|
|
27
|
+
// the copy law rides the boot: the seed refreshes the label store rows from
|
|
28
|
+
// the manifest (the browser cache mirrors the site db rows) before the theme
|
|
29
|
+
// renders — best effort, the manifest always answers the wingets.
|
|
30
|
+
if (typeof window !== "undefined") void seedLabels().catch(() => {});
|
|
31
|
+
|
|
32
|
+
function routerBase(): string {
|
|
33
|
+
// the base rides the BUILD declaration only (the sibling grammar): the
|
|
34
|
+
// pages subpath owns /devthink/cobaia/, and that declaration arrives
|
|
35
|
+
// through the vite base — deriving it from the runtime path made every
|
|
36
|
+
// deep link look like a deployment subfolder
|
|
37
|
+
const declaredBase = import.meta.env.BASE_URL;
|
|
38
|
+
if (declaredBase !== "/" && declaredBase !== "./") return declaredBase.replace(/\/$/, "");
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function App() {
|
|
43
|
+
return (
|
|
44
|
+
<WouterRouter base={routerBase()}>
|
|
45
|
+
<Sol />
|
|
46
|
+
</WouterRouter>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const root = document.getElementById("root");
|
|
51
|
+
if (!root) throw new Error("cobaia root element is missing.");
|
|
52
|
+
createRoot(root).render(<App />);
|
package/Dockerfile
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# cobaia 2.0.134 — THE SITE CONTAINER FILE
|
|
2
|
+
#
|
|
3
|
+
# The family container doctrine in the sol shape: one versioned static
|
|
4
|
+
# container per site. The sol theme builds inside the container — stage one
|
|
5
|
+
# installs the locked pnpm tree and runs the vite build, stage two serves
|
|
6
|
+
# the built theme through the unprivileged nginx image. The version rides
|
|
7
|
+
# the ARG the metadata lockstep stamps and the OCI labels declare — the
|
|
8
|
+
# registry lanes pass it at build time, nothing is hardcoded beside the
|
|
9
|
+
# stamp.
|
|
10
|
+
|
|
11
|
+
FROM node:26-alpine@sha256:40cd011b55415bc894e45b02fdd5a5dfde056921b7d23a64c74f80497af48797 AS solbuild
|
|
12
|
+
|
|
13
|
+
ARG COBAIA_VERSION=2.0.134
|
|
14
|
+
|
|
15
|
+
WORKDIR /src
|
|
16
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
17
|
+
COPY pnpmboot.package.json /tmp/pnpmboot/package.json
|
|
18
|
+
COPY pnpmboot.package-lock.json /tmp/pnpmboot/package-lock.json
|
|
19
|
+
RUN apk add --no-cache python3 make g++ \
|
|
20
|
+
&& npm ci --prefix /tmp/pnpmboot --no-fund --no-audit \
|
|
21
|
+
&& ln -sf /tmp/pnpmboot/node_modules/.bin/pnpm /usr/local/bin/pnpm \
|
|
22
|
+
&& pnpm install --frozen-lockfile
|
|
23
|
+
COPY . .
|
|
24
|
+
RUN pnpm build
|
|
25
|
+
|
|
26
|
+
FROM nginxinc/nginx-unprivileged:1.29-alpine@sha256:0c79d56aee561a1d81c63f00eee5fb5fe29279560cdc55e91425133104c7fbe6
|
|
27
|
+
|
|
28
|
+
ARG COBAIA_VERSION=2.0.134
|
|
29
|
+
|
|
30
|
+
LABEL org.opencontainers.image.title="cobaia" \
|
|
31
|
+
org.opencontainers.image.description="cobaia — the monetization surface: the ads, the plans and the conversion, kept apart from the main sites." \
|
|
32
|
+
org.opencontainers.image.version="${COBAIA_VERSION}" \
|
|
33
|
+
org.opencontainers.image.licenses="GPL-3.0-only" \
|
|
34
|
+
org.opencontainers.image.source="https://github.com/wenathlan/devthink" \
|
|
35
|
+
org.opencontainers.image.documentation="https://github.com/wenathlan/devthink/tree/main/cobaia"
|
|
36
|
+
|
|
37
|
+
COPY --from=solbuild /src/ /usr/share/nginx/html/
|
|
38
|
+
|
|
39
|
+
# the explicit non-root runtime user (uid 101 is the nginx-unprivileged
|
|
40
|
+
# default): the scan gate refuses an image without a pinned unprivileged
|
|
41
|
+
# user, so the declaration is spelled out instead of inherited.
|
|
42
|
+
USER 101
|
|
43
|
+
|
|
44
|
+
# the unprivileged image answers 8080; the healthcheck probes the served
|
|
45
|
+
# root the same way the pages lane does.
|
|
46
|
+
EXPOSE 8080
|
|
47
|
+
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
|
48
|
+
CMD ["wget", "-qO-", "http://127.0.0.1:8080/"]
|
package/_headers
ADDED
package/_redirects
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/spa/* /index.html 200
|
package/adsconfig.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* adsconfig.ts — the honest ad slot registry of the family site, pure data
|
|
3
|
+
* at the app root. The family site carries ONE reserved ad container per
|
|
4
|
+
* content page (home, the app directory, the blog index), always at the end
|
|
5
|
+
* of the content, always labeled "advertisement", always a fixed aspect
|
|
6
|
+
* ratio so the page never shifts when a real unit arrives.
|
|
7
|
+
*
|
|
8
|
+
* THE SCRIPT RULE: no ad network script lives in this repository. The
|
|
9
|
+
* AdSense (or equivalent) loader is injected AFTER the network approves the
|
|
10
|
+
* site, through the deploy environment only. Until then the slot ids stay
|
|
11
|
+
* empty and the container renders the honest empty note. No network call,
|
|
12
|
+
* no click encouragement, no automated traffic ever touches an ad surface —
|
|
13
|
+
* invalid traffic is the one thing that kills both the ad account and the
|
|
14
|
+
* domain reputation, so the family does not do it.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** one reserved ad slot: the config id and the reserved shape. */
|
|
18
|
+
export type adsslot = {
|
|
19
|
+
/** the ad network slot id — empty until the owner configures it after approval */
|
|
20
|
+
slotid: string;
|
|
21
|
+
/** the display format the reserved box mimics */
|
|
22
|
+
format: string;
|
|
23
|
+
/** the css aspect-ratio value the container reserves (never shifts layout) */
|
|
24
|
+
ratio: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** the fixed label every ad container renders, visible and honest. */
|
|
28
|
+
export const ADS_LABEL = "advertisement";
|
|
29
|
+
|
|
30
|
+
/** the honest note the container renders while no slot id is configured. */
|
|
31
|
+
export const ADS_EMPTY = "ad slot not configured yet";
|
|
32
|
+
|
|
33
|
+
/** the reserved slots per content page, keyed by the page folder name. */
|
|
34
|
+
export const ADSSLOTS: Readonly<Record<string, adsslot>> = {
|
|
35
|
+
home: { slotid: "", format: "display", ratio: "728 / 90" },
|
|
36
|
+
apps: { slotid: "", format: "in-feed", ratio: "3 / 1" },
|
|
37
|
+
blog: { slotid: "", format: "in-article", ratio: "3 / 1" },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Reads the reserved slot of one content page.
|
|
42
|
+
*
|
|
43
|
+
* @param page the page folder name (home, apps, blog).
|
|
44
|
+
* @returns the slot, or undefined when the page carries no slot.
|
|
45
|
+
*/
|
|
46
|
+
export function adsslotfor(page: string): adsslot | undefined {
|
|
47
|
+
return ADSSLOTS[page];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Answers whether a slot is configured with a real network id. An empty
|
|
52
|
+
* (or whitespace) slot id means the container stays an honest placeholder.
|
|
53
|
+
*
|
|
54
|
+
* @param slot the reserved slot entry.
|
|
55
|
+
* @returns true only when the slot id carries a value.
|
|
56
|
+
*/
|
|
57
|
+
export function adsslotlive(slot: adsslot | undefined): boolean {
|
|
58
|
+
return slot !== undefined && slot.slotid.trim().length > 0;
|
|
59
|
+
}
|
package/biome.jsonc
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// the family biome baseline: identical across the apps, the identity only names the app.
|
|
2
|
+
{
|
|
3
|
+
"$schema": "https://biomejs.dev/schemas/2.6.4/schema.json",
|
|
4
|
+
"vcs": {
|
|
5
|
+
"enabled": true,
|
|
6
|
+
"clientKind": "git",
|
|
7
|
+
"useIgnoreFile": false
|
|
8
|
+
},
|
|
9
|
+
"files": {
|
|
10
|
+
"includes": [
|
|
11
|
+
"**/*.ts",
|
|
12
|
+
"**/*.tsx",
|
|
13
|
+
"**/*.js",
|
|
14
|
+
"**/*.mjs",
|
|
15
|
+
"**/*.cjs",
|
|
16
|
+
"**/*.json",
|
|
17
|
+
"!**/dist",
|
|
18
|
+
"!**/distpackage",
|
|
19
|
+
"!**/target",
|
|
20
|
+
"!**/node_modules",
|
|
21
|
+
"!**/bun.lock",
|
|
22
|
+
"!**/*.min.js",
|
|
23
|
+
"!**/docs",
|
|
24
|
+
"!**/tests/tmp",
|
|
25
|
+
"!**/tests/code",
|
|
26
|
+
"!**/sol/dist",
|
|
27
|
+
"!**/sol/.vite",
|
|
28
|
+
"!**/sol/node_modules",
|
|
29
|
+
"!**/coverage",
|
|
30
|
+
"!**/android",
|
|
31
|
+
"!**/releasecheck",
|
|
32
|
+
"!**/build",
|
|
33
|
+
"!**/release",
|
|
34
|
+
"!index-*.js"
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
"formatter": {
|
|
38
|
+
"enabled": true,
|
|
39
|
+
"indentStyle": "space",
|
|
40
|
+
"indentWidth": 2,
|
|
41
|
+
"lineWidth": 120,
|
|
42
|
+
"lineEnding": "lf"
|
|
43
|
+
},
|
|
44
|
+
"linter": {
|
|
45
|
+
"enabled": true,
|
|
46
|
+
"rules": {
|
|
47
|
+
"preset": "recommended",
|
|
48
|
+
"correctness": {
|
|
49
|
+
"noUnusedVariables": "warn",
|
|
50
|
+
"noUnsafeOptionalChaining": "warn",
|
|
51
|
+
"useExhaustiveDependencies": "warn",
|
|
52
|
+
"noUnusedImports": "warn",
|
|
53
|
+
"noUnusedPrivateClassMembers": "warn",
|
|
54
|
+
"noUnusedFunctionParameters": "warn",
|
|
55
|
+
"noConstantCondition": "warn",
|
|
56
|
+
"noVoidTypeReturn": "warn"
|
|
57
|
+
},
|
|
58
|
+
"a11y": {
|
|
59
|
+
"useButtonType": "warn",
|
|
60
|
+
"noStaticElementInteractions": "warn",
|
|
61
|
+
"useSemanticElements": "warn",
|
|
62
|
+
"useAriaPropsSupportedByRole": "warn"
|
|
63
|
+
},
|
|
64
|
+
"suspicious": {
|
|
65
|
+
"noExplicitAny": "warn",
|
|
66
|
+
"noArrayIndexKey": "warn",
|
|
67
|
+
"noControlCharactersInRegex": "warn",
|
|
68
|
+
"noRedeclare": "warn",
|
|
69
|
+
"noAssignInExpressions": "warn",
|
|
70
|
+
"noShadowRestrictedNames": "warn",
|
|
71
|
+
"noDuplicateElseIf": "warn",
|
|
72
|
+
"noImplicitAnyLet": "warn",
|
|
73
|
+
"noTsIgnore": "warn",
|
|
74
|
+
"noGlobalIsNan": "warn"
|
|
75
|
+
},
|
|
76
|
+
"style": {
|
|
77
|
+
"noNonNullAssertion": "warn",
|
|
78
|
+
"useTemplate": "warn",
|
|
79
|
+
"useExponentiationOperator": "warn",
|
|
80
|
+
"useConst": "warn"
|
|
81
|
+
},
|
|
82
|
+
"complexity": {
|
|
83
|
+
"useLiteralKeys": "warn",
|
|
84
|
+
"useOptionalChain": "warn",
|
|
85
|
+
"noUselessContinue": "warn",
|
|
86
|
+
"noUselessUndefinedInitialization": "warn",
|
|
87
|
+
"noUselessEscapeInRegex": "warn"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"javascript": {
|
|
92
|
+
"formatter": {
|
|
93
|
+
"quoteStyle": "double",
|
|
94
|
+
"semicolons": "always",
|
|
95
|
+
"trailingCommas": "all",
|
|
96
|
+
"arrowParentheses": "always"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"css": {
|
|
100
|
+
"parser": {
|
|
101
|
+
"tailwindDirectives": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"overrides": [
|
|
105
|
+
{
|
|
106
|
+
"includes": ["**/*"],
|
|
107
|
+
"linter": {
|
|
108
|
+
"rules": {
|
|
109
|
+
"suspicious": {
|
|
110
|
+
"noDebugger": "off"
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"includes": ["tests/workflowcheck.ts", "tests/publishlanes.test.ts"],
|
|
117
|
+
"linter": {
|
|
118
|
+
"rules": {
|
|
119
|
+
"suspicious": {
|
|
120
|
+
"noTemplateCurlyInString": "off"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"assist": {
|
|
127
|
+
"enabled": true,
|
|
128
|
+
"actions": {
|
|
129
|
+
"source": {
|
|
130
|
+
"useSortedKeys": "off"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|