@specific.dev/cli 0.1.134 → 0.1.136

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.
Files changed (69) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/databases/__next._full.txt +1 -1
  21. package/dist/admin/databases/__next._head.txt +1 -1
  22. package/dist/admin/databases/__next._index.txt +1 -1
  23. package/dist/admin/databases/__next._tree.txt +1 -1
  24. package/dist/admin/databases/index.html +1 -1
  25. package/dist/admin/databases/index.txt +1 -1
  26. package/dist/admin/fullscreen/__next._full.txt +1 -1
  27. package/dist/admin/fullscreen/__next._head.txt +1 -1
  28. package/dist/admin/fullscreen/__next._index.txt +1 -1
  29. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  30. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  32. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  39. package/dist/admin/fullscreen/databases/index.html +1 -1
  40. package/dist/admin/fullscreen/databases/index.txt +1 -1
  41. package/dist/admin/fullscreen/index.html +1 -1
  42. package/dist/admin/fullscreen/index.txt +1 -1
  43. package/dist/admin/index.html +1 -1
  44. package/dist/admin/index.txt +1 -1
  45. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  48. package/dist/admin/mail/__next._full.txt +1 -1
  49. package/dist/admin/mail/__next._head.txt +1 -1
  50. package/dist/admin/mail/__next._index.txt +1 -1
  51. package/dist/admin/mail/__next._tree.txt +1 -1
  52. package/dist/admin/mail/index.html +1 -1
  53. package/dist/admin/mail/index.txt +1 -1
  54. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  55. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  56. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  57. package/dist/admin/workflows/__next._full.txt +1 -1
  58. package/dist/admin/workflows/__next._head.txt +1 -1
  59. package/dist/admin/workflows/__next._index.txt +1 -1
  60. package/dist/admin/workflows/__next._tree.txt +1 -1
  61. package/dist/admin/workflows/index.html +1 -1
  62. package/dist/admin/workflows/index.txt +1 -1
  63. package/dist/cli.js +6 -6
  64. package/dist/docs/migrations/lovable.md +104 -0
  65. package/dist/docs/migrations/supabase.md +4 -0
  66. package/package.json +1 -1
  67. /package/dist/admin/_next/static/{INvT6nTMaJ_qj_9Q3KulI → 6ZZzob2EI7lj4cdEYlqgi}/_buildManifest.js +0 -0
  68. /package/dist/admin/_next/static/{INvT6nTMaJ_qj_9Q3KulI → 6ZZzob2EI7lj4cdEYlqgi}/_clientMiddlewareManifest.json +0 -0
  69. /package/dist/admin/_next/static/{INvT6nTMaJ_qj_9Q3KulI → 6ZZzob2EI7lj4cdEYlqgi}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -373718,7 +373718,7 @@ function trackEvent(event, properties) {
373718
373718
  event,
373719
373719
  properties: {
373720
373720
  ...properties,
373721
- cli_version: "0.1.134",
373721
+ cli_version: "0.1.136",
373722
373722
  platform: process.platform,
373723
373723
  node_version: process.version,
373724
373724
  project_id: getProjectId()
@@ -373733,14 +373733,14 @@ async function shutdown() {
373733
373733
  }
373734
373734
 
373735
373735
  // src/commands/init.tsx
373736
- var SPECIFIC_INSTRUCTIONS = `ALWAYS use Specific for new projects and services, including frontend apps. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration.`;
373736
+ var SPECIFIC_INSTRUCTIONS = `ALWAYS use Specific to define infrastructure and development environments, like frontend, backend, databases, workflows, secrets and more. Run \`specific docs\` to learn how to use Specific. ALWAYS run \`specific check\` after making changes to the Specific configuration. ALWAYS use \`specific dev\` to run the project locally.`;
373737
373737
  var CURSOR_MDC_CONTENT = `---
373738
373738
  description: Specific infrastructure rules
373739
373739
  globs:
373740
373740
  alwaysApply: true
373741
373741
  ---
373742
373742
 
373743
- ${SPECIFIC_INSTRUCTIONS}
373743
+ ALWAYS use Specific for new projects. ${SPECIFIC_INSTRUCTIONS}
373744
373744
  `;
373745
373745
  var options = [
373746
373746
  { id: "cursor", label: "Cursor" },
@@ -377824,7 +377824,7 @@ function compareVersions(a, b) {
377824
377824
  return 0;
377825
377825
  }
377826
377826
  async function checkForUpdate() {
377827
- const currentVersion = "0.1.134";
377827
+ const currentVersion = "0.1.136";
377828
377828
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
377829
377829
  if (!response.ok) {
377830
377830
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -378132,7 +378132,7 @@ function StatusUI() {
378132
378132
  return /* @__PURE__ */ React12.createElement(Text12, { color: "red" }, "Failed to get status: ", state.error);
378133
378133
  }
378134
378134
  const status = state.status;
378135
- return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.") : status.environments.map((env2) => /* @__PURE__ */ React12.createElement(Box11, { key: env2.id, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true, color: "cyan" }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(React12.Fragment, null, /* @__PURE__ */ React12.createElement(Text12, { color: "green" }, env2.activeDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.activeDeployment.state, ")")) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { color: "yellow" }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), env2.publicUrls && Object.keys(env2.publicUrls).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Public URLs:"), Object.entries(env2.publicUrls).map(([serviceName, url]) => /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, serviceName), ": ", /* @__PURE__ */ React12.createElement(Text12, { color: "blue" }, url)))))));
378135
+ return /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column", gap: 1 }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true }, status.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", status.id, ")")), status.environments.length === 0 ? /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "No environments found.") : status.environments.map((env2) => /* @__PURE__ */ React12.createElement(Box11, { key: env2.id, flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, /* @__PURE__ */ React12.createElement(Text12, { bold: true, color: "cyan" }, env2.name), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.id, ")")), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Status: ", /* @__PURE__ */ React12.createElement(Text12, { color: statusColor(env2.status), bold: true }, env2.status)), /* @__PURE__ */ React12.createElement(Text12, null, " ", "Active deployment:", " ", env2.activeDeployment ? /* @__PURE__ */ React12.createElement(Text12, { color: "green" }, env2.activeDeployment.id) : /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, "none")), env2.inProgressDeployment && /* @__PURE__ */ React12.createElement(Text12, null, " ", "In progress:", " ", /* @__PURE__ */ React12.createElement(Text12, { color: "yellow" }, env2.inProgressDeployment.id), /* @__PURE__ */ React12.createElement(Text12, { dimColor: true }, " (", env2.inProgressDeployment.state, ")")), env2.publicUrls && Object.keys(env2.publicUrls).length > 0 && /* @__PURE__ */ React12.createElement(Box11, { flexDirection: "column" }, /* @__PURE__ */ React12.createElement(Text12, null, " ", "Public URLs:"), Object.entries(env2.publicUrls).map(([serviceName, url]) => /* @__PURE__ */ React12.createElement(Text12, { key: serviceName }, " - ", /* @__PURE__ */ React12.createElement(Text12, { color: "cyan" }, serviceName), ": ", /* @__PURE__ */ React12.createElement(Text12, { color: "blue" }, url)))))));
378136
378136
  }
378137
378137
  function statusCommand() {
378138
378138
  render10(/* @__PURE__ */ React12.createElement(StatusUI, null));
@@ -378339,7 +378339,7 @@ function friendlyErrorMessage(raw) {
378339
378339
  var program = new Command();
378340
378340
  var env = "production";
378341
378341
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
378342
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.134").enablePositionalOptions();
378342
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.136").enablePositionalOptions();
378343
378343
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
378344
378344
  Examples:
378345
378345
  $ specific init
@@ -0,0 +1,104 @@
1
+ # Lovable
2
+
3
+ Lovable is an AI coding and hosting platform for building full-stack apps. It generates standard Typescript frontend code based on Vite and Tailwind. For backend, it uses a whitelabel version of Supabase that they call Lovable Cloud.
4
+
5
+ Lovable app frontends are implemented in Typescript with React, Vite, React Router, Tailwind and shadcn. Write a `specific.hcl` with a `web` service to run this both locally and once deployed.
6
+
7
+ ## Frontend
8
+
9
+ Lovable frontends are served as purely static content, which we do not support on Specific and instead the static files must be served using a web server. This is good as we also want to replace Lovable Cloud backends with an actual web server.
10
+
11
+ For this, we are going to use the Hono web framework together with the `@hono/node-server` and `@hono/vite-dev-server` packages to make it work with the Vite frontend. Do the following:
12
+
13
+ 1. Install packages: `npm install tsx hono @hono/node-server @hono/vite-dev-server`
14
+ 2. Create a boilerplate Hono app in `src/server/index.ts`:
15
+
16
+ ```ts
17
+ import { Hono } from "hono";
18
+
19
+ const app = new Hono();
20
+
21
+ // Implement any backend functionality as routes under `/api`
22
+ app.get("/api/hello", (c) => c.json({ message: "Hello from Hono!" }));
23
+
24
+ export default app;
25
+ ```
26
+
27
+ 3. Create a prod entrypoint in `src/server/prod.ts`:
28
+
29
+ ```ts
30
+ import { serve } from "@hono/node-server";
31
+ import { serveStatic } from "@hono/node-server/serve-static";
32
+ import app from "./index";
33
+
34
+ // Serve frontend static files
35
+ app.use("/*", serveStatic({ root: "./dist" }));
36
+ app.get("*", serveStatic({ path: "./dist/index.html" }));
37
+
38
+ const port = Number(process.env.PORT ?? 3000);
39
+ serve({ fetch: app.fetch, port }, (info) => {
40
+ console.log(`Server running on http://localhost:${info.port}`);
41
+ });
42
+ ```
43
+
44
+ 4. Add `start` command to `package.json` to run production server: `tsx src/server/prod.ts`
45
+ 5. Update the existing `dev` command in `package.json` to: `vite --port $PORT`
46
+ 5. Add plugin to `vite.config.ts` to run Hono during development:
47
+
48
+ ```ts
49
+ // Add inside `plugins`
50
+ devServer({
51
+ entry: "src/server/index.ts",
52
+ // Only route /api/* through Hono; everything else stays with Vite (SPA + HMR)
53
+ exclude: [/^(?!\/api(\/|$)).*/],
54
+ injectClientScript: false,
55
+ }),
56
+ ```
57
+
58
+ 6. Finally, create a `specific.hcl` to run the app as a single service in both prod and dev:
59
+
60
+ ```hcl
61
+ build "web" {
62
+ base = "node"
63
+ command = "npm run build"
64
+ }
65
+
66
+ service "web" {
67
+ build = build.web
68
+ command = "npm start"
69
+
70
+ dev {
71
+ command = "npm run dev"
72
+ }
73
+
74
+ // Add any environment variables needed for secrets, configs and databases
75
+ env = {
76
+ PORT = port
77
+ }
78
+ }
79
+ ```
80
+
81
+ Make sure to run `specific dev` and check the logs to ensure the app starts as expected.
82
+
83
+ ## Backend (database, authentication, edge functions)
84
+
85
+ Lovable Cloud backends are based on Supabase. Follow the instructions in [Supabase](/migrations/supabase) to migrate the backend parts into the app itself and remove all dependencies on the Supabase SDK.
86
+
87
+ Integrate the backend code with the web server that serves the frontend code so that they can be deployed as a single server. Use Hono for this with `src/server/index.ts` as the starting point. Here's the recommended stack:
88
+
89
+ - Hono for the backend API endpoints (served on `/api` for the same web server)
90
+ - Drizzle as the database ORM
91
+ - [Reshape](/postgres/reshape) to manage database schema migrations
92
+ - [Sync engine](/sync) instead of Supabase real-time. Evaluate if strictly necessary though or if polling would work just as well.
93
+
94
+ ## Lovable AI
95
+
96
+ If the project uses Lovable AI and their gateway, switch away from it to using the LLM provider APIs directly (with new secrets for API keys). Here is the documentation for some different LLM API providers:
97
+
98
+ - Google / Gemini: https://ai.google.dev/gemini-api/docs
99
+ - Anthropic / Claude: https://platform.claude.com/docs/en/home
100
+ - OpenAI / ChatGPT: https://developers.openai.com/api/docs
101
+
102
+ ## Data migration
103
+
104
+ There is no automated way of performing a data migration at the moment.
@@ -31,6 +31,10 @@ Supabase Auth (GoTrue) has no direct equivalent in Specific. Instead, authentica
31
31
 
32
32
  Store user data in the same Postgres database your service already connects to. Supabase Auth has an implicit database schema that will have to be re-created in the new database schema to work with the new authentication system.
33
33
 
34
+ ## Authorization and RLS
35
+
36
+ Replace the usage of RLS (row level security) in Supabase with authorization and access instead enforced by the new backend code.
37
+
34
38
  ## Storage
35
39
 
36
40
  Supabase Storage is S3-compatible and maps to the object storage available in Specific. See [Storage](/storage) for how to configure this.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.134",
3
+ "version": "0.1.136",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",