@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.
- package/LICENSE +21 -0
- package/README.md +118 -0
- package/lib/commands/adopt.d.ts +38 -0
- package/lib/commands/adopt.js +105 -0
- package/lib/commands/init.d.ts +29 -0
- package/lib/commands/init.js +152 -0
- package/lib/commands/upgrade.d.ts +33 -0
- package/lib/commands/upgrade.js +280 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +123 -0
- package/lib/install-shared.d.ts +72 -0
- package/lib/install-shared.js +256 -0
- package/lib/manifest.d.ts +53 -0
- package/lib/manifest.js +74 -0
- package/lib/substitute.d.ts +15 -0
- package/lib/substitute.js +25 -0
- package/package.json +32 -0
- package/templates/functions/_shared/client.ts +20 -0
- package/templates/functions/_shared/config.ts +23 -0
- package/templates/functions/_shared/moderation.ts +102 -0
- package/templates/functions/_shared/push.ts +33 -0
- package/templates/functions/_shared/slack.ts +12 -0
- package/templates/functions/broadcast-post/index.ts +73 -0
- package/templates/functions/daily-moderation/index.ts +175 -0
- package/templates/functions/moderate-one/index.ts +62 -0
- package/templates/functions/notify-comment/index.ts +63 -0
- package/templates/functions/notify-like/index.ts +103 -0
- package/templates/functions/notify-reaction/index.ts +186 -0
- package/templates/functions/report-to-slack/index.ts +37 -0
- package/templates/functions/update-profile/index.ts +131 -0
- package/templates/migrations/core/001_tables.sql +177 -0
- package/templates/migrations/core/002_rls.sql +154 -0
- package/templates/migrations/core/003_moderation.sql +68 -0
- package/templates/migrations/core/004_dashboard.sql +102 -0
- package/templates/migrations/core/005_avatars.sql +19 -0
- package/templates/migrations/core/006_meta.sql +11 -0
- package/templates/migrations/inbox/001_inbox.sql +237 -0
- package/templates/migrations/polls/001_polls.sql +120 -0
- package/templates/migrations/push/001_push.sql +42 -0
- package/templates/migrations/push/002_triggers.sql +89 -0
- 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>;
|