create-smoodly-app 0.0.50 → 0.0.52
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 +22 -30
- package/dist/index.js +87 -329
- package/package.json +4 -5
- package/templates/astro-blank/AGENTS.md +2 -8
- package/templates/astro-blank/README.md +13 -23
- package/templates/astro-blank/package.json +2 -3
- package/templates/astro-blank/scripts/seed.ts +9 -3
- package/templates/astro-blank/src/smoodly/collections.ts +2 -2
- package/templates/astro-blank-intl/AGENTS.md +2 -8
- package/templates/astro-blank-intl/README.md +13 -23
- package/templates/astro-blank-intl/package.json +2 -3
- package/templates/astro-blank-intl/scripts/seed.ts +9 -3
- package/templates/astro-blank-intl/src/smoodly/collections.ts +2 -2
- package/templates/astro-lorem/AGENTS.md +2 -8
- package/templates/astro-lorem/README.md +12 -16
- package/templates/astro-lorem/package.json +2 -3
- package/templates/astro-lorem/scripts/seed.ts +9 -3
- package/templates/astro-lorem/src/components/sections/PageHero.schema.ts +3 -1
- package/templates/astro-lorem/src/sections/pageHero.astro +1 -1
- package/templates/astro-lorem-intl/AGENTS.md +2 -8
- package/templates/astro-lorem-intl/README.md +12 -16
- package/templates/astro-lorem-intl/package.json +2 -3
- package/templates/astro-lorem-intl/scripts/seed.ts +9 -3
- package/templates/astro-lorem-intl/src/components/sections/PageHero.schema.ts +3 -1
- package/templates/astro-lorem-intl/src/sections/pageHero.astro +1 -1
- package/templates/blank/AGENTS.md +7 -13
- package/templates/blank/README.md +12 -22
- package/templates/blank/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/blank/next.config.ts +1 -4
- package/templates/blank/package.json +4 -4
- package/templates/blank/scripts/seed.ts +9 -3
- package/templates/blank/smoodly/collections.ts +2 -2
- package/templates/blank/smoodly/server.ts +25 -47
- package/templates/blank-intl/AGENTS.md +7 -13
- package/templates/blank-intl/README.md +12 -22
- package/templates/blank-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/blank-intl/next.config.ts +1 -4
- package/templates/blank-intl/package.json +4 -4
- package/templates/blank-intl/scripts/seed.ts +9 -3
- package/templates/blank-intl/smoodly/collections.ts +2 -2
- package/templates/blank-intl/smoodly/server.ts +25 -47
- package/templates/blank-intl.tsconfig.json +0 -3
- package/templates/blank.tsconfig.json +0 -3
- package/templates/lorem/AGENTS.md +7 -13
- package/templates/lorem/README.md +13 -17
- package/templates/lorem/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/lorem/components/sections/PageHero.schema.ts +3 -1
- package/templates/lorem/components/sections/PageHero.tsx +1 -1
- package/templates/lorem/next.config.ts +1 -4
- package/templates/lorem/package.json +4 -4
- package/templates/lorem/scripts/seed.ts +9 -3
- package/templates/lorem/smoodly/server.ts +25 -47
- package/templates/lorem-intl/AGENTS.md +7 -13
- package/templates/lorem-intl/README.md +13 -17
- package/templates/lorem-intl/app/(smoodly)/admin/[[...segments]]/route.ts +6 -5
- package/templates/lorem-intl/components/sections/PageHero.schema.ts +3 -1
- package/templates/lorem-intl/components/sections/PageHero.tsx +1 -1
- package/templates/lorem-intl/next.config.ts +1 -4
- package/templates/lorem-intl/package.json +4 -4
- package/templates/lorem-intl/scripts/seed.ts +9 -3
- package/templates/lorem-intl/smoodly/server.ts +25 -47
- package/templates/lorem-intl.tsconfig.json +0 -3
- package/templates/lorem.tsconfig.json +0 -3
- package/templates/astro-blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/astro-blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/astro-lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/astro-lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/astro-lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/astro-lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/astro-lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/astro-lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/astro-lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/astro-lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/astro-lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/astro-lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/astro-lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/astro-lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/astro-lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/blank/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/blank/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/blank/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/blank/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/blank/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/blank/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/blank/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/blank/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/blank/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/blank/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/blank/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/blank/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/blank-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/blank-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/blank-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/blank-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/blank-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/blank-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/blank-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/blank-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/blank-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/blank-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/blank-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/blank-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -20
- package/templates/lorem/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/lorem/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/lorem/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/lorem/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/lorem/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/lorem/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/lorem/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/lorem/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/lorem/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/lorem/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/lorem/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/lorem/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
- package/templates/lorem-intl/supabase/migrations/20260926120000_smoodly_v1.sql +0 -535
- package/templates/lorem-intl/supabase/migrations/20260926130000_smoodly_v2.sql +0 -26
- package/templates/lorem-intl/supabase/migrations/20260926140000_smoodly_v3.sql +0 -38
- package/templates/lorem-intl/supabase/migrations/20260926150000_smoodly_v4.sql +0 -41
- package/templates/lorem-intl/supabase/migrations/20260929120000_smoodly_v5.sql +0 -56
- package/templates/lorem-intl/supabase/migrations/20260929135523_smoodly_v6.sql +0 -200
- package/templates/lorem-intl/supabase/migrations/20260930040956_smoodly_v7.sql +0 -84
- package/templates/lorem-intl/supabase/migrations/20261007120000_smoodly_v8.sql +0 -154
- package/templates/lorem-intl/supabase/migrations/20261007200000_smoodly_v9.sql +0 -30
- package/templates/lorem-intl/supabase/migrations/20261008120000_smoodly_v10.sql +0 -47
- package/templates/lorem-intl/supabase/migrations/20261009120000_smoodly_v11.sql +0 -49
- package/templates/lorem-intl/supabase/migrations/20261009120100_smoodly_views.sql +0 -58
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
npx create-smoodly-app my-site
|
|
4
4
|
|
|
5
|
-
A fresh Astro or Next.js site with the Smoodly admin mounted at `/admin`,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
A fresh Astro or Next.js site with the Smoodly admin mounted at `/admin`,
|
|
6
|
+
`.env` (Astro) or `.env.local` (Next) written with a space's three values,
|
|
7
|
+
the site's schema pushed to the space and the starter content seeded.
|
|
8
8
|
|
|
9
9
|
## What it asks
|
|
10
10
|
|
|
@@ -18,31 +18,23 @@ written, and a first editor who can sign in.
|
|
|
18
18
|
gets the next-intl layout: the site under `app/[locale]/`, `i18n/`,
|
|
19
19
|
`proxy.ts`, translated URLs per locale; Astro gets its own i18n config
|
|
20
20
|
(`astro.config.ts` and `src/smoodly/config.ts`). Flag: `--locales en,fi`.
|
|
21
|
-
4.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
the
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`supabase link --project-ref <ref>` + `supabase db push`, the ref taken
|
|
30
|
-
from the URL so the CLI never opens its project picker). The URL may be
|
|
31
|
-
pasted without `https://`; anything that is not an http(s) URL is refused
|
|
32
|
-
before any step runs. A custom domain has no ref, so the link is not
|
|
33
|
-
offered and the two commands are printed instead. Either way the
|
|
34
|
-
project is Smoodly's own: its tables are created unprefixed in `public`,
|
|
35
|
-
so an app that keeps its own tables in Supabase gives them a project of
|
|
36
|
-
their own, or takes Smoodly Cloud for content (DESIGN.md §1).
|
|
37
|
-
5. The first editor: pick an existing Auth user or create one (email +
|
|
38
|
-
password). The `members` row is written with role `admin`, the email
|
|
39
|
-
beside the id.
|
|
40
|
-
If `@` does not appear when you type it (macOS, `@` on Option+2 with the
|
|
41
|
-
terminal's "Option as Meta" setting on), paste the address or pass
|
|
42
|
-
`--editor-email`.
|
|
21
|
+
4. **Starter**: Lorem Co. (a tour of every feature, the default) or blank.
|
|
22
|
+
Flag: `--starter lorem|blank`.
|
|
23
|
+
5. **The space's values**: create a project at smoodly.io, open its preview
|
|
24
|
+
space and paste the space id (`sp_…`), the secret key (`sk_…`, asked
|
|
25
|
+
hidden) and the publishable key (`pk_…`). A key of the wrong kind is
|
|
26
|
+
refused at its prompt. Flags: `--space`, `--secret-key`,
|
|
27
|
+
`--publishable-key`; a value cannot be defaulted, so `--yes` still asks,
|
|
28
|
+
and a run with no terminal and no flags names the three flags.
|
|
43
29
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
30
|
+
Then it installs, writes the env file, runs `npx smoodly push` (the schema
|
|
31
|
+
to the space), `npm run seed` (the starter content through the API) and
|
|
32
|
+
commits. A failed push or seed keeps the folder and prints the command that
|
|
33
|
+
finishes it; a failed install removes the folder.
|
|
34
|
+
|
|
35
|
+
Every prompt has a flag, so CI can run it: `--framework`, `--locales`,
|
|
36
|
+
`--starter`, `--space`, `--secret-key`, `--publishable-key`, `--yes`.
|
|
37
|
+
`--help` lists them. Hidden flags for the repo's loop and CI:
|
|
38
|
+
`--api-url <url>` writes `SMOODLY_API_URL` (another service than
|
|
39
|
+
Smoodly's), and `--smoodly-dep <spec>` overrides the `smoodly`
|
|
40
|
+
dependency written into `package.json`; CI points it at the packed tarball.
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import * as p2 from "@clack/prompts";
|
|
5
5
|
import { existsSync } from "node:fs";
|
|
6
|
-
import { readdir as readdir2,
|
|
6
|
+
import { readdir as readdir2, rm, writeFile as writeFile2 } from "node:fs/promises";
|
|
7
7
|
import { join as join3, relative } from "node:path";
|
|
8
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
9
|
|
|
@@ -24,25 +24,19 @@ function parseFlags(argv) {
|
|
|
24
24
|
args: argv,
|
|
25
25
|
allowPositionals: true,
|
|
26
26
|
options: {
|
|
27
|
-
|
|
28
|
-
url: { type: "string" },
|
|
29
|
-
"publishable-key": { type: "string" },
|
|
27
|
+
space: { type: "string" },
|
|
30
28
|
"secret-key": { type: "string" },
|
|
31
|
-
"
|
|
29
|
+
"publishable-key": { type: "string" },
|
|
30
|
+
"api-url": { type: "string" },
|
|
32
31
|
"smoodly-dep": { type: "string" },
|
|
33
32
|
locales: { type: "string" },
|
|
34
33
|
starter: { type: "string" },
|
|
35
34
|
framework: { type: "string" },
|
|
36
|
-
"skip-link": { type: "boolean", default: false },
|
|
37
35
|
yes: { type: "boolean", short: "y", default: false },
|
|
38
36
|
help: { type: "boolean", short: "h", default: false }
|
|
39
37
|
}
|
|
40
38
|
});
|
|
41
39
|
if (positionals.length > 1) throw new Error("Only one project name is accepted.");
|
|
42
|
-
const supabase = values.supabase;
|
|
43
|
-
if (supabase !== void 0 && supabase !== "local" && supabase !== "hosted") {
|
|
44
|
-
throw new Error(`--supabase must be "local" or "hosted", got "${supabase}"`);
|
|
45
|
-
}
|
|
46
40
|
const starter = values.starter;
|
|
47
41
|
if (starter !== void 0 && starter !== "lorem" && starter !== "blank") {
|
|
48
42
|
throw new Error(`--starter must be "lorem" or "blank", got "${starter}"`);
|
|
@@ -53,12 +47,10 @@ function parseFlags(argv) {
|
|
|
53
47
|
}
|
|
54
48
|
return {
|
|
55
49
|
name: positionals[0],
|
|
56
|
-
|
|
57
|
-
url: values.url,
|
|
58
|
-
publishableKey: values["publishable-key"],
|
|
50
|
+
space: values.space,
|
|
59
51
|
secretKey: values["secret-key"],
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
publishableKey: values["publishable-key"],
|
|
53
|
+
apiUrl: values["api-url"],
|
|
62
54
|
yes: values.yes ?? false,
|
|
63
55
|
help: values.help ?? false,
|
|
64
56
|
smoodlyDep: values["smoodly-dep"],
|
|
@@ -76,61 +68,22 @@ Usage: npx create-smoodly-app [folder] [options]
|
|
|
76
68
|
|
|
77
69
|
Options:
|
|
78
70
|
--framework astro|next The framework (asked when omitted; astro is the default)
|
|
79
|
-
--supabase local|hosted Where content lives (asked when omitted; local needs the Supabase CLI)
|
|
80
71
|
--locales <a,b,\u2026> Multilingual: the locales, first is the default (Astro i18n or next-intl; omitted = one language)
|
|
81
72
|
--starter lorem|blank lorem: the Lorem Co. tour of every feature (default); blank: one of each, for building your own
|
|
82
|
-
--
|
|
83
|
-
--
|
|
84
|
-
--
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-y, --yes Accept every default
|
|
73
|
+
--space <id> The space's id (sp_\u2026)
|
|
74
|
+
--secret-key <key> The space's secret key (sk_\u2026)
|
|
75
|
+
--publishable-key <key> The space's publishable key (pk_\u2026)
|
|
76
|
+
The three come from your project at smoodly.io: open its preview space and copy them.
|
|
77
|
+
-y, --yes Accept every default (the space's values are still asked when not passed)
|
|
88
78
|
-h, --help This text
|
|
89
79
|
`;
|
|
90
80
|
|
|
91
|
-
// src/editor.ts
|
|
92
|
-
import { createClient } from "@supabase/supabase-js";
|
|
93
|
-
async function ensureEditor(client, ask) {
|
|
94
|
-
const existing = await client.listUsers();
|
|
95
|
-
const choice = await ask(existing);
|
|
96
|
-
const user = choice.kind === "pick" ? choice.user : await client.createUser(choice.email, choice.password);
|
|
97
|
-
await client.upsertEditor(user.id, user.email, "admin");
|
|
98
|
-
return user;
|
|
99
|
-
}
|
|
100
|
-
function clientFor(keys) {
|
|
101
|
-
const secret = keys.secretKey.startsWith("sb_secret_");
|
|
102
|
-
return createClient(keys.url, secret ? keys.secretKey : keys.publishableKey, {
|
|
103
|
-
global: { headers: secret ? {} : { Authorization: `Bearer ${keys.secretKey}` } },
|
|
104
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
function supabaseEditorClient(keys) {
|
|
108
|
-
const db = clientFor(keys);
|
|
109
|
-
const fail = (what, message) => new Error(`${what}: ${message}`);
|
|
110
|
-
return {
|
|
111
|
-
async listUsers() {
|
|
112
|
-
const res = await db.auth.admin.listUsers({ perPage: 200 });
|
|
113
|
-
if (res.error) throw fail("listing Auth users", res.error.message);
|
|
114
|
-
return res.data.users.map((u) => ({ id: u.id, email: u.email ?? null }));
|
|
115
|
-
},
|
|
116
|
-
async createUser(email, password2) {
|
|
117
|
-
const res = await db.auth.admin.createUser({ email, password: password2, email_confirm: true });
|
|
118
|
-
if (res.error) throw fail("creating the Auth user", res.error.message);
|
|
119
|
-
return { id: res.data.user.id, email: res.data.user.email ?? null };
|
|
120
|
-
},
|
|
121
|
-
async upsertEditor(userId, email, role) {
|
|
122
|
-
const res = await db.from("members").upsert({ user_id: userId, email, role }, { onConflict: "space_id,user_id" });
|
|
123
|
-
if (res.error) throw fail("inserting the members row", res.error.message);
|
|
124
|
-
}
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
81
|
// src/exec.ts
|
|
129
82
|
import { spawn } from "node:child_process";
|
|
130
83
|
var exec = (cmd, args, opts) => new Promise((resolve3, reject) => {
|
|
131
84
|
const child = spawn(cmd, args, {
|
|
132
85
|
cwd: opts.cwd,
|
|
133
|
-
stdio: [
|
|
86
|
+
stdio: ["inherit", opts.capture ? "pipe" : "inherit", "inherit"],
|
|
134
87
|
shell: process.platform === "win32"
|
|
135
88
|
});
|
|
136
89
|
let stdout = "";
|
|
@@ -141,72 +94,34 @@ var exec = (cmd, args, opts) => new Promise((resolve3, reject) => {
|
|
|
141
94
|
(code) => code === 0 ? resolve3({ stdout }) : reject(new Error(`\`${cmd} ${args.join(" ")}\` exited with code ${code}`))
|
|
142
95
|
);
|
|
143
96
|
});
|
|
144
|
-
async function hasCommand(cmd, run2, cwd) {
|
|
145
|
-
try {
|
|
146
|
-
await run2(cmd, ["--version"], { cwd, capture: true });
|
|
147
|
-
return true;
|
|
148
|
-
} catch {
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// src/ports-net.ts
|
|
154
|
-
import { createServer } from "node:net";
|
|
155
|
-
function canListen(port2, host) {
|
|
156
|
-
return new Promise((resolve3) => {
|
|
157
|
-
const server = createServer();
|
|
158
|
-
server.once("error", () => resolve3(false));
|
|
159
|
-
server.listen(port2, host, () => server.close(() => resolve3(true)));
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
async function isPortFree(port2) {
|
|
163
|
-
return await canListen(port2, "127.0.0.1") && await canListen(port2, "0.0.0.0");
|
|
164
|
-
}
|
|
165
97
|
|
|
166
98
|
// src/prompts.ts
|
|
167
99
|
import * as p from "@clack/prompts";
|
|
168
100
|
import { resolve as resolve2 } from "node:path";
|
|
169
101
|
|
|
170
102
|
// src/env.ts
|
|
171
|
-
|
|
103
|
+
var SPACE = "sp_(?:eu_)?[a-z2-7]{20}";
|
|
104
|
+
var KEY = (kind) => new RegExp(`^${kind}_${SPACE}_[a-z2-7]{32}_[a-z2-7]{16}$`);
|
|
105
|
+
var isSpaceId = (v) => new RegExp(`^${SPACE}$`).test(v);
|
|
106
|
+
var isSecretKey = (v) => KEY("sk").test(v);
|
|
107
|
+
var isPublishableKey = (v) => KEY("pk").test(v);
|
|
108
|
+
function renderEnv(values, siteUrl2) {
|
|
172
109
|
return [
|
|
173
|
-
"# Smoodly
|
|
174
|
-
"#
|
|
175
|
-
"#
|
|
176
|
-
|
|
177
|
-
`
|
|
178
|
-
`
|
|
179
|
-
`
|
|
110
|
+
"# Smoodly: the space this site reads, from its page at smoodly.io. The",
|
|
111
|
+
"# secret key stays on the server; replace it from the space page if it",
|
|
112
|
+
"# ever leaks.",
|
|
113
|
+
`SMOODLY_SPACE_ID=${values.spaceId}`,
|
|
114
|
+
`SMOODLY_SECRET_KEY=${values.secretKey}`,
|
|
115
|
+
`SMOODLY_PUBLISHABLE_KEY=${values.publishableKey}`,
|
|
116
|
+
...values.apiUrl ? [`SMOODLY_API_URL=${values.apiUrl}`] : [],
|
|
180
117
|
"",
|
|
181
|
-
"# The site's own address: absolute
|
|
182
|
-
"# the
|
|
118
|
+
"# The site's own address: absolute links in the page head, and the",
|
|
119
|
+
"# address the schema push gives the space. Set it to the production URL",
|
|
120
|
+
"# where you deploy.",
|
|
183
121
|
`SITE_URL=${siteUrl2}`,
|
|
184
122
|
""
|
|
185
123
|
].join("\n");
|
|
186
124
|
}
|
|
187
|
-
function normalizeProjectUrl(input) {
|
|
188
|
-
const raw = input.trim();
|
|
189
|
-
if (!raw) return null;
|
|
190
|
-
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
191
|
-
try {
|
|
192
|
-
const url = new URL(withScheme);
|
|
193
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
194
|
-
if (!url.hostname) return null;
|
|
195
|
-
return url.origin;
|
|
196
|
-
} catch {
|
|
197
|
-
return null;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function projectRef(url) {
|
|
201
|
-
let host;
|
|
202
|
-
try {
|
|
203
|
-
host = new URL(url).hostname;
|
|
204
|
-
} catch {
|
|
205
|
-
return null;
|
|
206
|
-
}
|
|
207
|
-
const m = /^([a-z0-9]{20})\.supabase\.(co|in|red)$/.exec(host);
|
|
208
|
-
return m ? m[1] : null;
|
|
209
|
-
}
|
|
210
125
|
|
|
211
126
|
// src/scaffold.ts
|
|
212
127
|
import { cp, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
@@ -313,12 +228,36 @@ async function answer(value) {
|
|
|
313
228
|
if (p.isCancel(v)) bail("Cancelled.");
|
|
314
229
|
return v;
|
|
315
230
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
231
|
+
var checkSpaceId = (v) => isSpaceId(v) ? void 0 : "A space id looks like sp_\u2026 \u2014 copy it from the space page.";
|
|
232
|
+
var checkSecretKey = (v) => v.startsWith("pk_") ? "That is the publishable key \u2014 paste the secret key (sk_\u2026)." : isSecretKey(v) ? void 0 : "A secret key looks like sk_sp_\u2026 \u2014 copy it from the space page.";
|
|
233
|
+
var checkPublishableKey = (v) => v.startsWith("sk_") ? "That is the secret key \u2014 it never goes where this one goes; paste the publishable key (pk_\u2026)." : isPublishableKey(v) ? void 0 : "A publishable key looks like pk_sp_\u2026 \u2014 copy it from the space page.";
|
|
234
|
+
function checked(value, check) {
|
|
235
|
+
const message = check(value);
|
|
236
|
+
if (message) throw new Error(message);
|
|
237
|
+
}
|
|
238
|
+
function typedValues(flags2) {
|
|
239
|
+
const spaceId = flags2.space?.trim(), secretKey = flags2.secretKey?.trim(), publishableKey = flags2.publishableKey?.trim();
|
|
240
|
+
if (spaceId !== void 0) checked(spaceId, checkSpaceId);
|
|
241
|
+
if (secretKey !== void 0) checked(secretKey, checkSecretKey);
|
|
242
|
+
if (publishableKey !== void 0) checked(publishableKey, checkPublishableKey);
|
|
243
|
+
return { spaceId, secretKey, publishableKey };
|
|
244
|
+
}
|
|
245
|
+
async function spaceValues(flags2) {
|
|
246
|
+
const hidden = flags2.apiUrl ? { apiUrl: flags2.apiUrl } : {};
|
|
247
|
+
const typed = typedValues(flags2);
|
|
248
|
+
if (typed.spaceId !== void 0 && typed.secretKey !== void 0 && typed.publishableKey !== void 0) {
|
|
249
|
+
return { spaceId: typed.spaceId, secretKey: typed.secretKey, publishableKey: typed.publishableKey, ...hidden };
|
|
250
|
+
}
|
|
251
|
+
if (!process.stdin.isTTY) throw new Error("smoodly: pass --space, --secret-key and --publishable-key \u2014 the space page at smoodly.io shows them.");
|
|
252
|
+
p.note("Create a project at smoodly.io and open its preview space; paste its values here.", "Smoodly");
|
|
253
|
+
const ask = (value) => answer(value).then((v) => v.trim());
|
|
254
|
+
const spaceId = typed.spaceId ?? await ask(p.text({ message: "Space id", placeholder: "sp_\u2026", validate: (v) => checkSpaceId((v ?? "").trim()) }));
|
|
255
|
+
const secretKey = typed.secretKey ?? await ask(p.password({ message: "Secret key", validate: (v) => checkSecretKey((v ?? "").trim()) }));
|
|
256
|
+
const publishableKey = typed.publishableKey ?? await ask(p.text({ message: "Publishable key", placeholder: "pk_\u2026", validate: (v) => checkPublishableKey((v ?? "").trim()) }));
|
|
257
|
+
return { spaceId, secretKey, publishableKey, ...hidden };
|
|
258
|
+
}
|
|
259
|
+
async function resolveAnswers(flags2, ctx) {
|
|
260
|
+
typedValues(flags2);
|
|
322
261
|
const folderName = (typed2) => nameFromDir(resolve2(ctx.cwd, typed2));
|
|
323
262
|
const typed = flags2.name ?? await answer(p.text({ message: "Project folder", placeholder: "my-site", validate: (v) => v ? validName(folderName(v)) ?? void 0 : "A folder name or path." }));
|
|
324
263
|
const dir = resolve2(ctx.cwd, typed);
|
|
@@ -357,64 +296,8 @@ async function resolveAnswers(flags2, ctx, env = process.env) {
|
|
|
357
296
|
]
|
|
358
297
|
})
|
|
359
298
|
));
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
message: "Where should content live?",
|
|
363
|
-
options: [
|
|
364
|
-
{ value: "local", label: "Local Supabase stack", hint: "supabase start inside the new app; needs the Supabase CLI and Docker" },
|
|
365
|
-
{ value: "hosted", label: "Hosted Supabase project", hint: "paste the project URL and keys" }
|
|
366
|
-
]
|
|
367
|
-
})
|
|
368
|
-
));
|
|
369
|
-
if (supabase === "local") {
|
|
370
|
-
if (!ctx.hasSupabaseCli) bail("Local mode needs the Supabase CLI: https://supabase.com/docs/guides/cli (brew install supabase/tap/supabase)");
|
|
371
|
-
if (flags2.editorEmail && !env.SMOODLY_EDITOR_PASSWORD) throw new Error(`Set SMOODLY_EDITOR_PASSWORD to create ${flags2.editorEmail}.`);
|
|
372
|
-
return { name, dir, supabase, hasSupabaseCli: true, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
373
|
-
}
|
|
374
|
-
const badUrl = "Supabase project URL must be an http(s) URL, like https://abcd.supabase.co";
|
|
375
|
-
const typedUrl = flags2.url ?? await answer(
|
|
376
|
-
p.text({
|
|
377
|
-
message: "Supabase project URL",
|
|
378
|
-
placeholder: "https://abcd.supabase.co",
|
|
379
|
-
validate: (v) => normalizeProjectUrl(v ?? "") ? void 0 : badUrl
|
|
380
|
-
})
|
|
381
|
-
);
|
|
382
|
-
const url = normalizeProjectUrl(typedUrl);
|
|
383
|
-
if (!url) throw new Error(`${badUrl} (got "${typedUrl}")`);
|
|
384
|
-
const publishableKey = flags2.publishableKey ?? await answer(p.text({ message: "Publishable (anon) key" }));
|
|
385
|
-
const secretKey = flags2.secretKey ?? await answer(p.password({ message: "Secret (service role) key" }));
|
|
386
|
-
const ref = projectRef(url);
|
|
387
|
-
if (ctx.hasSupabaseCli && !flags2.skipLink && !ref) {
|
|
388
|
-
p.log.warn(`No project ref in ${url}; skipping supabase link. Run \`supabase link --project-ref <project-ref>\` and \`supabase db push\` yourself.`);
|
|
389
|
-
}
|
|
390
|
-
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)` }));
|
|
391
|
-
return { name, dir, supabase, hasSupabaseCli: ctx.hasSupabaseCli, hosted: { keys: { url, publishableKey, secretKey }, linkAndPush }, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
392
|
-
}
|
|
393
|
-
function askEditorWith(flags2, env = process.env) {
|
|
394
|
-
return async (existing) => {
|
|
395
|
-
if (flags2.editorEmail) {
|
|
396
|
-
const found = existing.find((u) => u.email === flags2.editorEmail);
|
|
397
|
-
if (found) return { kind: "pick", user: found };
|
|
398
|
-
const password3 = env.SMOODLY_EDITOR_PASSWORD;
|
|
399
|
-
if (!password3) throw new Error(`Set SMOODLY_EDITOR_PASSWORD to create ${flags2.editorEmail}.`);
|
|
400
|
-
return { kind: "create", email: flags2.editorEmail, password: password3 };
|
|
401
|
-
}
|
|
402
|
-
if (existing.length > 0) {
|
|
403
|
-
const picked = await answerOrThrow(
|
|
404
|
-
p.select({
|
|
405
|
-
message: "Who is the first editor?",
|
|
406
|
-
options: [
|
|
407
|
-
...existing.map((u) => ({ value: u.id, label: u.email ?? u.id })),
|
|
408
|
-
{ value: "__new__", label: "Create a new user" }
|
|
409
|
-
]
|
|
410
|
-
})
|
|
411
|
-
);
|
|
412
|
-
if (picked !== "__new__") return { kind: "pick", user: existing.find((u) => u.id === picked) };
|
|
413
|
-
}
|
|
414
|
-
const email = await answerOrThrow(p.text({ message: "Editor email", validate: (v) => v && v.includes("@") ? void 0 : "An email address" }));
|
|
415
|
-
const password2 = await answerOrThrow(p.password({ message: "Editor password", validate: (v) => v && v.length >= 8 ? void 0 : "At least 8 characters" }));
|
|
416
|
-
return { kind: "create", email, password: password2 };
|
|
417
|
-
};
|
|
299
|
+
const space = await spaceValues(flags2);
|
|
300
|
+
return { name, dir, space, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
418
301
|
}
|
|
419
302
|
|
|
420
303
|
// src/run.ts
|
|
@@ -425,76 +308,18 @@ function templateName(framework, starter, locales) {
|
|
|
425
308
|
const base = locales === "intl" ? `${starter}-intl` : starter;
|
|
426
309
|
return framework === "astro" ? `astro-${base}` : base;
|
|
427
310
|
}
|
|
428
|
-
function plan(
|
|
429
|
-
|
|
430
|
-
throw new Error("Local mode needs the Supabase CLI: https://supabase.com/docs/guides/cli (brew install supabase/tap/supabase)");
|
|
431
|
-
}
|
|
432
|
-
const steps = ["copyTemplate", "install"];
|
|
433
|
-
if (a.supabase === "local") steps.push("supabaseLocal");
|
|
434
|
-
else if (a.hasSupabaseCli) {
|
|
435
|
-
steps.push("supabaseInit");
|
|
436
|
-
if (a.hosted?.linkAndPush) steps.push("linkAndPush");
|
|
437
|
-
}
|
|
438
|
-
steps.push("writeEnv", "ensureEditor", "seed", "gitInit", "finish");
|
|
439
|
-
return steps;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// src/ports.ts
|
|
443
|
-
var DEFAULT_BASE = 54300;
|
|
444
|
-
var BASES = Array.from({ length: 10 }, (_, i) => DEFAULT_BASE + i * 1e3);
|
|
445
|
-
var PORT_LINE = /^([ \t]*#?[ \t]*[a-z0-9_]*port[ \t]*=[ \t]*)543(\d\d)([ \t]*(?:#.*)?)$/gm;
|
|
446
|
-
var LIVE_PORT_LINE = /^[ \t]*[a-z0-9_]*port[ \t]*=[ \t]*(543\d\d)[ \t]*(?:#.*)?$/gm;
|
|
447
|
-
function blockPorts(toml) {
|
|
448
|
-
return [...toml.matchAll(LIVE_PORT_LINE)].map((m) => Number(m[1]));
|
|
449
|
-
}
|
|
450
|
-
function shiftBlock(toml, base) {
|
|
451
|
-
return toml.replace(PORT_LINE, (_m, pre, xx, post) => `${pre}${base + Number(xx)}${post}`);
|
|
452
|
-
}
|
|
453
|
-
async function findFreeBlock(ports, isFree, bases = BASES) {
|
|
454
|
-
for (const base of bases) {
|
|
455
|
-
const shifted = ports.map((p3) => base + (p3 - DEFAULT_BASE));
|
|
456
|
-
let allFree = true;
|
|
457
|
-
for (const port2 of shifted) {
|
|
458
|
-
if (!await isFree(port2)) {
|
|
459
|
-
allFree = false;
|
|
460
|
-
break;
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (allFree) return base;
|
|
464
|
-
}
|
|
465
|
-
throw new Error(`No free Supabase port block: tried ${bases.join(", ")}. Stop another local Supabase project and run again.`);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// src/supabase-cli.ts
|
|
469
|
-
function parseStatusEnv(output) {
|
|
470
|
-
const vars = /* @__PURE__ */ new Map();
|
|
471
|
-
for (const line of output.split("\n")) {
|
|
472
|
-
const m = /^([A-Z_]+)=(?:"([^"]*)"|(.*))$/.exec(line.trim());
|
|
473
|
-
if (m) vars.set(m[1], m[2] ?? m[3]);
|
|
474
|
-
}
|
|
475
|
-
const url = vars.get("API_URL");
|
|
476
|
-
const publishableKey = vars.get("ANON_KEY");
|
|
477
|
-
const secretKey = vars.get("SERVICE_ROLE_KEY");
|
|
478
|
-
if (!url || !publishableKey || !secretKey) {
|
|
479
|
-
throw new Error("`supabase status -o env` did not report API_URL, ANON_KEY and SERVICE_ROLE_KEY");
|
|
480
|
-
}
|
|
481
|
-
return { url, publishableKey, secretKey };
|
|
311
|
+
function plan(_a) {
|
|
312
|
+
return ["copyTemplate", "install", "writeEnv", "push", "seed", "gitInit", "finish"];
|
|
482
313
|
}
|
|
483
314
|
|
|
484
315
|
// src/run.ts
|
|
485
|
-
var LOCAL_START_ARGS = ["start", "-x", "imgproxy,edge-runtime,logflare,vector,supavisor"];
|
|
486
316
|
async function run(answers2, deps) {
|
|
487
|
-
if (answers2.supabase === "hosted" && !answers2.hosted) {
|
|
488
|
-
throw new Error("hosted answers need the project's keys");
|
|
489
|
-
}
|
|
490
317
|
if (!await deps.fs.isEmptyOrMissing(answers2.dir)) {
|
|
491
318
|
throw new Error(`${answers2.dir} exists and is not empty.`);
|
|
492
319
|
}
|
|
493
|
-
const steps = plan(answers2);
|
|
494
|
-
const ctx = { keys: answers2.hosted?.keys };
|
|
495
320
|
const cwd = answers2.dir;
|
|
496
321
|
const sh = (cmd, args, extra = {}) => deps.exec(cmd, args, { cwd, ...extra });
|
|
497
|
-
for (const step of
|
|
322
|
+
for (const step of plan(answers2)) {
|
|
498
323
|
try {
|
|
499
324
|
switch (step) {
|
|
500
325
|
case "copyTemplate":
|
|
@@ -512,58 +337,24 @@ async function run(answers2, deps) {
|
|
|
512
337
|
deps.log("Installing dependencies\u2026");
|
|
513
338
|
await sh("npm", ["install"]);
|
|
514
339
|
break;
|
|
515
|
-
case "gitInit":
|
|
516
|
-
if (answers2.framework === "next") await deps.agentFiles?.(cwd).catch(() => {
|
|
517
|
-
});
|
|
518
|
-
await sh("git", ["init"], { capture: true });
|
|
519
|
-
await sh("git", ["add", "-A"], { capture: true });
|
|
520
|
-
await sh("git", ["commit", "-m", "Smoodly site"], { capture: true });
|
|
521
|
-
break;
|
|
522
|
-
case "supabaseLocal": {
|
|
523
|
-
deps.log("Starting the local Supabase stack (first run pulls Docker images)\u2026");
|
|
524
|
-
await sh("supabase", ["init"], { stdin: "ignore" });
|
|
525
|
-
const configPath = join2(cwd, "supabase/config.toml");
|
|
526
|
-
const toml = withoutSeedSql(await deps.fs.readFile(configPath));
|
|
527
|
-
const ports = blockPorts(toml);
|
|
528
|
-
const base = await findFreeBlock(ports, deps.isPortFree);
|
|
529
|
-
await deps.fs.writeFile(configPath, base !== DEFAULT_BASE ? shiftBlock(toml, base) : toml);
|
|
530
|
-
if (base !== DEFAULT_BASE) {
|
|
531
|
-
deps.log(`Ports ${DEFAULT_BASE / 100}xx are busy (another Supabase project running?), using ${base / 100}xx.`);
|
|
532
|
-
}
|
|
533
|
-
try {
|
|
534
|
-
await sh("supabase", LOCAL_START_ARGS);
|
|
535
|
-
} catch (e) {
|
|
536
|
-
const shifted = ports.map((p3) => base + (p3 - DEFAULT_BASE));
|
|
537
|
-
const taken = (await Promise.all(shifted.map(deps.isPortFree))).some((free) => !free);
|
|
538
|
-
if (!taken) throw e;
|
|
539
|
-
const next = await findFreeBlock(ports, deps.isPortFree);
|
|
540
|
-
await deps.fs.writeFile(configPath, shiftBlock(toml, next));
|
|
541
|
-
deps.log(`Ports ${base / 100}xx were taken while starting (another Supabase project?), retrying on ${next / 100}xx.`);
|
|
542
|
-
await sh("supabase", LOCAL_START_ARGS);
|
|
543
|
-
}
|
|
544
|
-
ctx.keys = parseStatusEnv((await sh("supabase", ["status", "-o", "env"], { capture: true })).stdout);
|
|
545
|
-
break;
|
|
546
|
-
}
|
|
547
|
-
case "supabaseInit": {
|
|
548
|
-
await sh("supabase", ["init"], { stdin: "ignore" });
|
|
549
|
-
const configPath = join2(cwd, "supabase/config.toml");
|
|
550
|
-
await deps.fs.writeFile(configPath, withoutSeedSql(await deps.fs.readFile(configPath)));
|
|
551
|
-
break;
|
|
552
|
-
}
|
|
553
|
-
case "linkAndPush":
|
|
554
|
-
await sh("supabase", linkArgs(answers2));
|
|
555
|
-
await sh("supabase", ["db", "push"]);
|
|
556
|
-
break;
|
|
557
340
|
case "writeEnv":
|
|
558
|
-
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(
|
|
341
|
+
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(answers2.space, siteUrl(answers2)));
|
|
559
342
|
break;
|
|
560
|
-
case "
|
|
561
|
-
|
|
343
|
+
case "push":
|
|
344
|
+
deps.log("Pushing the site's schema to the space\u2026");
|
|
345
|
+
await sh("npx", ["smoodly", "push"]);
|
|
562
346
|
break;
|
|
563
347
|
case "seed":
|
|
564
348
|
deps.log("Seeding the starter content\u2026");
|
|
565
349
|
await sh("npm", ["run", "seed"]);
|
|
566
350
|
break;
|
|
351
|
+
case "gitInit":
|
|
352
|
+
if (answers2.framework === "next") await deps.agentFiles?.(cwd).catch(() => {
|
|
353
|
+
});
|
|
354
|
+
await sh("git", ["init"], { capture: true });
|
|
355
|
+
await sh("git", ["add", "-A"], { capture: true });
|
|
356
|
+
await sh("git", ["commit", "-m", "Smoodly site"], { capture: true });
|
|
357
|
+
break;
|
|
567
358
|
case "finish":
|
|
568
359
|
break;
|
|
569
360
|
}
|
|
@@ -577,57 +368,29 @@ async function run(answers2, deps) {
|
|
|
577
368
|
deps.log(`Skipped the initial git commit (${error}). Commit when you like.`);
|
|
578
369
|
continue;
|
|
579
370
|
}
|
|
580
|
-
return { ok: false, step, error, fallback: fallbackFor(step, answers2
|
|
371
|
+
return { ok: false, step, error, fallback: fallbackFor(step, answers2) };
|
|
581
372
|
}
|
|
582
373
|
}
|
|
583
|
-
return { ok: true
|
|
374
|
+
return { ok: true };
|
|
584
375
|
}
|
|
585
|
-
var withoutSeedSql = (toml) => toml.replace(/(\[db\.seed\]\n(?:#.*\n)*enabled = )true/, "$1false");
|
|
586
376
|
var envFile = (answers2) => answers2.framework === "astro" ? ".env" : ".env.local";
|
|
587
377
|
var siteUrl = (answers2) => answers2.framework === "astro" ? "http://localhost:4321" : "http://localhost:3000";
|
|
588
|
-
function fallbackFor(step, answers2
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
${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 = [
|
|
593
|
-
"Create the first editor, then the starter content:",
|
|
594
|
-
"",
|
|
595
|
-
`cd ${answers2.dir} && SMOODLY_ADMIN_PASSWORD=<a password> npx smoodly add-admin <your email>`,
|
|
596
|
-
`cd ${answers2.dir} && npm run seed`
|
|
597
|
-
].join("\n");
|
|
378
|
+
function fallbackFor(step, answers2) {
|
|
379
|
+
const finish = `cd ${answers2.dir} && npx smoodly push && npm run seed`;
|
|
598
380
|
switch (step) {
|
|
599
|
-
case "
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
return [...commands, "", envLines, "", editorLines].join("\n");
|
|
381
|
+
case "writeEnv": {
|
|
382
|
+
const masked = { ...answers2.space, secretKey: "<the secret key you pasted>", publishableKey: "<the publishable key you pasted>" };
|
|
383
|
+
const lines = renderEnv(masked, siteUrl(answers2)).split("\n").filter((l) => !l.startsWith("#")).join("\n").replace(/\n{2,}/g, "\n").trim();
|
|
384
|
+
return [`Write ${answers2.dir}/${envFile(answers2)}:`, "", lines, "", "Then the schema and the starter content:", "", finish].join("\n");
|
|
604
385
|
}
|
|
605
|
-
case "
|
|
606
|
-
return
|
|
607
|
-
case "writeEnv":
|
|
608
|
-
return [envLines, "", editorLines].join("\n");
|
|
609
|
-
case "ensureEditor":
|
|
610
|
-
return [
|
|
611
|
-
"If the members table is missing, apply the migration first: `supabase db push` (hosted) or `supabase start` (local).",
|
|
612
|
-
"",
|
|
613
|
-
editorLines
|
|
614
|
-
].join("\n");
|
|
386
|
+
case "push":
|
|
387
|
+
return finish;
|
|
615
388
|
case "seed":
|
|
616
389
|
return `cd ${answers2.dir} && npm run seed`;
|
|
617
390
|
default:
|
|
618
391
|
return "";
|
|
619
392
|
}
|
|
620
393
|
}
|
|
621
|
-
function linkArgs(answers2) {
|
|
622
|
-
const url = answers2.hosted?.keys.url ?? "";
|
|
623
|
-
const ref = projectRef(url);
|
|
624
|
-
if (!ref) throw new Error(`no project ref in ${url}; run \`supabase link --project-ref <project-ref>\` yourself`);
|
|
625
|
-
return ["link", "--project-ref", ref];
|
|
626
|
-
}
|
|
627
|
-
function linkCommand(answers2) {
|
|
628
|
-
const ref = answers2.hosted ? projectRef(answers2.hosted.keys.url) : null;
|
|
629
|
-
return `supabase link --project-ref ${ref ?? "<project-ref>"}`;
|
|
630
|
-
}
|
|
631
394
|
|
|
632
395
|
// src/index.ts
|
|
633
396
|
var TEMPLATE_DIRS = {
|
|
@@ -656,20 +419,15 @@ p2.intro("create-smoodly-app");
|
|
|
656
419
|
var answers;
|
|
657
420
|
var outcome;
|
|
658
421
|
try {
|
|
659
|
-
|
|
660
|
-
answers = await resolveAnswers(flags, { hasSupabaseCli, cwd: process.cwd() });
|
|
422
|
+
answers = await resolveAnswers(flags, { cwd: process.cwd() });
|
|
661
423
|
const existed = existsSync(answers.dir);
|
|
662
424
|
outcome = await run(answers, {
|
|
663
425
|
exec,
|
|
664
426
|
templateDirs: TEMPLATE_DIRS,
|
|
665
|
-
editorClient: supabaseEditorClient,
|
|
666
|
-
askEditor: askEditorWith(flags),
|
|
667
427
|
log: (line) => p2.log.step(line),
|
|
668
|
-
isPortFree,
|
|
669
428
|
fs: {
|
|
670
429
|
isEmptyOrMissing,
|
|
671
430
|
scaffold,
|
|
672
|
-
readFile: (path) => readFile2(path, "utf8"),
|
|
673
431
|
writeFile: (path, text2) => writeFile2(path, text2),
|
|
674
432
|
rm: async (dir) => {
|
|
675
433
|
if (!existed) return rm(dir, { recursive: true, force: true });
|
|
@@ -698,7 +456,7 @@ p2.note(
|
|
|
698
456
|
"",
|
|
699
457
|
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
458
|
`Admin: http://localhost:${port}/admin`,
|
|
701
|
-
`
|
|
459
|
+
`Space: ${answers.space.spaceId} (editors are invited from the project at smoodly.io)`,
|
|
702
460
|
...answers.locales ? [`Locales: ${answers.locales.join(", ")} (${answers.framework === "astro" ? "astro.config.ts and src/smoodly/config.ts" : "i18n/routing.ts"})`] : []
|
|
703
461
|
].join("\n"),
|
|
704
462
|
"Ready"
|