robodev 0.10.0 → 0.12.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/package.json +1 -1
- package/src/collect-files.test.ts +19 -0
- package/src/collect-files.ts +1 -0
- package/src/config.test.ts +26 -0
- package/src/config.ts +34 -5
- package/src/emit-starter.test.ts +54 -0
- package/src/emit-starter.ts +47 -13
- package/src/index.ts +37 -4
- package/templates/auth-chat/README.md +1 -1
- package/templates/auth-chat/api/health.ts +1 -1
- package/templates/auth-chat/api/me.ts +1 -1
- package/templates/auth-chat/api/messages.ts +1 -1
- package/templates/auth-chat/package.json +1 -1
- package/templates/auth-chat/src/main.tsx +5 -5
- package/templates/backend/README.md +1 -1
- package/templates/backend/api/health.ts +1 -1
- package/templates/backend/api/launch.ts +1 -1
- package/templates/backend/api/me.ts +1 -1
- package/templates/backend/api/motto.ts +1 -1
- package/templates/backend/api/planets/[id].ts +1 -1
- package/templates/backend/api/planets.ts +1 -1
- package/templates/backend/api/rockets.ts +1 -1
- package/templates/backend/package.json +1 -1
- package/templates/catalog.json +2 -2
- package/templates/empty/README.md +1 -1
- package/templates/empty/api/health.ts +1 -1
- package/templates/empty/package.json +1 -1
- package/templates/space/.config/local.spa.template.yml +27 -0
- package/templates/space/.oxlint-base.json +29 -0
- package/templates/space/.oxlintrc.json +78 -0
- package/templates/space/.rulesync/rules/frontend-api-boundary.md +36 -0
- package/templates/space/.rulesync/rules/frontend-component-structure.md +38 -0
- package/templates/space/.rulesync/rules/frontend-forms.md +32 -0
- package/templates/space/.rulesync/rules/frontend-notifications.md +24 -0
- package/templates/space/.rulesync/rules/frontend-povio-components.md +42 -0
- package/templates/space/.rulesync/rules/frontend-povio-ui.md +45 -0
- package/templates/space/.rulesync/rules/frontend-query-autocomplete.md +29 -0
- package/templates/space/.rulesync/rules/frontend-tables-and-lists.md +34 -0
- package/templates/space/.rulesync/rules/frontend-translations.md +26 -0
- package/templates/space/.rulesync/rules/project-overview.md +32 -0
- package/templates/space/.rulesync/rules/robodev-api.md +30 -0
- package/templates/space/.rulesync/rules/robodev-auth.md +40 -0
- package/templates/space/.rulesync/rules/robodev-database.md +22 -0
- package/templates/space/.rulesync/rules/role-based-app-structure.md +36 -0
- package/templates/space/.rulesync/skills/media-feature/SKILL.md +21 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/SKILL.md +208 -0
- package/templates/space/.rulesync/skills/povio-ui-styling/agents/openai.yaml +4 -0
- package/templates/space/.rulesync/skills/robodev-api-route/SKILL.md +13 -0
- package/templates/space/.rulesync/skills/robodev-table/SKILL.md +12 -0
- package/templates/space/README.md +26 -7
- package/templates/space/api/_lib.ts +359 -0
- package/templates/space/api/aliens/labels.ts +19 -0
- package/templates/space/api/files/presigned-url.ts +48 -0
- package/templates/space/api/files/upload.ts +29 -0
- package/templates/space/api/planets/[id]/like.ts +36 -0
- package/templates/space/api/planets/[id].ts +56 -13
- package/templates/space/api/planets/paginate.ts +36 -0
- package/templates/space/api/planets.ts +52 -44
- package/templates/space/apps/fe/index.html +24 -0
- package/templates/space/apps/fe/openapi-codegen.config.ts +37 -0
- package/templates/space/apps/fe/package.json +82 -0
- package/templates/space/apps/fe/public/apple-touch-icon.png +0 -0
- package/templates/space/apps/fe/public/favicon-96x96.png +0 -0
- package/templates/space/apps/fe/public/favicon.ico +0 -0
- package/templates/space/apps/fe/public/favicon.svg +3 -0
- package/templates/space/apps/fe/public/site.webmanifest +21 -0
- package/templates/space/apps/fe/public/web-app-manifest-192x192.png +0 -0
- package/templates/space/apps/fe/public/web-app-manifest-512x512.png +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Bold.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Medium.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Regular.otf +0 -0
- package/templates/space/apps/fe/src/assets/fonts/GeneralSans-Semibold.otf +0 -0
- package/templates/space/apps/fe/src/assets/locales/en/translation.json +301 -0
- package/templates/space/apps/fe/src/assets/locales/sl/translation.json +301 -0
- package/templates/space/apps/fe/src/clients/app-rest-client.ts +15 -0
- package/templates/space/apps/fe/src/clients/auth-token-store.ts +101 -0
- package/templates/space/apps/fe/src/clients/rest/app-error-handler.ts +31 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/authorization-header.interceptor.ts +15 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/refresh-token.interceptor.ts +37 -0
- package/templates/space/apps/fe/src/clients/rest/interceptors/response.interceptor.ts +17 -0
- package/templates/space/apps/fe/src/components/404.tsx +18 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthBrandPanel.tsx +60 -0
- package/templates/space/apps/fe/src/components/features/auth/AuthLayout.tsx +23 -0
- package/templates/space/apps/fe/src/components/features/auth/LoginPage.tsx +111 -0
- package/templates/space/apps/fe/src/components/features/auth/RegisterPage.tsx +143 -0
- package/templates/space/apps/fe/src/components/features/home/HomePage.tsx +21 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetDetailsPage.tsx +139 -0
- package/templates/space/apps/fe/src/components/features/planets/details/PlanetEditPage.tsx +266 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetFeedCard.tsx +131 -0
- package/templates/space/apps/fe/src/components/features/planets/feed/PlanetsFeedPage.tsx +119 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCard.tsx +89 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetCreateModal.tsx +179 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.css +3 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsFilters.tsx +73 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsGridPage.tsx +51 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsInfiniteTablePage.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsPage.tsx +65 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTablePage.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsTableSummary.tsx +29 -0
- package/templates/space/apps/fe/src/components/features/planets/list/PlanetsViewTabs.tsx +79 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTable.tsx +30 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableActions.tsx +64 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/PlanetsTableInfinite.tsx +36 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/planetsTableColumns.tsx +72 -0
- package/templates/space/apps/fe/src/components/features/planets/list/table/usePlanetTableFilters.tsx +102 -0
- package/templates/space/apps/fe/src/components/features/profile/ProfilePage.tsx +96 -0
- package/templates/space/apps/fe/src/components/googleAnalytics/GoogleAnalytics.tsx +35 -0
- package/templates/space/apps/fe/src/components/layout/AppLayout.tsx +19 -0
- package/templates/space/apps/fe/src/components/layout/app-header/AppHeader.tsx +93 -0
- package/templates/space/apps/fe/src/components/layout/app-header/MobileNavigation.tsx +53 -0
- package/templates/space/apps/fe/src/components/layout/app-header/NavLink.tsx +29 -0
- package/templates/space/apps/fe/src/components/shared/branding/BrandLogo.tsx +13 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorFallback.tsx +43 -0
- package/templates/space/apps/fe/src/components/shared/error/ErrorText.tsx +20 -0
- package/templates/space/apps/fe/src/components/shared/error/NotFound.tsx +36 -0
- package/templates/space/apps/fe/src/components/shared/forms/RequiredLabel.tsx +21 -0
- package/templates/space/apps/fe/src/components/shared/forms/RowInputWrapper.tsx +50 -0
- package/templates/space/apps/fe/src/components/shared/head/AppHead.tsx +32 -0
- package/templates/space/apps/fe/src/components/shared/head/DefaultAppHead.tsx +34 -0
- package/templates/space/apps/fe/src/components/shared/layout/LoadingState.tsx +9 -0
- package/templates/space/apps/fe/src/components/shared/layout/ThinPageWrapper.tsx +5 -0
- package/templates/space/apps/fe/src/components/shared/page/PageHeader.tsx +69 -0
- package/templates/space/apps/fe/src/components/shared/ui/Card.tsx +60 -0
- package/templates/space/apps/fe/src/components/shared/ui/GoogleLoginButton.tsx +19 -0
- package/templates/space/apps/fe/src/components/shared/ui/RequiredLabel.tsx +22 -0
- package/templates/space/apps/fe/src/components/shared/ui/TableActions.tsx +22 -0
- package/templates/space/apps/fe/src/config/app.config.ts +35 -0
- package/templates/space/apps/fe/src/config/i18n.ts +43 -0
- package/templates/space/apps/fe/src/config/inits/a11y.ts +14 -0
- package/templates/space/apps/fe/src/config/inits/logger.ts +7 -0
- package/templates/space/apps/fe/src/config/inits/sentry.ts +21 -0
- package/templates/space/apps/fe/src/config/jwt.config.ts +2 -0
- package/templates/space/apps/fe/src/config/query.config.ts +20 -0
- package/templates/space/apps/fe/src/hooks/useAuth.ts +5 -0
- package/templates/space/apps/fe/src/main.tsx +52 -0
- package/templates/space/apps/fe/src/pages/(guest)/login.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/register.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(guest)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(private)/index.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/edit.tsx +53 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/$id/index.tsx +41 -0
- package/templates/space/apps/fe/src/pages/(private)/planets/index.tsx +24 -0
- package/templates/space/apps/fe/src/pages/(private)/planets-feed.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/profile.tsx +23 -0
- package/templates/space/apps/fe/src/pages/(private)/route.tsx +18 -0
- package/templates/space/apps/fe/src/pages/(public)/auth.tsx +37 -0
- package/templates/space/apps/fe/src/pages/(public)/route.tsx +9 -0
- package/templates/space/apps/fe/src/pages/__root.tsx +164 -0
- package/templates/space/apps/fe/src/providers/AppErrorBoundary.tsx +10 -0
- package/templates/space/apps/fe/src/providers/OpenApiRuntimeProvider.tsx +31 -0
- package/templates/space/apps/fe/src/providers/index.tsx +44 -0
- package/templates/space/apps/fe/src/providers/jwt.provider.tsx +95 -0
- package/templates/space/apps/fe/src/routeTree.gen.ts +309 -0
- package/templates/space/apps/fe/src/styles/base.css +103 -0
- package/templates/space/apps/fe/src/styles/fonts/fonts.tsx +10 -0
- package/templates/space/apps/fe/src/styles/fonts/general-sans.css +31 -0
- package/templates/space/apps/fe/src/styles/globals.css +28 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/button.override.ts +536 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/checkbox.override.ts +71 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/input.override.ts +252 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/label.override.ts +91 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/modal.override.ts +57 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/radio.override.ts +46 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/table.override.ts +104 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/tag.override.ts +66 -0
- package/templates/space/apps/fe/src/styles/overrides/defaults/typography.override.ts +115 -0
- package/templates/space/apps/fe/src/styles/overrides/outline.clsx.ts +10 -0
- package/templates/space/apps/fe/src/styles/overrides/uiOverrides.override.ts +60 -0
- package/templates/space/apps/fe/src/styles/theme.css +2177 -0
- package/templates/space/apps/fe/src/types/i18next.d.ts +12 -0
- package/templates/space/apps/fe/src/types/table.d.ts +17 -0
- package/templates/space/apps/fe/src/types/ui.d.ts +26 -0
- package/templates/space/apps/fe/src/types/vite-env.d.ts +20 -0
- package/templates/space/apps/fe/src/utils/date.utils.ts +17 -0
- package/templates/space/apps/fe/src/utils/image-fallback.ts +9 -0
- package/templates/space/apps/fe/src/utils/media-upload.ts +63 -0
- package/templates/space/apps/fe/src/utils/number.utils.ts +12 -0
- package/templates/space/apps/fe/src/utils/string.utils.ts +5 -0
- package/templates/space/apps/fe/src/vite-env.d.ts +1 -0
- package/templates/space/apps/fe/tsconfig.app.json +32 -0
- package/templates/space/apps/fe/tsconfig.json +9 -0
- package/templates/space/apps/fe/tsconfig.node.json +31 -0
- package/templates/space/apps/fe/vite.config.ts +94 -0
- package/templates/space/database.ts +33 -13
- package/templates/space/openapi.json +1819 -0
- package/templates/space/oxfmt.config.js +23 -0
- package/templates/space/package.json +6 -13
- package/templates/space/rulesync.jsonc +18 -0
- package/templates/space/tsconfig.json +2 -4
- package/templates/space/api/health.ts +0 -7
- package/templates/space/api/launch.ts +0 -29
- package/templates/space/api/me.ts +0 -14
- package/templates/space/api/motto.ts +0 -9
- package/templates/space/api/rockets.ts +0 -43
- package/templates/space/index.html +0 -138
- package/templates/space/src/main.tsx +0 -229
- package/templates/space/vite.config.ts +0 -7
package/package.json
CHANGED
|
@@ -45,3 +45,22 @@ test("collectFiles includes hosted sources and skips denied paths", async () =>
|
|
|
45
45
|
await rm(root, { recursive: true, force: true });
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
|
|
49
|
+
test("collectFiles skips nested apps/fe sources", async () => {
|
|
50
|
+
const root = await mkdtemp(join(tmpdir(), "rd-cli-"));
|
|
51
|
+
try {
|
|
52
|
+
await writeFile(join(root, "database.ts"), "export default {}");
|
|
53
|
+
await mkdir(join(root, "apps/fe/src"), { recursive: true });
|
|
54
|
+
await writeFile(join(root, "apps/fe/src/main.tsx"), "export {}");
|
|
55
|
+
await writeFile(join(root, "apps/fe/index.html"), "<html></html>");
|
|
56
|
+
await writeFile(join(root, "apps/fe/package.json"), "{}");
|
|
57
|
+
const files = await collectFiles(root);
|
|
58
|
+
const paths = new Set(files.map((file) => file.path));
|
|
59
|
+
assert.ok(paths.has("database.ts"));
|
|
60
|
+
assert.ok(!paths.has("apps/fe/src/main.tsx"));
|
|
61
|
+
assert.ok(!paths.has("apps/fe/index.html"));
|
|
62
|
+
assert.ok(!paths.has("apps/fe/package.json"));
|
|
63
|
+
} finally {
|
|
64
|
+
await rm(root, { recursive: true, force: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
package/src/collect-files.ts
CHANGED
|
@@ -86,6 +86,7 @@ function isUnsafe(path: string): boolean {
|
|
|
86
86
|
function isDenied(path: string): boolean {
|
|
87
87
|
const top = path.split("/")[0];
|
|
88
88
|
if (SKIP_DIRS.has(top)) return true;
|
|
89
|
+
if (path === "apps" || path.startsWith("apps/")) return true;
|
|
89
90
|
if (path === ".env" || path.startsWith(".env.")) return true;
|
|
90
91
|
const name = basename(path);
|
|
91
92
|
if (name.startsWith(".") && name !== ".gitignore") return true;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { writeViteApiUrl } from "./config.js";
|
|
7
|
+
|
|
8
|
+
test("writeViteApiUrl sets Vite and Povio public API URLs", async () => {
|
|
9
|
+
const root = await mkdtemp(join(tmpdir(), "rd-env-"));
|
|
10
|
+
try {
|
|
11
|
+
await mkdir(join(root, ".config"), { recursive: true });
|
|
12
|
+
await writeFile(
|
|
13
|
+
join(root, ".config/local.spa.template.yml"),
|
|
14
|
+
`APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"\nVITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL\n`,
|
|
15
|
+
);
|
|
16
|
+
await writeViteApiUrl("https://prj.example.test", root);
|
|
17
|
+
const env = await readFile(join(root, ".env"), "utf8");
|
|
18
|
+
assert.match(env, /^VITE_API_URL=https:\/\/prj\.example\.test$/m);
|
|
19
|
+
assert.match(env, /^VITE_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
|
|
20
|
+
assert.match(env, /^APP_PUBLIC_API_URL=https:\/\/prj\.example\.test$/m);
|
|
21
|
+
const spa = await readFile(join(root, ".config/local.spa.yml"), "utf8");
|
|
22
|
+
assert.match(spa, /APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "https:\/\/prj\.example\.test"/);
|
|
23
|
+
} finally {
|
|
24
|
+
await rm(root, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -65,7 +65,17 @@ export async function writeLink(link: ProjectLink, cwd = process.cwd()): Promise
|
|
|
65
65
|
await writeFile(linkPath(cwd), `${JSON.stringify(link, null, 2)}\n`);
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
const PROJECT_API_ENV_KEYS = ["VITE_API_URL", "VITE_PUBLIC_API_URL", "APP_PUBLIC_API_URL"] as const;
|
|
69
|
+
|
|
70
|
+
function upsertEnvKey(content: string, key: string, value: string): string {
|
|
71
|
+
const line = `${key}=${value}`;
|
|
72
|
+
const pattern = new RegExp(`^${key}=.*$`, "m");
|
|
73
|
+
if (pattern.test(content)) return content.replace(pattern, line);
|
|
74
|
+
const trimmed = content.replace(/\s*$/, "");
|
|
75
|
+
return `${trimmed}${trimmed ? "\n" : ""}${line}\n`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Sets Vite / Povio API URLs so starter UIs talk to the deployed project host. */
|
|
69
79
|
export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()): Promise<void> {
|
|
70
80
|
const envPath = join(cwd, ".env");
|
|
71
81
|
let content = "";
|
|
@@ -74,10 +84,29 @@ export async function writeViteApiUrl(projectApiUrl: string, cwd = process.cwd()
|
|
|
74
84
|
} catch {
|
|
75
85
|
content = "";
|
|
76
86
|
}
|
|
77
|
-
|
|
78
|
-
content = content
|
|
79
|
-
} else {
|
|
80
|
-
content = `${content.replace(/\s*$/, "")}${content ? "\n" : ""}VITE_API_URL=${projectApiUrl}\n`;
|
|
87
|
+
for (const key of PROJECT_API_ENV_KEYS) {
|
|
88
|
+
content = upsertEnvKey(content, key, projectApiUrl);
|
|
81
89
|
}
|
|
82
90
|
await writeFile(envPath, content.endsWith("\n") ? content : `${content}\n`);
|
|
91
|
+
await rewriteLocalSpaConfig(projectApiUrl, cwd);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function rewriteLocalSpaConfig(projectApiUrl: string, cwd: string): Promise<void> {
|
|
95
|
+
const configDir = join(cwd, ".config");
|
|
96
|
+
const templatePath = join(configDir, "local.spa.template.yml");
|
|
97
|
+
const destPath = join(configDir, "local.spa.yml");
|
|
98
|
+
let source: string;
|
|
99
|
+
try {
|
|
100
|
+
source = await readFile(templatePath, "utf8");
|
|
101
|
+
} catch {
|
|
102
|
+
try {
|
|
103
|
+
source = await readFile(destPath, "utf8");
|
|
104
|
+
} catch {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const quoted = JSON.stringify(projectApiUrl);
|
|
109
|
+
const next = source.replace(/^(APP_PUBLIC_API_URL:\s*&APP_PUBLIC_API_URL\s+).+$/m, `$1${quoted}`);
|
|
110
|
+
await mkdir(configDir, { recursive: true });
|
|
111
|
+
await writeFile(destPath, next.endsWith("\n") ? next : `${next}\n`);
|
|
83
112
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { collectFiles } from "./collect-files.js";
|
|
7
|
+
import { cliRoot, emitStarter } from "./emit-starter.js";
|
|
8
|
+
|
|
9
|
+
const versions = { robodev: "0.12.0", sdk: "0.12.0", client: "0.12.0" };
|
|
10
|
+
|
|
11
|
+
test("emitStarter rewrites root package.json name only", async () => {
|
|
12
|
+
const dest = await mkdtemp(join(tmpdir(), "rd-emit-"));
|
|
13
|
+
try {
|
|
14
|
+
await emitStarter(join(cliRoot(), "..", "starters", "space"), dest, {
|
|
15
|
+
name: "Orbit",
|
|
16
|
+
packageName: "orbit-app",
|
|
17
|
+
title: "Space",
|
|
18
|
+
versions,
|
|
19
|
+
});
|
|
20
|
+
const rootPkg = JSON.parse(await readFile(join(dest, "package.json"), "utf8")) as {
|
|
21
|
+
name: string;
|
|
22
|
+
};
|
|
23
|
+
const fePkg = JSON.parse(await readFile(join(dest, "apps/fe/package.json"), "utf8")) as {
|
|
24
|
+
name: string;
|
|
25
|
+
dependencies?: Record<string, string>;
|
|
26
|
+
devDependencies?: Record<string, string>;
|
|
27
|
+
};
|
|
28
|
+
assert.equal(rootPkg.name, "orbit-app");
|
|
29
|
+
assert.equal(fePkg.name, "fe");
|
|
30
|
+
assert.equal(fePkg.dependencies?.robodev, undefined);
|
|
31
|
+
assert.equal(fePkg.devDependencies?.robodev, undefined);
|
|
32
|
+
const overview = await readFile(join(dest, ".rulesync/rules/project-overview.md"), "utf8");
|
|
33
|
+
assert.match(overview, /^# Space starter AI rules/m);
|
|
34
|
+
const readme = await readFile(join(dest, "README.md"), "utf8");
|
|
35
|
+
assert.match(readme, /^# Orbit/m);
|
|
36
|
+
} finally {
|
|
37
|
+
await rm(dest, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("collectFiles for space starter is Tiny APIs without apps/fe", async () => {
|
|
42
|
+
const files = await collectFiles(join(cliRoot(), "..", "starters", "space"));
|
|
43
|
+
const paths = new Set(files.map((file) => file.path));
|
|
44
|
+
assert.ok(paths.has("database.ts"));
|
|
45
|
+
assert.ok(paths.has("api/planets.ts"));
|
|
46
|
+
assert.ok(paths.has("api/planets/paginate.ts"));
|
|
47
|
+
assert.ok(paths.has("api/aliens/labels.ts"));
|
|
48
|
+
assert.ok(paths.has("api/files/presigned-url.ts"));
|
|
49
|
+
assert.ok(paths.has("api/files/upload.ts"));
|
|
50
|
+
assert.ok(!paths.has("index.html"));
|
|
51
|
+
assert.ok(!paths.has("src/main.tsx"));
|
|
52
|
+
assert.ok([...paths].every((path) => !path.startsWith("apps/")));
|
|
53
|
+
assert.ok(files.length < 200);
|
|
54
|
+
});
|
package/src/emit-starter.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
1
|
+
import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, extname, join } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
5
|
export type Starter = {
|
|
@@ -23,6 +23,20 @@ export type EmitContext = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
const SKIP_DIRS = new Set(["node_modules", ".robodev", ".git"]);
|
|
26
|
+
const BINARY_COPY_EXTENSIONS = new Set([
|
|
27
|
+
".png",
|
|
28
|
+
".jpg",
|
|
29
|
+
".jpeg",
|
|
30
|
+
".gif",
|
|
31
|
+
".webp",
|
|
32
|
+
".ico",
|
|
33
|
+
".bmp",
|
|
34
|
+
".woff",
|
|
35
|
+
".woff2",
|
|
36
|
+
".ttf",
|
|
37
|
+
".eot",
|
|
38
|
+
".otf",
|
|
39
|
+
]);
|
|
26
40
|
|
|
27
41
|
type DepMap = Record<string, string>;
|
|
28
42
|
|
|
@@ -103,22 +117,35 @@ export async function loadPackageVersions(): Promise<PackageVersions> {
|
|
|
103
117
|
}
|
|
104
118
|
|
|
105
119
|
/** Copies a starter tree and applies package + display rewrites used by create and prepack. */
|
|
106
|
-
export async function emitStarter(
|
|
120
|
+
export async function emitStarter(
|
|
121
|
+
src: string,
|
|
122
|
+
dest: string,
|
|
123
|
+
ctx: EmitContext,
|
|
124
|
+
isRoot = true,
|
|
125
|
+
): Promise<void> {
|
|
107
126
|
await mkdir(dest, { recursive: true });
|
|
108
127
|
const entries = await readdir(src, { withFileTypes: true });
|
|
109
128
|
for (const entry of entries) {
|
|
110
129
|
if (entry.isDirectory()) {
|
|
111
130
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
112
|
-
await emitStarter(join(src, entry.name), join(dest, entry.name), ctx);
|
|
131
|
+
await emitStarter(join(src, entry.name), join(dest, entry.name), ctx, false);
|
|
113
132
|
continue;
|
|
114
133
|
}
|
|
115
134
|
if (!entry.isFile() || entry.name === ".env") continue;
|
|
116
|
-
const
|
|
135
|
+
const srcPath = join(src, entry.name);
|
|
136
|
+
const destPath = join(dest, entry.name);
|
|
137
|
+
if (BINARY_COPY_EXTENSIONS.has(extname(entry.name).toLowerCase())) {
|
|
138
|
+
await copyFile(srcPath, destPath);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const content = await readFile(srcPath, "utf8");
|
|
117
142
|
const next =
|
|
118
143
|
entry.name === "package.json"
|
|
119
|
-
? rewritePackageJson(content, ctx)
|
|
120
|
-
:
|
|
121
|
-
|
|
144
|
+
? rewritePackageJson(content, ctx, isRoot)
|
|
145
|
+
: shouldRewriteDisplay(entry.name, isRoot)
|
|
146
|
+
? rewriteDisplay(content, ctx)
|
|
147
|
+
: content;
|
|
148
|
+
await writeFile(destPath, next);
|
|
122
149
|
}
|
|
123
150
|
}
|
|
124
151
|
|
|
@@ -154,9 +181,14 @@ async function readPackageVersion(path: string, fallback?: string): Promise<stri
|
|
|
154
181
|
throw new Error(`Missing version in ${path}`);
|
|
155
182
|
}
|
|
156
183
|
|
|
157
|
-
function
|
|
184
|
+
function shouldRewriteDisplay(fileName: string, isRoot: boolean): boolean {
|
|
185
|
+
if (!isRoot) return false;
|
|
186
|
+
return fileName === "README.md" || fileName.endsWith(".html");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function rewritePackageJson(raw: string, ctx: EmitContext, isRoot: boolean): string {
|
|
158
190
|
const pkg = JSON.parse(raw) as PackageJson;
|
|
159
|
-
pkg.name = ctx.packageName;
|
|
191
|
+
if (isRoot) pkg.name = ctx.packageName;
|
|
160
192
|
|
|
161
193
|
const pins: Record<string, string> = {
|
|
162
194
|
robodev: `^${ctx.versions.robodev}`,
|
|
@@ -172,9 +204,11 @@ function rewritePackageJson(raw: string, ctx: EmitContext): string {
|
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
const moved = pkg.dependencies?.robodev;
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
207
|
+
if (isRoot) {
|
|
208
|
+
if (pkg.dependencies) delete pkg.dependencies.robodev;
|
|
209
|
+
pkg.devDependencies = pkg.devDependencies ?? {};
|
|
210
|
+
pkg.devDependencies.robodev = pkg.devDependencies.robodev ?? moved ?? pins.robodev;
|
|
211
|
+
}
|
|
178
212
|
|
|
179
213
|
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
180
214
|
}
|
package/src/index.ts
CHANGED
|
@@ -397,6 +397,28 @@ async function npmInstall(root: string): Promise<void> {
|
|
|
397
397
|
});
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
+
async function bunAvailable(): Promise<boolean> {
|
|
401
|
+
try {
|
|
402
|
+
await execFileAsync("bun", ["--version"]);
|
|
403
|
+
return true;
|
|
404
|
+
} catch {
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function printSpaceNextSteps(displayPath: string): void {
|
|
410
|
+
if (displayPath !== ".") {
|
|
411
|
+
console.log(` cd ${displayPath}`);
|
|
412
|
+
}
|
|
413
|
+
console.log(" cd apps/fe");
|
|
414
|
+
console.log(" bun install");
|
|
415
|
+
console.log(" bun openapi:gen");
|
|
416
|
+
console.log(" bun dev");
|
|
417
|
+
console.log(
|
|
418
|
+
" Open the Vite URL (default http://localhost:3000). The project App URL is APIs and Swagger, not this UI.",
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
400
422
|
/** Creates a Starbase project, copies the chosen starter, links, deploys, and installs deps. */
|
|
401
423
|
async function runCreate(args: string[]): Promise<void> {
|
|
402
424
|
const parsed = parseCreateArgs(args);
|
|
@@ -442,6 +464,9 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
442
464
|
"npm install failed. Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.",
|
|
443
465
|
);
|
|
444
466
|
console.log("https://robodev.povio.dev/docs/frontend");
|
|
467
|
+
} else if (starter.id === "space") {
|
|
468
|
+
console.log("Run `npm install` in the project folder.");
|
|
469
|
+
console.log("bun is required for apps/fe. APIs are already deployed.");
|
|
445
470
|
} else {
|
|
446
471
|
console.log("Run `npm install` in the project folder, then `npm run dev`.");
|
|
447
472
|
}
|
|
@@ -451,14 +476,22 @@ async function runCreate(args: string[]): Promise<void> {
|
|
|
451
476
|
const displayPath = relative(process.cwd(), root) || ".";
|
|
452
477
|
console.log("");
|
|
453
478
|
console.log("Next:");
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
479
|
+
if (starter.id === "space") {
|
|
480
|
+
if (!(await bunAvailable())) {
|
|
481
|
+
console.log("bun is required for apps/fe. APIs are already deployed.");
|
|
482
|
+
}
|
|
483
|
+
printSpaceNextSteps(displayPath);
|
|
484
|
+
} else if (starter.kind === "backend") {
|
|
485
|
+
if (displayPath !== ".") {
|
|
486
|
+
console.log(` cd ${displayPath}`);
|
|
487
|
+
}
|
|
458
488
|
console.log(" Copy the API URL printed above into Lovable, Bolt, or v0 as VITE_API_URL.");
|
|
459
489
|
console.log(" Do not run robodev deploy or npm run build inside those tools.");
|
|
460
490
|
console.log(" https://robodev.povio.dev/docs/frontend");
|
|
461
491
|
} else {
|
|
492
|
+
if (displayPath !== ".") {
|
|
493
|
+
console.log(` cd ${displayPath}`);
|
|
494
|
+
}
|
|
462
495
|
console.log(" npm run dev");
|
|
463
496
|
}
|
|
464
497
|
}
|
|
@@ -12,6 +12,6 @@ Local Vite reads `VITE_API_URL` from `.env` (written by `robodev create` / `robo
|
|
|
12
12
|
|
|
13
13
|
Docs: https://robodev.povio.dev/docs/starter
|
|
14
14
|
|
|
15
|
-
`GET /me` and `GET/POST /messages` (`api/me.ts`, `api/messages.ts`) require a Robodev Auth Bearer token. `GET /health` stays public. Email auth works without Google. If Google is not configured on Starbase, the button shows a 503 message. Google OAuth may not complete inside the dashboard preview iframe — use Open app.
|
|
15
|
+
`GET /api/me` and `GET/POST /api/messages` (`api/me.ts`, `api/messages.ts`) require a Robodev Auth Bearer token. `GET /api/health` stays public. Email auth works without Google. If Google is not configured on Starbase, the button shows a 503 message. Google OAuth may not complete inside the dashboard preview iframe — use Open app.
|
|
16
16
|
|
|
17
17
|
Edit files in the dashboard Code page, or here, then Save / `npx robodev deploy`.
|
|
@@ -40,7 +40,7 @@ function App() {
|
|
|
40
40
|
const listRef = useRef<HTMLDivElement>(null);
|
|
41
41
|
|
|
42
42
|
async function refreshMessages() {
|
|
43
|
-
const res = await api.fetch("/messages");
|
|
43
|
+
const res = await api.fetch("/api/messages");
|
|
44
44
|
if (!res.ok) throw new Error(await res.text());
|
|
45
45
|
setMessages((await res.json()) as Message[]);
|
|
46
46
|
}
|
|
@@ -74,7 +74,7 @@ function App() {
|
|
|
74
74
|
}, [messages]);
|
|
75
75
|
|
|
76
76
|
useEffect(() => {
|
|
77
|
-
void fetch(`${apiUrl}/messages`).then((res) => {
|
|
77
|
+
void fetch(`${apiUrl}/api/messages`).then((res) => {
|
|
78
78
|
setUnauthStatus(
|
|
79
79
|
`${res.status} ${res.status === 401 ? "unauthorized (expected)" : res.statusText}`,
|
|
80
80
|
);
|
|
@@ -134,7 +134,7 @@ function App() {
|
|
|
134
134
|
setPending(true);
|
|
135
135
|
setError(null);
|
|
136
136
|
try {
|
|
137
|
-
const res = await api.fetch("/messages", {
|
|
137
|
+
const res = await api.fetch("/api/messages", {
|
|
138
138
|
method: "POST",
|
|
139
139
|
body: JSON.stringify({ body: draft.trim() }),
|
|
140
140
|
});
|
|
@@ -160,10 +160,10 @@ function App() {
|
|
|
160
160
|
return (
|
|
161
161
|
<main className="narrow">
|
|
162
162
|
<p className="eyebrow">Robodev Auth</p>
|
|
163
|
-
<h1>
|
|
163
|
+
<h1>Auth chat</h1>
|
|
164
164
|
<p className="lede">
|
|
165
165
|
Sign in with email or Google. Protected APIs live at {apiUrl}. Unauthenticated GET
|
|
166
|
-
/messages: {unauthStatus ?? "checking…"}.
|
|
166
|
+
/api/messages: {unauthStatus ?? "checking…"}.
|
|
167
167
|
</p>
|
|
168
168
|
{error ? <p className="error">{error}</p> : null}
|
|
169
169
|
|
|
@@ -14,4 +14,4 @@ Do not run `npm run dev` or `npm run build` in this folder. Do not run `robodev`
|
|
|
14
14
|
|
|
15
15
|
Frontend how-to: https://robodev.povio.dev/docs/frontend
|
|
16
16
|
|
|
17
|
-
`GET /me` (`api/me.ts`) requires a Robodev Auth Bearer token. Planet and rocket routes stay public.
|
|
17
|
+
`GET /api/me` (`api/me.ts`) requires a Robodev Auth Bearer token. Planet and rocket routes stay public.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** GET/POST /planets — list (seeds sample rows on first GET) and create planets. */
|
|
1
|
+
/** GET/POST /api/planets — list (seeds sample rows on first GET) and create planets. */
|
|
2
2
|
import { planets, rockets } from "../database";
|
|
3
3
|
import { defineApi, z } from "@robodev-ai/sdk";
|
|
4
4
|
|
package/templates/catalog.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
{
|
|
4
4
|
"id": "space",
|
|
5
5
|
"title": "Space",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Tiny Template planets and aliens APIs plus a local Povio UI in apps/fe"
|
|
7
7
|
},
|
|
8
8
|
{
|
|
9
9
|
"id": "auth-chat",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
{
|
|
20
20
|
"id": "empty",
|
|
21
21
|
"title": "Empty",
|
|
22
|
-
"description": "Blank database.ts and GET /health — no sample schema or UI. For Lovable, Bolt, or v0",
|
|
22
|
+
"description": "Blank database.ts and GET /api/health — no sample schema or UI. For Lovable, Bolt, or v0",
|
|
23
23
|
"kind": "backend"
|
|
24
24
|
}
|
|
25
25
|
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
STAGE: "local"
|
|
2
|
+
OUTPUT: "export"
|
|
3
|
+
|
|
4
|
+
ANALYZE: "false"
|
|
5
|
+
NEXT_TELEMETRY_DISABLED: "1"
|
|
6
|
+
|
|
7
|
+
VITE_PUBLIC_RELEASE: ${env:RELEASE}
|
|
8
|
+
VITE_PUBLIC_STAGE: ${func:stage}
|
|
9
|
+
|
|
10
|
+
APP_PUBLIC_API_URL: &APP_PUBLIC_API_URL "http://localhost:4000"
|
|
11
|
+
APP_PUBLIC_API_MODE: &APP_PUBLIC_API_MODE "real"
|
|
12
|
+
VITE_PUBLIC_API_URL: *APP_PUBLIC_API_URL
|
|
13
|
+
VITE_PUBLIC_API_MODE: *APP_PUBLIC_API_MODE
|
|
14
|
+
|
|
15
|
+
VITE_PUBLIC_LOG_LEVEL: "trace"
|
|
16
|
+
|
|
17
|
+
VITE_PUBLIC_SENTRY_DSN: ""
|
|
18
|
+
VITE_PUBLIC_SENTRY_ENVIRONMENT: ${func:stage}
|
|
19
|
+
VITE_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: "0"
|
|
20
|
+
VITE_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE: "0"
|
|
21
|
+
VITE_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE: "0"
|
|
22
|
+
|
|
23
|
+
VITE_PUBLIC_AUTH_CUSTOM_JWT_ENABLE_MAGIC_LINK: "true"
|
|
24
|
+
|
|
25
|
+
VITE_PUBLIC_GOOGLE_ANALYTICS_MEASUREMENT_ID: ""
|
|
26
|
+
|
|
27
|
+
VITE_DEV_PORT: "3000"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "apps/fe/node_modules/oxlint/configuration_schema.json",
|
|
3
|
+
"plugins": ["eslint", "typescript", "unicorn", "import", "oxc", "promise", "vitest"],
|
|
4
|
+
"categories": {
|
|
5
|
+
"correctness": "error",
|
|
6
|
+
"perf": "error",
|
|
7
|
+
"pedantic": "off",
|
|
8
|
+
"style": "off",
|
|
9
|
+
"restriction": "off",
|
|
10
|
+
"suspicious": "off",
|
|
11
|
+
"nursery": "off"
|
|
12
|
+
},
|
|
13
|
+
"env": {
|
|
14
|
+
"builtin": true,
|
|
15
|
+
"node": true,
|
|
16
|
+
"shared-node-browser": true
|
|
17
|
+
},
|
|
18
|
+
"rules": {
|
|
19
|
+
"eslint/eqeqeq": [
|
|
20
|
+
"error",
|
|
21
|
+
"always",
|
|
22
|
+
{
|
|
23
|
+
"null": "ignore"
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"typescript/no-floating-promises": "off",
|
|
27
|
+
"eslint/capitalized-comments": "off"
|
|
28
|
+
}
|
|
29
|
+
}
|