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 +11 -2
- package/dist/index.js +56 -27
- package/package.json +1 -1
- package/templates/next/package.json +1 -1
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
|
|
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
|
|
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
|
|
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
|
|
419
|
-
|
|
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
|
-
|
|
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