@specific.dev/cli 0.1.39 → 0.1.41

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 (35) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.__PAGE__.txt +1 -1
  4. package/dist/admin/__next._full.txt +1 -1
  5. package/dist/admin/__next._head.txt +1 -1
  6. package/dist/admin/__next._index.txt +1 -1
  7. package/dist/admin/__next._tree.txt +1 -1
  8. package/dist/admin/_next/static/chunks/{4b5ae5ebb2087f0d.js → 9054c84ba21a4c14.js} +1 -1
  9. package/dist/admin/_next/static/chunks/{0afa5e999b6c353a.js → dde2c8e6322d1671.js} +1 -1
  10. package/dist/admin/_next/static/chunks/{turbopack-e5185af8e7c716ca.js → turbopack-22b7312525502d51.js} +1 -1
  11. package/dist/admin/_not-found/__next._full.txt +1 -1
  12. package/dist/admin/_not-found/__next._head.txt +1 -1
  13. package/dist/admin/_not-found/__next._index.txt +1 -1
  14. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  15. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  16. package/dist/admin/_not-found/__next._tree.txt +1 -1
  17. package/dist/admin/_not-found/index.html +1 -1
  18. package/dist/admin/_not-found/index.txt +1 -1
  19. package/dist/admin/databases/__next._full.txt +1 -1
  20. package/dist/admin/databases/__next._head.txt +1 -1
  21. package/dist/admin/databases/__next._index.txt +1 -1
  22. package/dist/admin/databases/__next._tree.txt +1 -1
  23. package/dist/admin/databases/__next.databases.__PAGE__.txt +1 -1
  24. package/dist/admin/databases/__next.databases.txt +1 -1
  25. package/dist/admin/databases/index.html +1 -1
  26. package/dist/admin/databases/index.txt +1 -1
  27. package/dist/admin/index.html +1 -1
  28. package/dist/admin/index.txt +1 -1
  29. package/dist/cli.js +30 -41
  30. package/dist/docs/index.md +12 -0
  31. package/dist/docs/migrations/supabase.md +58 -0
  32. package/package.json +2 -2
  33. /package/dist/admin/_next/static/{cNL40bpv360RCjyOls7vD → 9wAYbYI1Gg_jzlXqEZ1W5}/_buildManifest.js +0 -0
  34. /package/dist/admin/_next/static/{cNL40bpv360RCjyOls7vD → 9wAYbYI1Gg_jzlXqEZ1W5}/_clientMiddlewareManifest.json +0 -0
  35. /package/dist/admin/_next/static/{cNL40bpv360RCjyOls7vD → 9wAYbYI1Gg_jzlXqEZ1W5}/_ssgManifest.js +0 -0
@@ -11,7 +11,7 @@ f:I[68027,[],"default"]
11
11
  :HL["/_next/static/chunks/5ff94899b8b7a03a.css","style"]
12
12
  :HL["/_next/static/media/797e433ab948586e-s.p.dbea232f.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
13
13
  :HL["/_next/static/media/caa3a2e1cccd8315-s.p.853070df.woff2","font",{"crossOrigin":"","type":"font/woff2"}]
14
- 0:{"P":null,"b":"cNL40bpv360RCjyOls7vD","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/5ff94899b8b7a03a.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable antialiased","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@6","$@7"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/ff1a16fafef87110.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/6aae09c9429b9f24.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/_next/static/chunks/0476153b08658d87.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$","$9",null,{"name":"Next.MetadataOutlet","children":"$@a"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lb",null,{"children":"$Lc"}],["$","div",null,{"hidden":true,"children":["$","$Ld",null,{"children":["$","$9",null,{"name":"Next.Metadata","children":"$Le"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$f",[]],"S":true}
14
+ 0:{"P":null,"b":"9wAYbYI1Gg_jzlXqEZ1W5","c":["",""],"q":"","i":false,"f":[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/chunks/5ff94899b8b7a03a.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"geist_a71539c9-module__T19VSG__variable geist_mono_8d43a2aa-module__8Li5zG__variable antialiased","children":["$","$L2",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L3",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]]}],{"children":[["$","$1","c",{"children":[["$","$L4",null,{"Component":"$5","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@6","$@7"]}}],[["$","script","script-0",{"src":"/_next/static/chunks/ff1a16fafef87110.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/_next/static/chunks/6aae09c9429b9f24.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/_next/static/chunks/0476153b08658d87.js","async":true,"nonce":"$undefined"}]],["$","$L8",null,{"children":["$","$9",null,{"name":"Next.MetadataOutlet","children":"$@a"}]}]]}],{},null,false,false]},null,false,false],["$","$1","h",{"children":[null,["$","$Lb",null,{"children":"$Lc"}],["$","div",null,{"hidden":true,"children":["$","$Ld",null,{"children":["$","$9",null,{"name":"Next.Metadata","children":"$Le"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],false]],"m":"$undefined","G":["$f",[]],"S":true}
15
15
  6:{}
16
16
  7:"$0:f:0:1:1:children:0:props:children:0:props:serverProvidedParams:params"
17
17
  c:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]
package/dist/cli.js CHANGED
@@ -182491,7 +182491,7 @@ function trackEvent(event, properties) {
182491
182491
  event,
182492
182492
  properties: {
182493
182493
  ...properties,
182494
- cli_version: "0.1.39",
182494
+ cli_version: "0.1.41",
182495
182495
  platform: process.platform,
182496
182496
  node_version: process.version,
182497
182497
  project_id: getProjectId()
@@ -190217,53 +190217,42 @@ function DeployUI({ environment, config, skipBuildTest }) {
190217
190217
  useEffect5(() => {
190218
190218
  if (state.phase !== "testing-builds" || !state.projectId) return;
190219
190219
  let cancelled = false;
190220
- async function runBuildTests() {
190220
+ let pollInterval;
190221
+ async function runBuildTestsAndDeploy() {
190221
190222
  const projectDir = process.cwd();
190222
190223
  const builds = config.builds || [];
190223
- if (skipBuildTest || builds.length === 0) {
190224
- writeLog("deploy", skipBuildTest ? "Skipping build tests (--skip-build-test)" : "No builds to test");
190225
- setState((s) => ({ ...s, phase: "creating-tarball" }));
190226
- return;
190227
- }
190228
- writeLog("deploy", `Testing ${builds.length} build(s) locally`);
190229
- try {
190230
- const results = await testAllBuilds(builds, projectDir);
190231
- if (cancelled) return;
190232
- if (!results.allPassed) {
190233
- const failures = results.results.filter((r) => !r.success);
190234
- const errorMsg = failures.map((f) => `Build "${f.buildName}" failed:
190224
+ if (!skipBuildTest && builds.length > 0) {
190225
+ writeLog("deploy", `Testing ${builds.length} build(s) locally`);
190226
+ try {
190227
+ const results = await testAllBuilds(builds, projectDir);
190228
+ if (cancelled) return;
190229
+ if (!results.allPassed) {
190230
+ const failures = results.results.filter((r) => !r.success);
190231
+ const errorMsg = failures.map((f) => `Build "${f.buildName}" failed:
190235
190232
  ${f.output}`).join("\n\n");
190233
+ writeLog("deploy:error", errorMsg);
190234
+ setState({
190235
+ phase: "error",
190236
+ error: `Build test failed:
190237
+ ${errorMsg}`
190238
+ });
190239
+ return;
190240
+ }
190241
+ writeLog("deploy", "All builds passed local testing");
190242
+ } catch (err) {
190243
+ if (cancelled) return;
190244
+ const errorMsg = `Build test failed: ${err instanceof Error ? err.message : String(err)}`;
190236
190245
  writeLog("deploy:error", errorMsg);
190237
190246
  setState({
190238
190247
  phase: "error",
190239
- error: `Build test failed:
190240
- ${errorMsg}`
190248
+ error: errorMsg
190241
190249
  });
190242
190250
  return;
190243
190251
  }
190244
- writeLog("deploy", "All builds passed local testing");
190245
- setState((s) => ({ ...s, phase: "creating-tarball" }));
190246
- } catch (err) {
190247
- if (cancelled) return;
190248
- const errorMsg = `Build test failed: ${err instanceof Error ? err.message : String(err)}`;
190249
- writeLog("deploy:error", errorMsg);
190250
- setState({
190251
- phase: "error",
190252
- error: errorMsg
190253
- });
190252
+ } else {
190253
+ writeLog("deploy", skipBuildTest ? "Skipping build tests (--skip-build-test)" : "No builds to test");
190254
190254
  }
190255
- }
190256
- runBuildTests();
190257
- return () => {
190258
- cancelled = true;
190259
- };
190260
- }, [state.phase, state.projectId, config.builds, skipBuildTest]);
190261
- useEffect5(() => {
190262
- if (state.phase !== "creating-tarball" || !state.projectId) return;
190263
- let cancelled = false;
190264
- let pollInterval;
190265
- async function deploy() {
190266
- const projectDir = process.cwd();
190255
+ setState((s) => ({ ...s, phase: "creating-tarball" }));
190267
190256
  writeLog("deploy", `Starting deployment to "${environment}"`);
190268
190257
  writeLog("deploy", `Project directory: ${projectDir}`);
190269
190258
  const authToken = await getValidAccessToken();
@@ -190404,12 +190393,12 @@ ${errorMsg}`
190404
190393
  pollInterval = setInterval(pollForCompletion, 2e3);
190405
190394
  pollForCompletion();
190406
190395
  }
190407
- deploy();
190396
+ runBuildTestsAndDeploy();
190408
190397
  return () => {
190409
190398
  cancelled = true;
190410
190399
  if (pollInterval) clearInterval(pollInterval);
190411
190400
  };
190412
- }, [state.projectId, environment]);
190401
+ }, [state.projectId, environment, config.builds, skipBuildTest]);
190413
190402
  useEffect5(() => {
190414
190403
  let pollInterval;
190415
190404
  if ((state.phase === "building" || state.phase === "deploying") && state.deployment && state.missingSecrets === void 0 && state.secretValues === void 0 && state.missingConfigs === void 0 && state.configValues === void 0) {
@@ -191291,7 +191280,7 @@ function logoutCommand() {
191291
191280
  var program = new Command();
191292
191281
  var env = "production";
191293
191282
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
191294
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.39").enablePositionalOptions();
191283
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.41").enablePositionalOptions();
191295
191284
  program.command("init").description("Initialize project for use with a coding agent").action(initCommand);
191296
191285
  program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
191297
191286
  program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
@@ -28,3 +28,15 @@ The following is a list of common frameworks and libraries with guidance on how
28
28
  - [Next.js](/integrations/nextjs): full-stack React framework
29
29
  - [Drizzle ORM](/integrations/drizzle): TypeScript ORM with type safety
30
30
  - [Prisma](/integrations/prisma): TypeScript ORM with auto-generated client
31
+
32
+ ## Adding Specific to existing projects / migrating to Specific
33
+
34
+ When adding Specific to an existing codebase, explore the codebase in detail to figure out what infrastructure components is needed. In particular:
35
+
36
+ - Find ALL secrets and config variables, make sure they are read from environment variables and define them in `specific.hcl`. If a `.env` file exists, guide the user to move that into local Specific configuration instead.
37
+
38
+ ### Common migrations
39
+
40
+ If the codebase uses any of the following technologies, read the migration guides linked:
41
+
42
+ - [Supabase](/migrations/supabase)
@@ -0,0 +1,58 @@
1
+ # Supabase
2
+
3
+ Supabase is a backend-as-a-service and framework built around Postgres. It automatically generates a backend API based on the Postgres schema which can be connected to from the frontend. To migrate to Specific, a backend service must be built to replicate the API that Supabase generates automatically, and also implement any edge functions.
4
+
5
+ Pick a backend stack together with the user (programming language/framework) to be used to replace Supabase.
6
+
7
+ ## Database
8
+
9
+ Both Supabase and Specific use Postgres, so the schema transfers directly. Determine the schema from the Supabase codebase or export the existing schema from Supabase:
10
+
11
+ ```
12
+ supabase db dump --schema public > schema.sql
13
+ ```
14
+
15
+ Configure a [Postgres](/postgres) database through Specific connected to the backend and set up the schema and database migrations in the stack chosen for the project.
16
+
17
+ ## API / Backend Service / Edge Functions
18
+
19
+ Supabase auto-generates a PostgREST API from your database schema. With Specific, you write an explicit backend service that handles routing, request validation, and business logic. This gives you full control over your API surface and means we need to re-implement the routes that Supabase provides derived from the database.
20
+
21
+ Read the frontend code to determine what endpoints are needed and implement them on the backend. Keep in mind that Supabase uses RLS for authorization, which doesn't translate well without Supabase Authentication present. Instead, read the RLS policies and mirror the logic into the new backend service instead.
22
+
23
+ Supabase supports writing custom code for the backend using what they call Edge Functions. Transfer these over to be part of the new backend service instead and deployed with the rest of the API.
24
+
25
+ ## Authentication
26
+
27
+ Supabase Auth (GoTrue) has no direct equivalent in Specific. Instead, authentication should be integrated in whatever way suits the stack chosen for the project, for example:
28
+
29
+ - Authentication library. For Typescript/Javascript stacks, we recommend [BetterAuth](https://www.better-auth.com/llms.txt)
30
+ - External auth provider (Auth0, Clerk, WorkOS, etc.)
31
+
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
+
34
+ ## Storage
35
+
36
+ Supabase Storage is S3-compatible and maps to the object storage available in Specific. See [Storage](/storage) for how to configure this.
37
+
38
+ ## Realtime
39
+
40
+ Supabase Realtime can be replace with the sync engine available in Specific. See [sync](/sync) for configuration and implementation details.
41
+
42
+ ## Queues
43
+
44
+ There is currently no equivalent to Supabase Queues available in Specific. It will be coming soon. Tell the user to reach out to fabian@specific.dev if you have a need for this.
45
+
46
+ ## Cron
47
+
48
+ There is currently no equivalent to Supabase Cron available in Specific. It will be coming soon. Tell the user to reach out to fabian@specific.dev if you have a need for this.
49
+
50
+ ## Client-side Changes
51
+
52
+ - Remove the `@supabase/supabase-js` dependency
53
+ - Replace all Supabase client calls with standard `fetch` requests to your backend API service
54
+ - Pass auth tokens via request headers (e.g., `Authorization: Bearer <token>`) to the backend service for authenticated endpoints
55
+
56
+ ## Data migration
57
+
58
+ Data migration out of Supabase Postgres and Storage (S3) into Specific is currently a manual process. Help guide the user through this if they want it.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.39",
3
+ "version": "0.1.41",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",
@@ -52,4 +52,4 @@
52
52
  "@types/react": "^19.2.7",
53
53
  "esbuild": "^0.24.0"
54
54
  }
55
- }
55
+ }