create-smoodly-app 0.0.50 → 0.0.51
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 +3 -0
- package/dist/index.js +28 -10
- package/package.json +1 -1
- package/templates/astro-blank/package.json +1 -1
- package/templates/astro-blank-intl/package.json +1 -1
- package/templates/astro-lorem/package.json +1 -1
- package/templates/astro-lorem-intl/package.json +1 -1
- package/templates/blank/package.json +1 -1
- package/templates/blank/smoodly/server.ts +1 -1
- package/templates/blank-intl/package.json +1 -1
- package/templates/blank-intl/smoodly/server.ts +1 -1
- package/templates/lorem/package.json +1 -1
- package/templates/lorem/smoodly/server.ts +1 -1
- package/templates/lorem-intl/package.json +1 -1
- package/templates/lorem-intl/smoodly/server.ts +1 -1
package/README.md
CHANGED
|
@@ -44,5 +44,8 @@ written, and a first editor who can sign in.
|
|
|
44
44
|
Every prompt has a flag, so CI can run it: `--framework astro|next`, `--supabase local|hosted`, `--locales`,
|
|
45
45
|
`--url`, `--publishable-key`, `--secret-key`, `--editor-email` (password from
|
|
46
46
|
`SMOODLY_EDITOR_PASSWORD`), `--skip-link`, `--yes`. `--help` lists them.
|
|
47
|
+
`--cloud` takes a Smoodly Cloud space's three values as a hosted project and
|
|
48
|
+
skips `supabase init`, link, `db push` and the first editor: the shared
|
|
49
|
+
project is migrated by Smoodly Cloud and the space's owner is its admin.
|
|
47
50
|
A hidden `--smoodly-dep <spec>` overrides the `smoodly` dependency written into
|
|
48
51
|
`package.json`; CI points it at the packed tarball.
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,7 @@ function parseFlags(argv) {
|
|
|
34
34
|
starter: { type: "string" },
|
|
35
35
|
framework: { type: "string" },
|
|
36
36
|
"skip-link": { type: "boolean", default: false },
|
|
37
|
+
cloud: { type: "boolean", default: false },
|
|
37
38
|
yes: { type: "boolean", short: "y", default: false },
|
|
38
39
|
help: { type: "boolean", short: "h", default: false }
|
|
39
40
|
}
|
|
@@ -43,6 +44,9 @@ function parseFlags(argv) {
|
|
|
43
44
|
if (supabase !== void 0 && supabase !== "local" && supabase !== "hosted") {
|
|
44
45
|
throw new Error(`--supabase must be "local" or "hosted", got "${supabase}"`);
|
|
45
46
|
}
|
|
47
|
+
const cloud = values.cloud ?? false;
|
|
48
|
+
if (cloud && supabase === "local") throw new Error("--cloud takes a Smoodly Cloud space's values; it cannot be combined with --supabase local.");
|
|
49
|
+
if (cloud && values["editor-email"]) throw new Error("--cloud has no first editor to create: your smoodly.io account is the space's admin, and --editor-email does not apply. Invite others from the space page.");
|
|
46
50
|
const starter = values.starter;
|
|
47
51
|
if (starter !== void 0 && starter !== "lorem" && starter !== "blank") {
|
|
48
52
|
throw new Error(`--starter must be "lorem" or "blank", got "${starter}"`);
|
|
@@ -53,12 +57,13 @@ function parseFlags(argv) {
|
|
|
53
57
|
}
|
|
54
58
|
return {
|
|
55
59
|
name: positionals[0],
|
|
56
|
-
supabase,
|
|
60
|
+
supabase: cloud ? "hosted" : supabase,
|
|
57
61
|
url: values.url,
|
|
58
62
|
publishableKey: values["publishable-key"],
|
|
59
63
|
secretKey: values["secret-key"],
|
|
60
64
|
editorEmail: values["editor-email"],
|
|
61
65
|
skipLink: values["skip-link"] ?? false,
|
|
66
|
+
cloud,
|
|
62
67
|
yes: values.yes ?? false,
|
|
63
68
|
help: values.help ?? false,
|
|
64
69
|
smoodlyDep: values["smoodly-dep"],
|
|
@@ -84,6 +89,7 @@ Options:
|
|
|
84
89
|
--secret-key <key> Hosted: the service-role / secret key
|
|
85
90
|
--editor-email <email> The first editor (password from SMOODLY_EDITOR_PASSWORD)
|
|
86
91
|
--skip-link Hosted: do not run supabase link / db push
|
|
92
|
+
--cloud Smoodly Cloud: paste the three values from your space page; no supabase link or db push
|
|
87
93
|
-y, --yes Accept every default
|
|
88
94
|
-h, --help This text
|
|
89
95
|
`;
|
|
@@ -168,12 +174,20 @@ import * as p from "@clack/prompts";
|
|
|
168
174
|
import { resolve as resolve2 } from "node:path";
|
|
169
175
|
|
|
170
176
|
// src/env.ts
|
|
171
|
-
function renderEnv(keys, siteUrl2 = "http://localhost:3000") {
|
|
172
|
-
|
|
177
|
+
function renderEnv(keys, siteUrl2 = "http://localhost:3000", mode = "self-host") {
|
|
178
|
+
const header = mode === "cloud" ? [
|
|
179
|
+
"# Smoodly Cloud: the values from your space page at smoodly.io/spaces.",
|
|
180
|
+
"# The live site reads with SMOODLY_URL and SMOODLY_SECRET_KEY; the admin",
|
|
181
|
+
"# also signs in with SMOODLY_PUBLISHABLE_KEY. Replace the key from the",
|
|
182
|
+
"# space page if it ever leaks."
|
|
183
|
+
] : [
|
|
173
184
|
"# Smoodly, self-host: the content project's values. The live site reads",
|
|
174
185
|
"# with SMOODLY_URL and SMOODLY_SECRET_KEY; the admin also signs in with",
|
|
175
186
|
"# SMOODLY_PUBLISHABLE_KEY. SMOODLY_AUTH_URL and SMOODLY_AUTH_KEY default",
|
|
176
|
-
"# to these \u2014 leave them unset."
|
|
187
|
+
"# to these \u2014 leave them unset."
|
|
188
|
+
];
|
|
189
|
+
return [
|
|
190
|
+
...header,
|
|
177
191
|
`SMOODLY_URL=${keys.url}`,
|
|
178
192
|
`SMOODLY_SECRET_KEY=${keys.secretKey}`,
|
|
179
193
|
`SMOODLY_PUBLISHABLE_KEY=${keys.publishableKey}`,
|
|
@@ -383,6 +397,9 @@ async function resolveAnswers(flags2, ctx, env = process.env) {
|
|
|
383
397
|
if (!url) throw new Error(`${badUrl} (got "${typedUrl}")`);
|
|
384
398
|
const publishableKey = flags2.publishableKey ?? await answer(p.text({ message: "Publishable (anon) key" }));
|
|
385
399
|
const secretKey = flags2.secretKey ?? await answer(p.password({ message: "Secret (service role) key" }));
|
|
400
|
+
if (flags2.cloud) {
|
|
401
|
+
return { name, dir, supabase, hasSupabaseCli: ctx.hasSupabaseCli, hosted: { keys: { url, publishableKey, secretKey }, linkAndPush: false, cloud: true }, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
402
|
+
}
|
|
386
403
|
const ref = projectRef(url);
|
|
387
404
|
if (ctx.hasSupabaseCli && !flags2.skipLink && !ref) {
|
|
388
405
|
p.log.warn(`No project ref in ${url}; skipping supabase link. Run \`supabase link --project-ref <project-ref>\` and \`supabase db push\` yourself.`);
|
|
@@ -431,11 +448,12 @@ function plan(a) {
|
|
|
431
448
|
}
|
|
432
449
|
const steps = ["copyTemplate", "install"];
|
|
433
450
|
if (a.supabase === "local") steps.push("supabaseLocal");
|
|
434
|
-
else if (a.hasSupabaseCli) {
|
|
451
|
+
else if (a.hasSupabaseCli && !a.hosted?.cloud) {
|
|
435
452
|
steps.push("supabaseInit");
|
|
436
453
|
if (a.hosted?.linkAndPush) steps.push("linkAndPush");
|
|
437
454
|
}
|
|
438
|
-
steps.push("writeEnv", "ensureEditor", "seed"
|
|
455
|
+
steps.push("writeEnv", ...a.hosted?.cloud ? [] : ["ensureEditor"], "seed");
|
|
456
|
+
steps.push("gitInit", "finish");
|
|
439
457
|
return steps;
|
|
440
458
|
}
|
|
441
459
|
|
|
@@ -555,7 +573,7 @@ async function run(answers2, deps) {
|
|
|
555
573
|
await sh("supabase", ["db", "push"]);
|
|
556
574
|
break;
|
|
557
575
|
case "writeEnv":
|
|
558
|
-
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(ctx.keys, siteUrl(answers2)));
|
|
576
|
+
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(ctx.keys, siteUrl(answers2), answers2.hosted?.cloud ? "cloud" : "self-host"));
|
|
559
577
|
break;
|
|
560
578
|
case "ensureEditor":
|
|
561
579
|
ctx.editor = await ensureEditor(deps.editorClient(ctx.keys), deps.askEditor);
|
|
@@ -580,7 +598,7 @@ async function run(answers2, deps) {
|
|
|
580
598
|
return { ok: false, step, error, fallback: fallbackFor(step, answers2, ctx.keys) };
|
|
581
599
|
}
|
|
582
600
|
}
|
|
583
|
-
return { ok: true, keys: ctx.keys, editor: ctx.editor };
|
|
601
|
+
return ctx.editor ? { ok: true, keys: ctx.keys, editor: ctx.editor } : { ok: true, keys: ctx.keys };
|
|
584
602
|
}
|
|
585
603
|
var withoutSeedSql = (toml) => toml.replace(/(\[db\.seed\]\n(?:#.*\n)*enabled = )true/, "$1false");
|
|
586
604
|
var envFile = (answers2) => answers2.framework === "astro" ? ".env" : ".env.local";
|
|
@@ -589,7 +607,7 @@ function fallbackFor(step, answers2, keys) {
|
|
|
589
607
|
const envLines = keys ? `Write ${answers2.dir}/${envFile(answers2)}:
|
|
590
608
|
|
|
591
609
|
${renderEnv(keys, siteUrl(answers2)).split("\n").filter((l) => !l.startsWith("#")).join("\n").trimEnd()}` : `Write ${answers2.dir}/${envFile(answers2)} with SMOODLY_URL, SMOODLY_SECRET_KEY and SMOODLY_PUBLISHABLE_KEY (from \`supabase status\`), and SITE_URL=${siteUrl(answers2)}.`;
|
|
592
|
-
const editorLines = [
|
|
610
|
+
const editorLines = answers2.hosted?.cloud ? ["Then the starter content:", "", `cd ${answers2.dir} && npm run seed`].join("\n") : [
|
|
593
611
|
"Create the first editor, then the starter content:",
|
|
594
612
|
"",
|
|
595
613
|
`cd ${answers2.dir} && SMOODLY_ADMIN_PASSWORD=<a password> npx smoodly add-admin <your email>`,
|
|
@@ -698,7 +716,7 @@ p2.note(
|
|
|
698
716
|
"",
|
|
699
717
|
answers.starter === "blank" ? `Site: http://localhost:${port} (a blank site \u2014 build it in ${answers.framework === "astro" ? "src/sections/, src/components/" : "components/"} and at /admin)` : `Site: http://localhost:${port} (the Lorem Co. starter \u2014 edit it at /admin)`,
|
|
700
718
|
`Admin: http://localhost:${port}/admin`,
|
|
701
|
-
`Editor: ${outcome.editor.email ?? outcome.editor.id}
|
|
719
|
+
outcome.editor ? `Editor: ${outcome.editor.email ?? outcome.editor.id}` : "Editor: your smoodly.io account (invite others from the space page)",
|
|
702
720
|
...answers.locales ? [`Locales: ${answers.locales.join(", ")} (${answers.framework === "astro" ? "astro.config.ts and src/smoodly/config.ts" : "i18n/routing.ts"})`] : []
|
|
703
721
|
].join("\n"),
|
|
704
722
|
"Ready"
|
package/package.json
CHANGED
|
@@ -105,7 +105,7 @@ export const q = (): Queries =>
|
|
|
105
105
|
// route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
|
|
106
106
|
// reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
|
|
107
107
|
// CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
|
|
108
|
-
/** The
|
|
108
|
+
/** The route file calls these. */
|
|
109
109
|
export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
|
|
110
110
|
export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
|
|
111
111
|
export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
|
|
@@ -106,7 +106,7 @@ export const q = (): Queries =>
|
|
|
106
106
|
// route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
|
|
107
107
|
// reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
|
|
108
108
|
// CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
|
|
109
|
-
/** The
|
|
109
|
+
/** The route file calls these. */
|
|
110
110
|
export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
|
|
111
111
|
export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
|
|
112
112
|
export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
|
|
@@ -105,7 +105,7 @@ export const q = (): Queries =>
|
|
|
105
105
|
// route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
|
|
106
106
|
// reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
|
|
107
107
|
// CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
|
|
108
|
-
/** The
|
|
108
|
+
/** The route file calls these. */
|
|
109
109
|
export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
|
|
110
110
|
export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
|
|
111
111
|
export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
|
|
@@ -106,7 +106,7 @@ export const q = (): Queries =>
|
|
|
106
106
|
// route file without a dynamic segment reads nothing at build and the build needs no keys — a layout
|
|
107
107
|
// reading a global through q() included (2026-10-02, tester round 6). At run time it does nothing: a
|
|
108
108
|
// CMS route renders on its first visit and is cached until a publish expires it (plan 2026-10-09).
|
|
109
|
-
/** The
|
|
109
|
+
/** The route file calls these. */
|
|
110
110
|
export const renderSmoodlyPath: Renderer["renderSmoodlyPath"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPath(...args); };
|
|
111
111
|
export const smoodlyMetadata: Renderer["smoodlyMetadata"] = async (...args) => { await buildGate(); return bound().renderer.smoodlyMetadata(...args); };
|
|
112
112
|
export const renderSmoodlyPage: Renderer["renderSmoodlyPage"] = async (...args) => { await buildGate(); return bound().renderer.renderSmoodlyPage(...args); };
|