create-smoodly-app 0.0.3 → 0.0.4

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 CHANGED
@@ -15,7 +15,10 @@ sign in. Spec: `docs/superpowers/specs/2026-09-04-create-smoodly-app-design.md`.
15
15
  next free block, `553xx` and so on, and says so) or a
16
16
  **hosted project** (URL, publishable key, secret key; then an offer to
17
17
  `supabase link --project-ref <ref>` + `supabase db push`, the ref taken
18
- from the URL so the CLI never opens its project picker).
18
+ from the URL so the CLI never opens its project picker). The URL may be
19
+ pasted without `https://`; anything that is not an http(s) URL is refused
20
+ before any step runs. A custom domain has no ref, so the link is not
21
+ offered and the two commands are printed instead.
19
22
  3. The first editor: pick an existing Auth user or create one (email +
20
23
  password). The `editors` row is written with role `owner`.
21
24
  If `@` does not appear when you type it (macOS, `@` on Option+2 with the
@@ -84,7 +87,13 @@ The CLI has no runtime dependency on `smoodly`.
84
87
  project sorts first (seen 2026-09-05 during the hosted run, caught before
85
88
  the push). Fixed 2026-09-05: `projectRef` (src/env.ts) takes the ref from
86
89
  the typed URL's first host label and the link step and every fallback
87
- line pass it; a custom domain yields no ref and falls back to the picker.
90
+ line pass it. Seen again 2026-09-05 from the published 0.0.3: a URL typed
91
+ without `https://` gave no ref (the picker opened) and then failed
92
+ `ensureEditor` with supabase-js's "Invalid supabaseUrl". Fixed the same
93
+ day: `normalizeProjectUrl` (src/env.ts) adds the scheme and refuses
94
+ non-URLs at the prompt and for `--url`; a URL with no ref (custom domain)
95
+ never offers the link, and `run` throws rather than run a bare
96
+ `supabase link`.
88
97
  - The `--yes` flag defaults to local mode; with the Supabase CLI absent that
89
98
  stops with the install message rather than falling through to hosted.
90
99
  - Captured `exec` output is stdout only; stderr streams straight through, so
package/dist/index.js CHANGED
@@ -135,6 +135,41 @@ async function isPortFree(port) {
135
135
  import * as p from "@clack/prompts";
136
136
  import { resolve } from "node:path";
137
137
 
138
+ // src/env.ts
139
+ function renderEnv(keys) {
140
+ return [
141
+ "# Smoodly, self-host: the content project's values. SMOODLY_AUTH_URL and",
142
+ "# SMOODLY_AUTH_KEY default to these two \u2014 leave them unset.",
143
+ `SMOODLY_URL=${keys.url}`,
144
+ `SMOODLY_SECRET_KEY=${keys.secretKey}`,
145
+ `SMOODLY_PUBLISHABLE_KEY=${keys.publishableKey}`,
146
+ ""
147
+ ].join("\n");
148
+ }
149
+ function normalizeProjectUrl(input) {
150
+ const raw = input.trim();
151
+ if (!raw) return null;
152
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
153
+ try {
154
+ const url = new URL(withScheme);
155
+ if (url.protocol !== "http:" && url.protocol !== "https:") return null;
156
+ if (!url.hostname) return null;
157
+ return url.origin;
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ function projectRef(url) {
163
+ let host;
164
+ try {
165
+ host = new URL(url).hostname;
166
+ } catch {
167
+ return null;
168
+ }
169
+ const m = /^([a-z0-9]{20})\.supabase\.(co|in|red)$/.exec(host);
170
+ return m ? m[1] : null;
171
+ }
172
+
138
173
  // src/scaffold.ts
139
174
  import { cp, readdir, readFile, rename, writeFile } from "node:fs/promises";
140
175
  import { join } from "node:path";
@@ -197,10 +232,23 @@ async function resolveAnswers(flags2, ctx) {
197
232
  if (!ctx.hasSupabaseCli) bail("Local mode needs the Supabase CLI: https://supabase.com/docs/guides/cli (brew install supabase/tap/supabase)");
198
233
  return { name, dir, supabase, hasSupabaseCli: true, smoodlyDep: flags2.smoodlyDep };
199
234
  }
200
- const url = flags2.url ?? await answer(p.text({ message: "Supabase project URL", placeholder: "https://abcd.supabase.co" }));
235
+ const badUrl = "Supabase project URL must be an http(s) URL, like https://abcd.supabase.co";
236
+ const typedUrl = flags2.url ?? await answer(
237
+ p.text({
238
+ message: "Supabase project URL",
239
+ placeholder: "https://abcd.supabase.co",
240
+ validate: (v) => normalizeProjectUrl(v ?? "") ? void 0 : badUrl
241
+ })
242
+ );
243
+ const url = normalizeProjectUrl(typedUrl);
244
+ if (!url) throw new Error(`${badUrl} (got "${typedUrl}")`);
201
245
  const publishableKey = flags2.publishableKey ?? await answer(p.text({ message: "Publishable (anon) key" }));
202
246
  const secretKey = flags2.secretKey ?? await answer(p.password({ message: "Secret (service role) key" }));
203
- const linkAndPush = !ctx.hasSupabaseCli ? false : flags2.skipLink ? false : flags2.yes ? true : await answer(p.confirm({ message: "Link this app to the project and push the migration now? (supabase link, supabase db push)" }));
247
+ const ref = projectRef(url);
248
+ if (ctx.hasSupabaseCli && !flags2.skipLink && !ref) {
249
+ p.log.warn(`No project ref in ${url}; skipping supabase link. Run \`supabase link --project-ref <project-ref>\` and \`supabase db push\` yourself.`);
250
+ }
251
+ const linkAndPush = !ctx.hasSupabaseCli ? false : flags2.skipLink || !ref ? false : flags2.yes ? true : await answer(p.confirm({ message: `Link this app to the project and push the migration now? (supabase link --project-ref ${ref}, supabase db push)` }));
204
252
  return { name, dir, supabase, hasSupabaseCli: ctx.hasSupabaseCli, hosted: { keys: { url, publishableKey, secretKey }, linkAndPush }, smoodlyDep: flags2.smoodlyDep };
205
253
  }
206
254
  function askEditorWith(flags2, env = process.env) {
@@ -233,28 +281,6 @@ function askEditorWith(flags2, env = process.env) {
233
281
  // src/run.ts
234
282
  import { join as join2 } from "node:path";
235
283
 
236
- // src/env.ts
237
- function renderEnv(keys) {
238
- return [
239
- "# Smoodly, self-host: the content project's values. SMOODLY_AUTH_URL and",
240
- "# SMOODLY_AUTH_KEY default to these two \u2014 leave them unset.",
241
- `SMOODLY_URL=${keys.url}`,
242
- `SMOODLY_SECRET_KEY=${keys.secretKey}`,
243
- `SMOODLY_PUBLISHABLE_KEY=${keys.publishableKey}`,
244
- ""
245
- ].join("\n");
246
- }
247
- function projectRef(url) {
248
- let host;
249
- try {
250
- host = new URL(url).hostname;
251
- } catch {
252
- return null;
253
- }
254
- const m = /^([a-z0-9]{20})\.supabase\.(co|in|red)$/.exec(host);
255
- return m ? m[1] : null;
256
- }
257
-
258
284
  // src/plan.ts
259
285
  function plan(a) {
260
286
  if (a.supabase === "local" && !a.hasSupabaseCli) {
@@ -415,11 +441,14 @@ ${renderEnv(keys).split("\n").filter((l) => !l.startsWith("#")).join("\n").trimE
415
441
  }
416
442
  }
417
443
  function linkArgs(answers2) {
418
- const ref = answers2.hosted ? projectRef(answers2.hosted.keys.url) : null;
419
- return ref ? ["link", "--project-ref", ref] : ["link"];
444
+ const url = answers2.hosted?.keys.url ?? "";
445
+ const ref = projectRef(url);
446
+ if (!ref) throw new Error(`no project ref in ${url}; run \`supabase link --project-ref <project-ref>\` yourself`);
447
+ return ["link", "--project-ref", ref];
420
448
  }
421
449
  function linkCommand(answers2) {
422
- return `supabase ${linkArgs(answers2).join(" ")}`;
450
+ const ref = answers2.hosted ? projectRef(answers2.hosted.keys.url) : null;
451
+ return `supabase link --project-ref ${ref ?? "<project-ref>"}`;
423
452
  }
424
453
 
425
454
  // src/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-smoodly-app",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "Create a Smoodly site: a fresh Next.js app with the admin mounted, a Supabase schema, an env file and a first editor.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,7 +13,7 @@
13
13
  "react": "^19",
14
14
  "react-dom": "^19",
15
15
  "@supabase/supabase-js": "^2.113.0",
16
- "smoodly": "0.0.3"
16
+ "smoodly": "0.0.4"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^26.4.1",