@rocapine/community 0.1.0

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +118 -0
  3. package/lib/commands/adopt.d.ts +38 -0
  4. package/lib/commands/adopt.js +105 -0
  5. package/lib/commands/init.d.ts +29 -0
  6. package/lib/commands/init.js +152 -0
  7. package/lib/commands/upgrade.d.ts +33 -0
  8. package/lib/commands/upgrade.js +280 -0
  9. package/lib/index.d.ts +2 -0
  10. package/lib/index.js +123 -0
  11. package/lib/install-shared.d.ts +72 -0
  12. package/lib/install-shared.js +256 -0
  13. package/lib/manifest.d.ts +53 -0
  14. package/lib/manifest.js +74 -0
  15. package/lib/substitute.d.ts +15 -0
  16. package/lib/substitute.js +25 -0
  17. package/package.json +32 -0
  18. package/templates/functions/_shared/client.ts +20 -0
  19. package/templates/functions/_shared/config.ts +23 -0
  20. package/templates/functions/_shared/moderation.ts +102 -0
  21. package/templates/functions/_shared/push.ts +33 -0
  22. package/templates/functions/_shared/slack.ts +12 -0
  23. package/templates/functions/broadcast-post/index.ts +73 -0
  24. package/templates/functions/daily-moderation/index.ts +175 -0
  25. package/templates/functions/moderate-one/index.ts +62 -0
  26. package/templates/functions/notify-comment/index.ts +63 -0
  27. package/templates/functions/notify-like/index.ts +103 -0
  28. package/templates/functions/notify-reaction/index.ts +186 -0
  29. package/templates/functions/report-to-slack/index.ts +37 -0
  30. package/templates/functions/update-profile/index.ts +131 -0
  31. package/templates/migrations/core/001_tables.sql +177 -0
  32. package/templates/migrations/core/002_rls.sql +154 -0
  33. package/templates/migrations/core/003_moderation.sql +68 -0
  34. package/templates/migrations/core/004_dashboard.sql +102 -0
  35. package/templates/migrations/core/005_avatars.sql +19 -0
  36. package/templates/migrations/core/006_meta.sql +11 -0
  37. package/templates/migrations/inbox/001_inbox.sql +237 -0
  38. package/templates/migrations/polls/001_polls.sql +120 -0
  39. package/templates/migrations/push/001_push.sql +42 -0
  40. package/templates/migrations/push/002_triggers.sql +89 -0
  41. package/templates/migrations/reaction/001_reactions.sql +139 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Rocapine
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # @rocapine/community (CLI)
2
+
3
+ Installs and manages the Community SDK backend — Supabase migrations and
4
+ Edge Functions — inside your app's own Supabase project.
5
+
6
+ ```bash
7
+ npx @rocapine/community <command>
8
+ ```
9
+
10
+ No install needed for one-off use; `npx` always runs the latest published
11
+ version. The binary is named `community`.
12
+
13
+ ## `init`
14
+
15
+ ```bash
16
+ npx @rocapine/community init [--modules core,push,polls,reaction,inbox] [--project-url <url>] [--anon-key <key>] [--dir supabase]
17
+ ```
18
+
19
+ Copies the migrations and Edge Functions for the requested modules into
20
+ `<dir>/migrations/` and `<dir>/functions/` (default `dir`: `supabase`,
21
+ relative to the current directory), and writes a `community-sdk.json`
22
+ manifest at the repo root recording what was installed.
23
+
24
+ - `--modules` — comma-separated (`core,push,polls,reaction,inbox`). `core`
25
+ is always implied even if omitted. Omit the flag entirely to install every
26
+ module. Regardless of the order you type them, modules are installed
27
+ `core → push → polls → reaction → inbox` — this order is load-bearing:
28
+ the inbox module's reaction trigger is guarded at install time and must
29
+ find the reaction module's table already present, so reaction has to land
30
+ first. Requesting `inbox` without `reaction` prints a (non-fatal) warning.
31
+ - `--project-url` / `--anon-key` — needed only if a migration in the
32
+ selected modules carries a placeholder (see "Placeholder guard" below).
33
+ If omitted, the CLI tries to read the project URL from
34
+ `<dir>/config.toml`'s `project_id`, then prompts interactively; if stdin
35
+ isn't a TTY and a value is still missing, it fails loudly rather than
36
+ hanging.
37
+ - `--dir` — target Supabase directory, default `supabase`.
38
+ - Refuses to run if `community-sdk.json` already exists (use `upgrade`
39
+ instead) — see `ALREADY_INITIALIZED_MESSAGE`.
40
+ - All-or-nothing: if anything fails partway through, every file this run
41
+ wrote is removed before the error propagates, so a retry starts clean.
42
+
43
+ Prints next steps on success: review + `supabase db push`, set
44
+ `OPENAI_API_KEY` (and optionally `SLACK_WEBHOOK_URL`,
45
+ `COMMUNITY_APP_NAME`, `COMMUNITY_FALLBACK_NAME`, and — for the push module —
46
+ `EXPO_ACCESS_TOKEN`), then `supabase functions deploy`.
47
+
48
+ ## `upgrade`
49
+
50
+ ```bash
51
+ npx @rocapine/community upgrade [--project-url <url>] [--anon-key <key>] [--dir supabase]
52
+ ```
53
+
54
+ Copies any migration templates shipped since `community-sdk.json` was last
55
+ written (diffed by stable template identity, `<module>/<templateBaseName>`,
56
+ not by filename — a template's numeric ordering prefix is not part of its
57
+ identity), and copies in any Edge Function present in the shipped set but
58
+ missing on disk. An Edge Function that already exists on disk but whose
59
+ content differs from the shipped template is **overwritten, with a
60
+ warning** — review the diff before deploying. `--project-url`/`--anon-key`
61
+ are only needed if a newly-added migration carries a placeholder. Fails if
62
+ `community-sdk.json` doesn't exist yet (run `init` or `adopt` first).
63
+
64
+ ## `adopt`
65
+
66
+ ```bash
67
+ npx @rocapine/community adopt --schema-version <n> --modules core,push,... [--dir supabase]
68
+ ```
69
+
70
+ For a backend that's already live in production, built by hand or migrated
71
+ before this SDK existed (the mold precedent: Eve's Rhythm and other internal
72
+ apps that hand-rolled their own community backend before this SDK existed).
73
+ Writes **only** `community-sdk.json` — no migration or function file is ever
74
+ copied — on the assumption the equivalent schema is already applied. A
75
+ subsequent `upgrade` will still seed any Edge Function source not yet
76
+ present in the repo (for review before deploy), since `adopt` never writes
77
+ function files. If `--schema-version` doesn't match the CLI's current
78
+ schema version, it warns that `installedTemplates` was reconstructed from
79
+ the _current_ template set and points at `docs/compat.md` to check/adjust
80
+ `community-sdk.json` by hand for a historical schema.
81
+
82
+ ## The manifest (`community-sdk.json`)
83
+
84
+ ```json
85
+ {
86
+ "schemaVersion": 1,
87
+ "sdkVersion": "0.1.0",
88
+ "modules": ["core", "push", "polls", "reaction", "inbox"],
89
+ "installedFiles": ["supabase/migrations/20260101000000_community_core_tables.sql", "..."],
90
+ "installedTemplates": ["core/tables", "core/rls", "..."]
91
+ }
92
+ ```
93
+
94
+ - `schemaVersion` — the schema version this app's backend corresponds to
95
+ (see `docs/compat.md`).
96
+ - `sdkVersion` — the CLI package version that last touched the manifest.
97
+ - `modules` — canonical, ordered module list installed/adopted.
98
+ - `installedFiles` — every file path this CLI has written (empty for an
99
+ `adopt`ed backend, since it copies nothing).
100
+ - `installedTemplates` — stable `<module>/<templateBaseName>` identities of
101
+ every migration template known to be applied; this, not `installedFiles`,
102
+ is what `upgrade` diffs against (timestamped destination filenames can't
103
+ be compared meaningfully run to run).
104
+
105
+ ## Placeholder guard
106
+
107
+ Three backend migration files (`core/003_moderation.sql`,
108
+ `push/002_triggers.sql`, `reaction/001_reactions.sql`) embed the project's
109
+ own URL/anon key (pg_net crons and webhooks that call back into the same
110
+ project) as `__SUPABASE_PROJECT_URL__` / `__SUPABASE_ANON_KEY__`
111
+ placeholders in the shipped template. `init`/`upgrade` substitute real
112
+ values before writing the
113
+ file to disk; if a value is missing and can't be resolved (flag, then
114
+ `config.toml`, then a TTY prompt), the command fails rather than writing a
115
+ file with a literal placeholder still in it — a migration with an
116
+ unsubstituted placeholder would push successfully to Postgres and then fail
117
+ silently at cron/webhook runtime, so this guard turns that into a loud,
118
+ immediate CLI error instead.
@@ -0,0 +1,38 @@
1
+ import { type Manifest } from "../manifest";
2
+ /**
3
+ * `community adopt` is for an app whose community backend already exists in
4
+ * production (hand-rolled before this SDK existed) rather than one being
5
+ * installed fresh. It writes ONLY community-sdk.json — no migration or
6
+ * function file ever touches disk — so `installedFiles` is always `[]`.
7
+ *
8
+ * `installedTemplates`, however, IS populated: it's set to every migration
9
+ * template this CLI currently ships for the adopted modules, on the
10
+ * assumption that a live/adopted schema already has everything up to the
11
+ * current template set (the only schemaVersion this CLI knows how to
12
+ * reconstruct — see the `--schema-version` mismatch warning below). This is
13
+ * what lets a subsequent `upgrade` recognize "nothing new to add" for
14
+ * migrations instead of trying to re-apply the app's entire schema history.
15
+ * It deliberately does NOT extend the same courtesy to functions: those are
16
+ * diffed by `upgrade` directly against files on disk, and adopt writes none,
17
+ * so the first `upgrade` after an `adopt` will (correctly) seed the
18
+ * Edge Function source into the repo for review before it's deployed.
19
+ */
20
+ export declare const ALREADY_INITIALIZED_MESSAGE_ADOPT = "community-sdk: already initialized (found community-sdk.json) \u2014 adopt is only for a repo with no manifest yet";
21
+ export interface AdoptOptions {
22
+ schemaVersion: number;
23
+ modules: string[];
24
+ /** Target Supabase directory, relative to `cwd`. Defaults to "supabase". Validated but otherwise unused — adopt copies nothing. */
25
+ dir?: string;
26
+ /** App repo root — where community-sdk.json lives. Defaults to process.cwd(). */
27
+ cwd?: string;
28
+ /** Source of the migrations/ tree, used only to compute `installedTemplates`. Defaults to the shipped templates. */
29
+ templatesDir?: string;
30
+ /** Called for non-fatal warnings (unknown-module-adjacent dependency warnings, schema-version mismatch). */
31
+ onWarn?: (message: string) => void;
32
+ /** Used to print the summary + compat-table pointer. */
33
+ log?: (message: string) => void;
34
+ }
35
+ export interface AdoptResult {
36
+ manifest: Manifest;
37
+ }
38
+ export declare function runAdopt(options: AdoptOptions): Promise<AdoptResult>;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ALREADY_INITIALIZED_MESSAGE_ADOPT = void 0;
37
+ exports.runAdopt = runAdopt;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const manifest_1 = require("../manifest");
41
+ const install_shared_1 = require("../install-shared");
42
+ /**
43
+ * `community adopt` is for an app whose community backend already exists in
44
+ * production (hand-rolled before this SDK existed) rather than one being
45
+ * installed fresh. It writes ONLY community-sdk.json — no migration or
46
+ * function file ever touches disk — so `installedFiles` is always `[]`.
47
+ *
48
+ * `installedTemplates`, however, IS populated: it's set to every migration
49
+ * template this CLI currently ships for the adopted modules, on the
50
+ * assumption that a live/adopted schema already has everything up to the
51
+ * current template set (the only schemaVersion this CLI knows how to
52
+ * reconstruct — see the `--schema-version` mismatch warning below). This is
53
+ * what lets a subsequent `upgrade` recognize "nothing new to add" for
54
+ * migrations instead of trying to re-apply the app's entire schema history.
55
+ * It deliberately does NOT extend the same courtesy to functions: those are
56
+ * diffed by `upgrade` directly against files on disk, and adopt writes none,
57
+ * so the first `upgrade` after an `adopt` will (correctly) seed the
58
+ * Edge Function source into the repo for review before it's deployed.
59
+ */
60
+ exports.ALREADY_INITIALIZED_MESSAGE_ADOPT = "community-sdk: already initialized (found community-sdk.json) — adopt is only for a repo with no manifest yet";
61
+ async function runAdopt(options) {
62
+ const cwd = options.cwd ?? process.cwd();
63
+ const log = options.log ?? ((message) => console.log(message));
64
+ const onWarn = options.onWarn ?? ((message) => console.warn(message));
65
+ if ((0, manifest_1.readManifest)(cwd)) {
66
+ throw new Error(exports.ALREADY_INITIALIZED_MESSAGE_ADOPT);
67
+ }
68
+ if (!Number.isInteger(options.schemaVersion) || options.schemaVersion < 1) {
69
+ throw new Error(`community-sdk: --schema-version must be a positive integer, got ${options.schemaVersion}`);
70
+ }
71
+ const canonicalModules = (0, install_shared_1.resolveModules)(options.modules, onWarn);
72
+ const dirRel = options.dir ?? "supabase";
73
+ // Validated for consistency with init/upgrade (and to catch typos early)
74
+ // even though adopt never writes under it.
75
+ (0, install_shared_1.resolveContainedDir)(cwd, dirRel);
76
+ const templatesDir = options.templatesDir ?? (0, install_shared_1.defaultTemplatesDir)();
77
+ const { migrationsSrcRoot } = (0, install_shared_1.resolveTemplateRoots)(templatesDir);
78
+ if (options.schemaVersion !== manifest_1.CURRENT_SCHEMA_VERSION) {
79
+ onWarn(`community-sdk: --schema-version ${options.schemaVersion} was requested, but this CLI only knows the templates for the current schema (version ${manifest_1.CURRENT_SCHEMA_VERSION}) — it cannot reconstruct a historical template snapshot. installedTemplates was populated from the CURRENT template set; consult the compat table (docs/compat.md) and adjust community-sdk.json by hand if this app's live schema predates it.`);
80
+ }
81
+ const installedTemplates = [];
82
+ for (const moduleName of canonicalModules) {
83
+ const moduleSrcDir = path.join(migrationsSrcRoot, moduleName);
84
+ if (!fs.existsSync(moduleSrcDir))
85
+ continue;
86
+ const filenames = fs.readdirSync(moduleSrcDir).filter((f) => f.endsWith(".sql"));
87
+ for (const filename of filenames) {
88
+ installedTemplates.push((0, manifest_1.migrationTemplateId)(moduleName, (0, install_shared_1.templateBaseName)(filename)));
89
+ }
90
+ }
91
+ const manifest = {
92
+ schemaVersion: options.schemaVersion,
93
+ sdkVersion: (0, install_shared_1.readOwnPackageVersion)(),
94
+ modules: canonicalModules,
95
+ installedFiles: [],
96
+ installedTemplates: installedTemplates.sort(),
97
+ };
98
+ (0, manifest_1.writeManifest)(cwd, manifest);
99
+ log("");
100
+ log(`community-sdk adopted (${canonicalModules.join(", ")}) at schema version ${options.schemaVersion}.`);
101
+ log("No files were copied — this repo's schema/functions are assumed to already be live.");
102
+ log("Compat table: docs/compat.md (module <-> schemaVersion history, if this version isn't current).");
103
+ log("Next: `community upgrade` will seed any Edge Function source not yet present in this repo.");
104
+ return { manifest };
105
+ }
@@ -0,0 +1,29 @@
1
+ import { type Manifest } from "../manifest";
2
+ import { MODULE_ORDER, type Module } from "../install-shared";
3
+ export { MODULE_ORDER, type Module };
4
+ export declare const ALREADY_INITIALIZED_MESSAGE = "community-sdk: already initialized (found community-sdk.json), use upgrade";
5
+ export interface InitOptions {
6
+ /** Modules to install. Defaults to every module. `core` is always implied. */
7
+ modules?: string[];
8
+ projectUrl?: string;
9
+ anonKey?: string;
10
+ /** Target Supabase directory, relative to `cwd`. Defaults to "supabase". */
11
+ dir?: string;
12
+ /** App repo root — where community-sdk.json lives. Defaults to process.cwd(). */
13
+ cwd?: string;
14
+ /** Source of the migrations/ + functions/ trees. Defaults to the shipped templates. */
15
+ templatesDir?: string;
16
+ /** Used to ask for projectUrl/anonKey when missing and not derivable. */
17
+ prompt?: (question: string) => Promise<string>;
18
+ /** Called for non-fatal dependency warnings (e.g. inbox without reaction). */
19
+ onWarn?: (message: string) => void;
20
+ /** Used to print the summary + next steps. */
21
+ log?: (message: string) => void;
22
+ /** Clock override for deterministic migration timestamps in tests. */
23
+ now?: Date;
24
+ }
25
+ export interface InitResult {
26
+ manifest: Manifest;
27
+ dir: string;
28
+ }
29
+ export declare function runInit(options?: InitOptions): Promise<InitResult>;
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ALREADY_INITIALIZED_MESSAGE = exports.MODULE_ORDER = void 0;
37
+ exports.runInit = runInit;
38
+ const fs = __importStar(require("node:fs"));
39
+ const path = __importStar(require("node:path"));
40
+ const substitute_1 = require("../substitute");
41
+ const manifest_1 = require("../manifest");
42
+ const install_shared_1 = require("../install-shared");
43
+ Object.defineProperty(exports, "MODULE_ORDER", { enumerable: true, get: function () { return install_shared_1.MODULE_ORDER; } });
44
+ exports.ALREADY_INITIALIZED_MESSAGE = "community-sdk: already initialized (found community-sdk.json), use upgrade";
45
+ async function runInit(options = {}) {
46
+ const cwd = options.cwd ?? process.cwd();
47
+ const log = options.log ?? ((message) => console.log(message));
48
+ const onWarn = options.onWarn ?? ((message) => console.warn(message));
49
+ if ((0, manifest_1.readManifest)(cwd)) {
50
+ throw new Error(exports.ALREADY_INITIALIZED_MESSAGE);
51
+ }
52
+ const canonicalModules = (0, install_shared_1.resolveModules)(options.modules, onWarn);
53
+ const dirRel = options.dir ?? "supabase";
54
+ const dir = (0, install_shared_1.resolveContainedDir)(cwd, dirRel);
55
+ const templatesDir = options.templatesDir ?? (0, install_shared_1.defaultTemplatesDir)();
56
+ const { migrationsSrcRoot, functionsSrcRoot } = (0, install_shared_1.resolveTemplateRoots)(templatesDir);
57
+ const { projectUrl, anonKey } = await (0, install_shared_1.resolveProjectCredentials)({
58
+ dir,
59
+ projectUrl: options.projectUrl,
60
+ anonKey: options.anonKey,
61
+ prompt: options.prompt,
62
+ });
63
+ const now = options.now ?? new Date();
64
+ // ---- migrations + functions, staged atomically: on any failure partway
65
+ // through, every file this run wrote (which may already contain the real
66
+ // project URL / anon key) is removed before the error propagates, so a
67
+ // retry starts clean instead of finding stale/duplicate files and no
68
+ // manifest to explain them. The manifest itself is written last, only once
69
+ // every file below has landed successfully.
70
+ const migrationsDestRoot = path.join(dir, "migrations");
71
+ const functionsDestRoot = path.join(dir, "functions");
72
+ const migrationsDestRootExisted = fs.existsSync(migrationsDestRoot);
73
+ const functionsDestRootExisted = fs.existsSync(functionsDestRoot);
74
+ const writtenPaths = [];
75
+ const installedTemplates = [];
76
+ try {
77
+ // ---- migrations ----
78
+ fs.mkdirSync(migrationsDestRoot, { recursive: true });
79
+ let fileIndex = 0;
80
+ for (const moduleName of canonicalModules) {
81
+ const moduleSrcDir = path.join(migrationsSrcRoot, moduleName);
82
+ const filenames = fs
83
+ .readdirSync(moduleSrcDir)
84
+ .filter((f) => f.endsWith(".sql"))
85
+ .sort();
86
+ for (const filename of filenames) {
87
+ const name = (0, install_shared_1.templateBaseName)(filename);
88
+ const timestamp = (0, install_shared_1.formatTimestamp)((0, install_shared_1.addSeconds)(now, fileIndex));
89
+ fileIndex += 1;
90
+ const destFilename = (0, install_shared_1.migrationDestFilename)(timestamp, moduleName, name);
91
+ const rawSql = fs.readFileSync(path.join(moduleSrcDir, filename), "utf8");
92
+ let substituted;
93
+ try {
94
+ substituted = (0, substitute_1.substitutePlaceholders)(rawSql, { projectUrl, anonKey });
95
+ }
96
+ catch (err) {
97
+ const message = err instanceof Error ? err.message : String(err);
98
+ throw new Error(`${message} (source: ${moduleName}/${filename})`);
99
+ }
100
+ const destPath = path.join(migrationsDestRoot, destFilename);
101
+ fs.writeFileSync(destPath, substituted, "utf8");
102
+ writtenPaths.push(destPath);
103
+ installedTemplates.push((0, manifest_1.migrationTemplateId)(moduleName, name));
104
+ }
105
+ }
106
+ // ---- functions ----
107
+ fs.mkdirSync(functionsDestRoot, { recursive: true });
108
+ for (const fnName of (0, install_shared_1.functionsForModules)(canonicalModules)) {
109
+ const srcDir = path.join(functionsSrcRoot, fnName);
110
+ if (!fs.existsSync(srcDir))
111
+ continue;
112
+ const destDir = path.join(functionsDestRoot, fnName);
113
+ fs.cpSync(srcDir, destDir, { recursive: true });
114
+ writtenPaths.push(...(0, install_shared_1.listFilesRecursive)(destDir));
115
+ }
116
+ }
117
+ catch (err) {
118
+ for (const p of writtenPaths) {
119
+ fs.rmSync(p, { force: true });
120
+ }
121
+ if (!functionsDestRootExisted) {
122
+ fs.rmSync(functionsDestRoot, { recursive: true, force: true });
123
+ }
124
+ if (!migrationsDestRootExisted) {
125
+ fs.rmSync(migrationsDestRoot, { recursive: true, force: true });
126
+ }
127
+ throw err;
128
+ }
129
+ // ---- manifest ----
130
+ const manifest = {
131
+ schemaVersion: manifest_1.CURRENT_SCHEMA_VERSION,
132
+ sdkVersion: (0, install_shared_1.readOwnPackageVersion)(),
133
+ modules: canonicalModules,
134
+ installedFiles: writtenPaths.map((f) => (0, install_shared_1.toRelativePosix)(cwd, f)).sort(),
135
+ installedTemplates: installedTemplates.sort(),
136
+ };
137
+ (0, manifest_1.writeManifest)(cwd, manifest);
138
+ printNextSteps(log, canonicalModules);
139
+ return { manifest, dir };
140
+ }
141
+ function printNextSteps(log, modules) {
142
+ log("");
143
+ log(`community-sdk initialized (${modules.join(", ")}).`);
144
+ log("Next steps:");
145
+ log(" 1. Review the copied migrations, then: supabase db push");
146
+ log(" 2. Set Edge Function secrets: supabase secrets set OPENAI_API_KEY=...");
147
+ log(" optional: SLACK_WEBHOOK_URL, COMMUNITY_APP_NAME, COMMUNITY_FALLBACK_NAME");
148
+ if (modules.includes("push")) {
149
+ log(" push module: EXPO_ACCESS_TOKEN (required for notify-like/notify-comment)");
150
+ }
151
+ log(" 3. Deploy the Edge Functions: supabase functions deploy");
152
+ }
@@ -0,0 +1,33 @@
1
+ import { type Manifest } from "../manifest";
2
+ export declare const NOT_INITIALIZED_MESSAGE = "community-sdk: not initialized, run init (or adopt)";
3
+ export interface UpgradeOptions {
4
+ /** Target Supabase directory, relative to `cwd`. Defaults to "supabase". */
5
+ dir?: string;
6
+ projectUrl?: string;
7
+ anonKey?: string;
8
+ /** App repo root — where community-sdk.json lives. Defaults to process.cwd(). */
9
+ cwd?: string;
10
+ /** Source of the migrations/ + functions/ trees. Defaults to the shipped templates. */
11
+ templatesDir?: string;
12
+ /** Used to ask for projectUrl/anonKey when a newly copied migration needs them. */
13
+ prompt?: (question: string) => Promise<string>;
14
+ /** Called for non-fatal warnings — most importantly, functions overwritten with new content. */
15
+ onWarn?: (message: string) => void;
16
+ /** Used to print the summary + next steps. */
17
+ log?: (message: string) => void;
18
+ /** Clock override for deterministic migration timestamps in tests. */
19
+ now?: Date;
20
+ }
21
+ export interface UpgradeResult {
22
+ manifest: Manifest;
23
+ dir: string;
24
+ /** True when nothing needed to change — no new migrations, no function drift. */
25
+ upToDate: boolean;
26
+ /** Relative (to `cwd`) paths of newly copied migration files, in copy order. */
27
+ addedMigrations: string[];
28
+ /** Function names copied because they weren't present on disk yet. */
29
+ newFunctions: string[];
30
+ /** Function names whose installed content differed from the template and was overwritten. */
31
+ overwrittenFunctions: string[];
32
+ }
33
+ export declare function runUpgrade(options?: UpgradeOptions): Promise<UpgradeResult>;