create-smoodly-app 0.0.51 → 0.0.53
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 -33
- package/dist/index.js +86 -347
- 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/_gitignore +1 -0
- 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/_gitignore +1 -0
- 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/_gitignore +1 -0
- 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/_gitignore +1 -0
- 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/_gitignore +1 -0
- 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 +24 -46
- package/templates/blank-intl/AGENTS.md +7 -13
- package/templates/blank-intl/README.md +12 -22
- package/templates/blank-intl/_gitignore +1 -0
- 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 +24 -46
- 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/_gitignore +1 -0
- 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/config.ts +1 -1
- package/templates/lorem/smoodly/server.ts +24 -46
- package/templates/lorem-intl/AGENTS.md +7 -13
- package/templates/lorem-intl/README.md +13 -17
- package/templates/lorem-intl/_gitignore +1 -0
- 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/config.ts +1 -1
- package/templates/lorem-intl/smoodly/server.ts +24 -46
- 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/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,29 +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
|
-
cloud: { type: "boolean", default: false },
|
|
38
35
|
yes: { type: "boolean", short: "y", default: false },
|
|
39
36
|
help: { type: "boolean", short: "h", default: false }
|
|
40
37
|
}
|
|
41
38
|
});
|
|
42
39
|
if (positionals.length > 1) throw new Error("Only one project name is accepted.");
|
|
43
|
-
const supabase = values.supabase;
|
|
44
|
-
if (supabase !== void 0 && supabase !== "local" && supabase !== "hosted") {
|
|
45
|
-
throw new Error(`--supabase must be "local" or "hosted", got "${supabase}"`);
|
|
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.");
|
|
50
40
|
const starter = values.starter;
|
|
51
41
|
if (starter !== void 0 && starter !== "lorem" && starter !== "blank") {
|
|
52
42
|
throw new Error(`--starter must be "lorem" or "blank", got "${starter}"`);
|
|
@@ -57,13 +47,10 @@ function parseFlags(argv) {
|
|
|
57
47
|
}
|
|
58
48
|
return {
|
|
59
49
|
name: positionals[0],
|
|
60
|
-
|
|
61
|
-
url: values.url,
|
|
62
|
-
publishableKey: values["publishable-key"],
|
|
50
|
+
space: values.space,
|
|
63
51
|
secretKey: values["secret-key"],
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
cloud,
|
|
52
|
+
publishableKey: values["publishable-key"],
|
|
53
|
+
apiUrl: values["api-url"],
|
|
67
54
|
yes: values.yes ?? false,
|
|
68
55
|
help: values.help ?? false,
|
|
69
56
|
smoodlyDep: values["smoodly-dep"],
|
|
@@ -81,62 +68,22 @@ Usage: npx create-smoodly-app [folder] [options]
|
|
|
81
68
|
|
|
82
69
|
Options:
|
|
83
70
|
--framework astro|next The framework (asked when omitted; astro is the default)
|
|
84
|
-
--supabase local|hosted Where content lives (asked when omitted; local needs the Supabase CLI)
|
|
85
71
|
--locales <a,b,\u2026> Multilingual: the locales, first is the default (Astro i18n or next-intl; omitted = one language)
|
|
86
72
|
--starter lorem|blank lorem: the Lorem Co. tour of every feature (default); blank: one of each, for building your own
|
|
87
|
-
--
|
|
88
|
-
--
|
|
89
|
-
--
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
--cloud Smoodly Cloud: paste the three values from your space page; no supabase link or db push
|
|
93
|
-
-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)
|
|
94
78
|
-h, --help This text
|
|
95
79
|
`;
|
|
96
80
|
|
|
97
|
-
// src/editor.ts
|
|
98
|
-
import { createClient } from "@supabase/supabase-js";
|
|
99
|
-
async function ensureEditor(client, ask) {
|
|
100
|
-
const existing = await client.listUsers();
|
|
101
|
-
const choice = await ask(existing);
|
|
102
|
-
const user = choice.kind === "pick" ? choice.user : await client.createUser(choice.email, choice.password);
|
|
103
|
-
await client.upsertEditor(user.id, user.email, "admin");
|
|
104
|
-
return user;
|
|
105
|
-
}
|
|
106
|
-
function clientFor(keys) {
|
|
107
|
-
const secret = keys.secretKey.startsWith("sb_secret_");
|
|
108
|
-
return createClient(keys.url, secret ? keys.secretKey : keys.publishableKey, {
|
|
109
|
-
global: { headers: secret ? {} : { Authorization: `Bearer ${keys.secretKey}` } },
|
|
110
|
-
auth: { persistSession: false, autoRefreshToken: false }
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
function supabaseEditorClient(keys) {
|
|
114
|
-
const db = clientFor(keys);
|
|
115
|
-
const fail = (what, message) => new Error(`${what}: ${message}`);
|
|
116
|
-
return {
|
|
117
|
-
async listUsers() {
|
|
118
|
-
const res = await db.auth.admin.listUsers({ perPage: 200 });
|
|
119
|
-
if (res.error) throw fail("listing Auth users", res.error.message);
|
|
120
|
-
return res.data.users.map((u) => ({ id: u.id, email: u.email ?? null }));
|
|
121
|
-
},
|
|
122
|
-
async createUser(email, password2) {
|
|
123
|
-
const res = await db.auth.admin.createUser({ email, password: password2, email_confirm: true });
|
|
124
|
-
if (res.error) throw fail("creating the Auth user", res.error.message);
|
|
125
|
-
return { id: res.data.user.id, email: res.data.user.email ?? null };
|
|
126
|
-
},
|
|
127
|
-
async upsertEditor(userId, email, role) {
|
|
128
|
-
const res = await db.from("members").upsert({ user_id: userId, email, role }, { onConflict: "space_id,user_id" });
|
|
129
|
-
if (res.error) throw fail("inserting the members row", res.error.message);
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
81
|
// src/exec.ts
|
|
135
82
|
import { spawn } from "node:child_process";
|
|
136
83
|
var exec = (cmd, args, opts) => new Promise((resolve3, reject) => {
|
|
137
84
|
const child = spawn(cmd, args, {
|
|
138
85
|
cwd: opts.cwd,
|
|
139
|
-
stdio: [
|
|
86
|
+
stdio: ["inherit", opts.capture ? "pipe" : "inherit", "inherit"],
|
|
140
87
|
shell: process.platform === "win32"
|
|
141
88
|
});
|
|
142
89
|
let stdout = "";
|
|
@@ -147,80 +94,34 @@ var exec = (cmd, args, opts) => new Promise((resolve3, reject) => {
|
|
|
147
94
|
(code) => code === 0 ? resolve3({ stdout }) : reject(new Error(`\`${cmd} ${args.join(" ")}\` exited with code ${code}`))
|
|
148
95
|
);
|
|
149
96
|
});
|
|
150
|
-
async function hasCommand(cmd, run2, cwd) {
|
|
151
|
-
try {
|
|
152
|
-
await run2(cmd, ["--version"], { cwd, capture: true });
|
|
153
|
-
return true;
|
|
154
|
-
} catch {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// src/ports-net.ts
|
|
160
|
-
import { createServer } from "node:net";
|
|
161
|
-
function canListen(port2, host) {
|
|
162
|
-
return new Promise((resolve3) => {
|
|
163
|
-
const server = createServer();
|
|
164
|
-
server.once("error", () => resolve3(false));
|
|
165
|
-
server.listen(port2, host, () => server.close(() => resolve3(true)));
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
async function isPortFree(port2) {
|
|
169
|
-
return await canListen(port2, "127.0.0.1") && await canListen(port2, "0.0.0.0");
|
|
170
|
-
}
|
|
171
97
|
|
|
172
98
|
// src/prompts.ts
|
|
173
99
|
import * as p from "@clack/prompts";
|
|
174
100
|
import { resolve as resolve2 } from "node:path";
|
|
175
101
|
|
|
176
102
|
// src/env.ts
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
] : [
|
|
184
|
-
"# Smoodly, self-host: the content project's values. The live site reads",
|
|
185
|
-
"# with SMOODLY_URL and SMOODLY_SECRET_KEY; the admin also signs in with",
|
|
186
|
-
"# SMOODLY_PUBLISHABLE_KEY. SMOODLY_AUTH_URL and SMOODLY_AUTH_KEY default",
|
|
187
|
-
"# to these \u2014 leave them unset."
|
|
188
|
-
];
|
|
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) {
|
|
189
109
|
return [
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`
|
|
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}`] : [],
|
|
194
117
|
"",
|
|
195
|
-
"# The site's own address: absolute
|
|
196
|
-
"# 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.",
|
|
197
121
|
`SITE_URL=${siteUrl2}`,
|
|
198
122
|
""
|
|
199
123
|
].join("\n");
|
|
200
124
|
}
|
|
201
|
-
function normalizeProjectUrl(input) {
|
|
202
|
-
const raw = input.trim();
|
|
203
|
-
if (!raw) return null;
|
|
204
|
-
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
205
|
-
try {
|
|
206
|
-
const url = new URL(withScheme);
|
|
207
|
-
if (url.protocol !== "http:" && url.protocol !== "https:") return null;
|
|
208
|
-
if (!url.hostname) return null;
|
|
209
|
-
return url.origin;
|
|
210
|
-
} catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function projectRef(url) {
|
|
215
|
-
let host;
|
|
216
|
-
try {
|
|
217
|
-
host = new URL(url).hostname;
|
|
218
|
-
} catch {
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
const m = /^([a-z0-9]{20})\.supabase\.(co|in|red)$/.exec(host);
|
|
222
|
-
return m ? m[1] : null;
|
|
223
|
-
}
|
|
224
125
|
|
|
225
126
|
// src/scaffold.ts
|
|
226
127
|
import { cp, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
@@ -327,12 +228,35 @@ async function answer(value) {
|
|
|
327
228
|
if (p.isCancel(v)) bail("Cancelled.");
|
|
328
229
|
return v;
|
|
329
230
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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, typed) {
|
|
246
|
+
const hidden = flags2.apiUrl ? { apiUrl: flags2.apiUrl } : {};
|
|
247
|
+
if (typed.spaceId !== void 0 && typed.secretKey !== void 0 && typed.publishableKey !== void 0) {
|
|
248
|
+
return { spaceId: typed.spaceId, secretKey: typed.secretKey, publishableKey: typed.publishableKey, ...hidden };
|
|
249
|
+
}
|
|
250
|
+
if (!process.stdin.isTTY) throw new Error("smoodly: pass --space, --secret-key and --publishable-key \u2014 the space page at smoodly.io shows them.");
|
|
251
|
+
p.note("Create a project at smoodly.io and open its preview space; paste its values here.", "Smoodly");
|
|
252
|
+
const ask = (value) => answer(value).then((v) => v.trim());
|
|
253
|
+
const spaceId = typed.spaceId ?? await ask(p.text({ message: "Space id", placeholder: "sp_\u2026", validate: (v) => checkSpaceId((v ?? "").trim()) }));
|
|
254
|
+
const secretKey = typed.secretKey ?? await ask(p.password({ message: "Secret key", validate: (v) => checkSecretKey((v ?? "").trim()) }));
|
|
255
|
+
const publishableKey = typed.publishableKey ?? await ask(p.text({ message: "Publishable key", placeholder: "pk_\u2026", validate: (v) => checkPublishableKey((v ?? "").trim()) }));
|
|
256
|
+
return { spaceId, secretKey, publishableKey, ...hidden };
|
|
257
|
+
}
|
|
258
|
+
async function resolveAnswers(flags2, ctx) {
|
|
259
|
+
const typedFlags = typedValues(flags2);
|
|
336
260
|
const folderName = (typed2) => nameFromDir(resolve2(ctx.cwd, typed2));
|
|
337
261
|
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." }));
|
|
338
262
|
const dir = resolve2(ctx.cwd, typed);
|
|
@@ -371,67 +295,8 @@ async function resolveAnswers(flags2, ctx, env = process.env) {
|
|
|
371
295
|
]
|
|
372
296
|
})
|
|
373
297
|
));
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
message: "Where should content live?",
|
|
377
|
-
options: [
|
|
378
|
-
{ value: "local", label: "Local Supabase stack", hint: "supabase start inside the new app; needs the Supabase CLI and Docker" },
|
|
379
|
-
{ value: "hosted", label: "Hosted Supabase project", hint: "paste the project URL and keys" }
|
|
380
|
-
]
|
|
381
|
-
})
|
|
382
|
-
));
|
|
383
|
-
if (supabase === "local") {
|
|
384
|
-
if (!ctx.hasSupabaseCli) bail("Local mode needs the Supabase CLI: https://supabase.com/docs/guides/cli (brew install supabase/tap/supabase)");
|
|
385
|
-
if (flags2.editorEmail && !env.SMOODLY_EDITOR_PASSWORD) throw new Error(`Set SMOODLY_EDITOR_PASSWORD to create ${flags2.editorEmail}.`);
|
|
386
|
-
return { name, dir, supabase, hasSupabaseCli: true, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
387
|
-
}
|
|
388
|
-
const badUrl = "Supabase project URL must be an http(s) URL, like https://abcd.supabase.co";
|
|
389
|
-
const typedUrl = flags2.url ?? await answer(
|
|
390
|
-
p.text({
|
|
391
|
-
message: "Supabase project URL",
|
|
392
|
-
placeholder: "https://abcd.supabase.co",
|
|
393
|
-
validate: (v) => normalizeProjectUrl(v ?? "") ? void 0 : badUrl
|
|
394
|
-
})
|
|
395
|
-
);
|
|
396
|
-
const url = normalizeProjectUrl(typedUrl);
|
|
397
|
-
if (!url) throw new Error(`${badUrl} (got "${typedUrl}")`);
|
|
398
|
-
const publishableKey = flags2.publishableKey ?? await answer(p.text({ message: "Publishable (anon) key" }));
|
|
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
|
-
}
|
|
403
|
-
const ref = projectRef(url);
|
|
404
|
-
if (ctx.hasSupabaseCli && !flags2.skipLink && !ref) {
|
|
405
|
-
p.log.warn(`No project ref in ${url}; skipping supabase link. Run \`supabase link --project-ref <project-ref>\` and \`supabase db push\` yourself.`);
|
|
406
|
-
}
|
|
407
|
-
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)` }));
|
|
408
|
-
return { name, dir, supabase, hasSupabaseCli: ctx.hasSupabaseCli, hosted: { keys: { url, publishableKey, secretKey }, linkAndPush }, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
409
|
-
}
|
|
410
|
-
function askEditorWith(flags2, env = process.env) {
|
|
411
|
-
return async (existing) => {
|
|
412
|
-
if (flags2.editorEmail) {
|
|
413
|
-
const found = existing.find((u) => u.email === flags2.editorEmail);
|
|
414
|
-
if (found) return { kind: "pick", user: found };
|
|
415
|
-
const password3 = env.SMOODLY_EDITOR_PASSWORD;
|
|
416
|
-
if (!password3) throw new Error(`Set SMOODLY_EDITOR_PASSWORD to create ${flags2.editorEmail}.`);
|
|
417
|
-
return { kind: "create", email: flags2.editorEmail, password: password3 };
|
|
418
|
-
}
|
|
419
|
-
if (existing.length > 0) {
|
|
420
|
-
const picked = await answerOrThrow(
|
|
421
|
-
p.select({
|
|
422
|
-
message: "Who is the first editor?",
|
|
423
|
-
options: [
|
|
424
|
-
...existing.map((u) => ({ value: u.id, label: u.email ?? u.id })),
|
|
425
|
-
{ value: "__new__", label: "Create a new user" }
|
|
426
|
-
]
|
|
427
|
-
})
|
|
428
|
-
);
|
|
429
|
-
if (picked !== "__new__") return { kind: "pick", user: existing.find((u) => u.id === picked) };
|
|
430
|
-
}
|
|
431
|
-
const email = await answerOrThrow(p.text({ message: "Editor email", validate: (v) => v && v.includes("@") ? void 0 : "An email address" }));
|
|
432
|
-
const password2 = await answerOrThrow(p.password({ message: "Editor password", validate: (v) => v && v.length >= 8 ? void 0 : "At least 8 characters" }));
|
|
433
|
-
return { kind: "create", email, password: password2 };
|
|
434
|
-
};
|
|
298
|
+
const space = await spaceValues(flags2, typedFlags);
|
|
299
|
+
return { name, dir, space, smoodlyDep: flags2.smoodlyDep, locales, starter, framework };
|
|
435
300
|
}
|
|
436
301
|
|
|
437
302
|
// src/run.ts
|
|
@@ -442,77 +307,18 @@ function templateName(framework, starter, locales) {
|
|
|
442
307
|
const base = locales === "intl" ? `${starter}-intl` : starter;
|
|
443
308
|
return framework === "astro" ? `astro-${base}` : base;
|
|
444
309
|
}
|
|
445
|
-
function plan(
|
|
446
|
-
|
|
447
|
-
throw new Error("Local mode needs the Supabase CLI: https://supabase.com/docs/guides/cli (brew install supabase/tap/supabase)");
|
|
448
|
-
}
|
|
449
|
-
const steps = ["copyTemplate", "install"];
|
|
450
|
-
if (a.supabase === "local") steps.push("supabaseLocal");
|
|
451
|
-
else if (a.hasSupabaseCli && !a.hosted?.cloud) {
|
|
452
|
-
steps.push("supabaseInit");
|
|
453
|
-
if (a.hosted?.linkAndPush) steps.push("linkAndPush");
|
|
454
|
-
}
|
|
455
|
-
steps.push("writeEnv", ...a.hosted?.cloud ? [] : ["ensureEditor"], "seed");
|
|
456
|
-
steps.push("gitInit", "finish");
|
|
457
|
-
return steps;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
// src/ports.ts
|
|
461
|
-
var DEFAULT_BASE = 54300;
|
|
462
|
-
var BASES = Array.from({ length: 10 }, (_, i) => DEFAULT_BASE + i * 1e3);
|
|
463
|
-
var PORT_LINE = /^([ \t]*#?[ \t]*[a-z0-9_]*port[ \t]*=[ \t]*)543(\d\d)([ \t]*(?:#.*)?)$/gm;
|
|
464
|
-
var LIVE_PORT_LINE = /^[ \t]*[a-z0-9_]*port[ \t]*=[ \t]*(543\d\d)[ \t]*(?:#.*)?$/gm;
|
|
465
|
-
function blockPorts(toml) {
|
|
466
|
-
return [...toml.matchAll(LIVE_PORT_LINE)].map((m) => Number(m[1]));
|
|
467
|
-
}
|
|
468
|
-
function shiftBlock(toml, base) {
|
|
469
|
-
return toml.replace(PORT_LINE, (_m, pre, xx, post) => `${pre}${base + Number(xx)}${post}`);
|
|
470
|
-
}
|
|
471
|
-
async function findFreeBlock(ports, isFree, bases = BASES) {
|
|
472
|
-
for (const base of bases) {
|
|
473
|
-
const shifted = ports.map((p3) => base + (p3 - DEFAULT_BASE));
|
|
474
|
-
let allFree = true;
|
|
475
|
-
for (const port2 of shifted) {
|
|
476
|
-
if (!await isFree(port2)) {
|
|
477
|
-
allFree = false;
|
|
478
|
-
break;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
if (allFree) return base;
|
|
482
|
-
}
|
|
483
|
-
throw new Error(`No free Supabase port block: tried ${bases.join(", ")}. Stop another local Supabase project and run again.`);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// src/supabase-cli.ts
|
|
487
|
-
function parseStatusEnv(output) {
|
|
488
|
-
const vars = /* @__PURE__ */ new Map();
|
|
489
|
-
for (const line of output.split("\n")) {
|
|
490
|
-
const m = /^([A-Z_]+)=(?:"([^"]*)"|(.*))$/.exec(line.trim());
|
|
491
|
-
if (m) vars.set(m[1], m[2] ?? m[3]);
|
|
492
|
-
}
|
|
493
|
-
const url = vars.get("API_URL");
|
|
494
|
-
const publishableKey = vars.get("ANON_KEY");
|
|
495
|
-
const secretKey = vars.get("SERVICE_ROLE_KEY");
|
|
496
|
-
if (!url || !publishableKey || !secretKey) {
|
|
497
|
-
throw new Error("`supabase status -o env` did not report API_URL, ANON_KEY and SERVICE_ROLE_KEY");
|
|
498
|
-
}
|
|
499
|
-
return { url, publishableKey, secretKey };
|
|
310
|
+
function plan(_a) {
|
|
311
|
+
return ["copyTemplate", "install", "writeEnv", "push", "seed", "gitInit", "finish"];
|
|
500
312
|
}
|
|
501
313
|
|
|
502
314
|
// src/run.ts
|
|
503
|
-
var LOCAL_START_ARGS = ["start", "-x", "imgproxy,edge-runtime,logflare,vector,supavisor"];
|
|
504
315
|
async function run(answers2, deps) {
|
|
505
|
-
if (answers2.supabase === "hosted" && !answers2.hosted) {
|
|
506
|
-
throw new Error("hosted answers need the project's keys");
|
|
507
|
-
}
|
|
508
316
|
if (!await deps.fs.isEmptyOrMissing(answers2.dir)) {
|
|
509
317
|
throw new Error(`${answers2.dir} exists and is not empty.`);
|
|
510
318
|
}
|
|
511
|
-
const steps = plan(answers2);
|
|
512
|
-
const ctx = { keys: answers2.hosted?.keys };
|
|
513
319
|
const cwd = answers2.dir;
|
|
514
320
|
const sh = (cmd, args, extra = {}) => deps.exec(cmd, args, { cwd, ...extra });
|
|
515
|
-
for (const step of
|
|
321
|
+
for (const step of plan(answers2)) {
|
|
516
322
|
try {
|
|
517
323
|
switch (step) {
|
|
518
324
|
case "copyTemplate":
|
|
@@ -530,58 +336,24 @@ async function run(answers2, deps) {
|
|
|
530
336
|
deps.log("Installing dependencies\u2026");
|
|
531
337
|
await sh("npm", ["install"]);
|
|
532
338
|
break;
|
|
533
|
-
case "gitInit":
|
|
534
|
-
if (answers2.framework === "next") await deps.agentFiles?.(cwd).catch(() => {
|
|
535
|
-
});
|
|
536
|
-
await sh("git", ["init"], { capture: true });
|
|
537
|
-
await sh("git", ["add", "-A"], { capture: true });
|
|
538
|
-
await sh("git", ["commit", "-m", "Smoodly site"], { capture: true });
|
|
539
|
-
break;
|
|
540
|
-
case "supabaseLocal": {
|
|
541
|
-
deps.log("Starting the local Supabase stack (first run pulls Docker images)\u2026");
|
|
542
|
-
await sh("supabase", ["init"], { stdin: "ignore" });
|
|
543
|
-
const configPath = join2(cwd, "supabase/config.toml");
|
|
544
|
-
const toml = withoutSeedSql(await deps.fs.readFile(configPath));
|
|
545
|
-
const ports = blockPorts(toml);
|
|
546
|
-
const base = await findFreeBlock(ports, deps.isPortFree);
|
|
547
|
-
await deps.fs.writeFile(configPath, base !== DEFAULT_BASE ? shiftBlock(toml, base) : toml);
|
|
548
|
-
if (base !== DEFAULT_BASE) {
|
|
549
|
-
deps.log(`Ports ${DEFAULT_BASE / 100}xx are busy (another Supabase project running?), using ${base / 100}xx.`);
|
|
550
|
-
}
|
|
551
|
-
try {
|
|
552
|
-
await sh("supabase", LOCAL_START_ARGS);
|
|
553
|
-
} catch (e) {
|
|
554
|
-
const shifted = ports.map((p3) => base + (p3 - DEFAULT_BASE));
|
|
555
|
-
const taken = (await Promise.all(shifted.map(deps.isPortFree))).some((free) => !free);
|
|
556
|
-
if (!taken) throw e;
|
|
557
|
-
const next = await findFreeBlock(ports, deps.isPortFree);
|
|
558
|
-
await deps.fs.writeFile(configPath, shiftBlock(toml, next));
|
|
559
|
-
deps.log(`Ports ${base / 100}xx were taken while starting (another Supabase project?), retrying on ${next / 100}xx.`);
|
|
560
|
-
await sh("supabase", LOCAL_START_ARGS);
|
|
561
|
-
}
|
|
562
|
-
ctx.keys = parseStatusEnv((await sh("supabase", ["status", "-o", "env"], { capture: true })).stdout);
|
|
563
|
-
break;
|
|
564
|
-
}
|
|
565
|
-
case "supabaseInit": {
|
|
566
|
-
await sh("supabase", ["init"], { stdin: "ignore" });
|
|
567
|
-
const configPath = join2(cwd, "supabase/config.toml");
|
|
568
|
-
await deps.fs.writeFile(configPath, withoutSeedSql(await deps.fs.readFile(configPath)));
|
|
569
|
-
break;
|
|
570
|
-
}
|
|
571
|
-
case "linkAndPush":
|
|
572
|
-
await sh("supabase", linkArgs(answers2));
|
|
573
|
-
await sh("supabase", ["db", "push"]);
|
|
574
|
-
break;
|
|
575
339
|
case "writeEnv":
|
|
576
|
-
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(
|
|
340
|
+
await deps.fs.writeFile(join2(cwd, envFile(answers2)), renderEnv(answers2.space, siteUrl(answers2)));
|
|
577
341
|
break;
|
|
578
|
-
case "
|
|
579
|
-
|
|
342
|
+
case "push":
|
|
343
|
+
deps.log("Pushing the site's schema to the space\u2026");
|
|
344
|
+
await sh("npx", ["smoodly", "push"]);
|
|
580
345
|
break;
|
|
581
346
|
case "seed":
|
|
582
347
|
deps.log("Seeding the starter content\u2026");
|
|
583
348
|
await sh("npm", ["run", "seed"]);
|
|
584
349
|
break;
|
|
350
|
+
case "gitInit":
|
|
351
|
+
if (answers2.framework === "next") await deps.agentFiles?.(cwd).catch(() => {
|
|
352
|
+
});
|
|
353
|
+
await sh("git", ["init"], { capture: true });
|
|
354
|
+
await sh("git", ["add", "-A"], { capture: true });
|
|
355
|
+
await sh("git", ["commit", "-m", "Smoodly site"], { capture: true });
|
|
356
|
+
break;
|
|
585
357
|
case "finish":
|
|
586
358
|
break;
|
|
587
359
|
}
|
|
@@ -595,57 +367,29 @@ async function run(answers2, deps) {
|
|
|
595
367
|
deps.log(`Skipped the initial git commit (${error}). Commit when you like.`);
|
|
596
368
|
continue;
|
|
597
369
|
}
|
|
598
|
-
return { ok: false, step, error, fallback: fallbackFor(step, answers2
|
|
370
|
+
return { ok: false, step, error, fallback: fallbackFor(step, answers2) };
|
|
599
371
|
}
|
|
600
372
|
}
|
|
601
|
-
return
|
|
373
|
+
return { ok: true };
|
|
602
374
|
}
|
|
603
|
-
var withoutSeedSql = (toml) => toml.replace(/(\[db\.seed\]\n(?:#.*\n)*enabled = )true/, "$1false");
|
|
604
375
|
var envFile = (answers2) => answers2.framework === "astro" ? ".env" : ".env.local";
|
|
605
376
|
var siteUrl = (answers2) => answers2.framework === "astro" ? "http://localhost:4321" : "http://localhost:3000";
|
|
606
|
-
function fallbackFor(step, answers2
|
|
607
|
-
const
|
|
608
|
-
|
|
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)}.`;
|
|
610
|
-
const editorLines = answers2.hosted?.cloud ? ["Then the starter content:", "", `cd ${answers2.dir} && npm run seed`].join("\n") : [
|
|
611
|
-
"Create the first editor, then the starter content:",
|
|
612
|
-
"",
|
|
613
|
-
`cd ${answers2.dir} && SMOODLY_ADMIN_PASSWORD=<a password> npx smoodly add-admin <your email>`,
|
|
614
|
-
`cd ${answers2.dir} && npm run seed`
|
|
615
|
-
].join("\n");
|
|
377
|
+
function fallbackFor(step, answers2) {
|
|
378
|
+
const finish = `cd ${answers2.dir} && npx smoodly push && npm run seed`;
|
|
616
379
|
switch (step) {
|
|
617
|
-
case "
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
return [...commands, "", envLines, "", editorLines].join("\n");
|
|
380
|
+
case "writeEnv": {
|
|
381
|
+
const masked = { ...answers2.space, secretKey: "<the secret key you pasted>", publishableKey: "<the publishable key you pasted>" };
|
|
382
|
+
const lines = renderEnv(masked, siteUrl(answers2)).split("\n").filter((l) => !l.startsWith("#")).join("\n").replace(/\n{2,}/g, "\n").trim();
|
|
383
|
+
return [`Write ${answers2.dir}/${envFile(answers2)}:`, "", lines, "", "Then the schema and the starter content:", "", finish].join("\n");
|
|
622
384
|
}
|
|
623
|
-
case "
|
|
624
|
-
return
|
|
625
|
-
case "writeEnv":
|
|
626
|
-
return [envLines, "", editorLines].join("\n");
|
|
627
|
-
case "ensureEditor":
|
|
628
|
-
return [
|
|
629
|
-
"If the members table is missing, apply the migration first: `supabase db push` (hosted) or `supabase start` (local).",
|
|
630
|
-
"",
|
|
631
|
-
editorLines
|
|
632
|
-
].join("\n");
|
|
385
|
+
case "push":
|
|
386
|
+
return finish;
|
|
633
387
|
case "seed":
|
|
634
388
|
return `cd ${answers2.dir} && npm run seed`;
|
|
635
389
|
default:
|
|
636
390
|
return "";
|
|
637
391
|
}
|
|
638
392
|
}
|
|
639
|
-
function linkArgs(answers2) {
|
|
640
|
-
const url = answers2.hosted?.keys.url ?? "";
|
|
641
|
-
const ref = projectRef(url);
|
|
642
|
-
if (!ref) throw new Error(`no project ref in ${url}; run \`supabase link --project-ref <project-ref>\` yourself`);
|
|
643
|
-
return ["link", "--project-ref", ref];
|
|
644
|
-
}
|
|
645
|
-
function linkCommand(answers2) {
|
|
646
|
-
const ref = answers2.hosted ? projectRef(answers2.hosted.keys.url) : null;
|
|
647
|
-
return `supabase link --project-ref ${ref ?? "<project-ref>"}`;
|
|
648
|
-
}
|
|
649
393
|
|
|
650
394
|
// src/index.ts
|
|
651
395
|
var TEMPLATE_DIRS = {
|
|
@@ -674,20 +418,15 @@ p2.intro("create-smoodly-app");
|
|
|
674
418
|
var answers;
|
|
675
419
|
var outcome;
|
|
676
420
|
try {
|
|
677
|
-
|
|
678
|
-
answers = await resolveAnswers(flags, { hasSupabaseCli, cwd: process.cwd() });
|
|
421
|
+
answers = await resolveAnswers(flags, { cwd: process.cwd() });
|
|
679
422
|
const existed = existsSync(answers.dir);
|
|
680
423
|
outcome = await run(answers, {
|
|
681
424
|
exec,
|
|
682
425
|
templateDirs: TEMPLATE_DIRS,
|
|
683
|
-
editorClient: supabaseEditorClient,
|
|
684
|
-
askEditor: askEditorWith(flags),
|
|
685
426
|
log: (line) => p2.log.step(line),
|
|
686
|
-
isPortFree,
|
|
687
427
|
fs: {
|
|
688
428
|
isEmptyOrMissing,
|
|
689
429
|
scaffold,
|
|
690
|
-
readFile: (path) => readFile2(path, "utf8"),
|
|
691
430
|
writeFile: (path, text2) => writeFile2(path, text2),
|
|
692
431
|
rm: async (dir) => {
|
|
693
432
|
if (!existed) return rm(dir, { recursive: true, force: true });
|
|
@@ -716,7 +455,7 @@ p2.note(
|
|
|
716
455
|
"",
|
|
717
456
|
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)`,
|
|
718
457
|
`Admin: http://localhost:${port}/admin`,
|
|
719
|
-
|
|
458
|
+
`Space: ${answers.space.spaceId} (editors are invited from the project at smoodly.io)`,
|
|
720
459
|
...answers.locales ? [`Locales: ${answers.locales.join(", ")} (${answers.framework === "astro" ? "astro.config.ts and src/smoodly/config.ts" : "i18n/routing.ts"})`] : []
|
|
721
460
|
].join("\n"),
|
|
722
461
|
"Ready"
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-smoodly-app",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Create a Smoodly site: a fresh Next.js app with the admin mounted, a
|
|
3
|
+
"version": "0.0.53",
|
|
4
|
+
"description": "Create a Smoodly site: a fresh Astro or Next.js app with the admin mounted, its env file on a space's three values, its schema pushed and its starter content seeded.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/
|
|
8
|
+
"url": "git+https://github.com/smoodly-io/smoodly.git",
|
|
9
9
|
"directory": "packages/create-smoodly-app"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"typecheck:templates": "npm run build:template && tsc -p templates/lorem.tsconfig.json && tsc -p templates/lorem-intl.tsconfig.json && tsc -p templates/blank.tsconfig.json && tsc -p templates/blank-intl.tsconfig.json && tsc -p templates/astro-lorem.tsconfig.json && tsc -p templates/astro-lorem-intl.tsconfig.json && tsc -p templates/astro-blank.tsconfig.json && tsc -p templates/astro-blank-intl.tsconfig.json"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@clack/prompts": "^1.7.0"
|
|
33
|
-
"@supabase/supabase-js": "^2.113.0"
|
|
32
|
+
"@clack/prompts": "^1.7.0"
|
|
34
33
|
},
|
|
35
34
|
"devDependencies": {
|
|
36
35
|
"@types/node": "^26.4.1",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Working in this Smoodly site
|
|
2
2
|
|
|
3
|
-
This is an Astro site with Smoodly installed: content lives in
|
|
3
|
+
This is an Astro site with Smoodly installed: content lives in Smoodly's service, reached through the API, the admin is mounted at /admin by the `smoodly/astro` integration in `astro.config.ts`, and the site reads content through live collections (`src/live.config.ts`). Read README.md first: it says where things are.
|
|
4
4
|
|
|
5
5
|
## Rules
|
|
6
6
|
|
|
@@ -14,12 +14,6 @@ This is an Astro site with Smoodly installed: content lives in Supabase, the adm
|
|
|
14
14
|
names a schema with no file, and a file with no schema, at the first request.
|
|
15
15
|
- **A `.schema.ts` file imports no component.** Zones, toolsets, global items and
|
|
16
16
|
`node()` take schemas (`z.locked(heroSchema)`), never views.
|
|
17
|
-
- **After a new collection or a `smoodly` upgrade, run `npx smoodly migrate`**
|
|
18
|
-
and apply what it writes (`supabase db push` hosted, `supabase migration up`
|
|
19
|
-
local). It copies the core schema files the package ships and writes a
|
|
20
|
-
read view and order index per collection. Never edit a file it wrote.
|
|
21
|
-
The admin reads the base tables, so it lists a collection either way;
|
|
22
|
-
the view is for SQL and PostgREST readers, the index for `order`.
|
|
23
17
|
- **Content from code goes through `createContent(config)`** from `smoodly/astro`:
|
|
24
18
|
`scripts/seed.ts` is starter content written with it — the admin's own
|
|
25
19
|
operations, so validation and publish checks apply and a failure throws. Never
|
|
@@ -49,7 +43,7 @@ This is an Astro site with Smoodly installed: content lives in Supabase, the adm
|
|
|
49
43
|
## Commands
|
|
50
44
|
|
|
51
45
|
npm run dev # http://localhost:4321, /admin is the editor
|
|
52
|
-
npm run build #
|
|
46
|
+
npm run build # astro build
|
|
53
47
|
npm run start # the built server (dist/server/entry.mjs)
|
|
54
48
|
npm run seed # starter content, idempotent
|
|
55
49
|
npm run typecheck # astro check
|