@rocapine/community 0.2.0 → 0.3.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/README.md CHANGED
@@ -61,6 +61,11 @@ warning** — review the diff before deploying. `--project-url`/`--anon-key`
61
61
  are only needed if a newly-added migration carries a placeholder. Fails if
62
62
  `community-sdk.json` doesn't exist yet (run `init` or `adopt` first).
63
63
 
64
+ `--add-modules <modules>` installs one or more modules not yet in
65
+ `community-sdk.json` on top of the existing backend (comma-separated, same
66
+ parsing as `init --modules`) — e.g. `upgrade --add-modules translation` to
67
+ add the translation module to an already-initialized app.
68
+
64
69
  ## `adopt`
65
70
 
66
71
  ```bash
@@ -110,7 +110,7 @@ async function runInit(options = {}) {
110
110
  if (!fs.existsSync(srcDir))
111
111
  continue;
112
112
  const destDir = path.join(functionsDestRoot, fnName);
113
- fs.cpSync(srcDir, destDir, { recursive: true });
113
+ fs.cpSync(srcDir, destDir, { recursive: true, filter: install_shared_1.isShippedFunctionFile });
114
114
  writtenPaths.push(...(0, install_shared_1.listFilesRecursive)(destDir));
115
115
  }
116
116
  }
@@ -148,5 +148,8 @@ function printNextSteps(log, modules) {
148
148
  if (modules.includes("push")) {
149
149
  log(" push module: EXPO_ACCESS_TOKEN (only if your Expo project enforces Enhanced Security for push); COMMUNITY_PUSH_COPY (optional per-locale copy)");
150
150
  }
151
+ if (modules.includes("translation")) {
152
+ log(' translation module: COMMUNITY_TRANSLATION_LOCALES="en,es-419,..." (required, same list as modules.translation.locales in the app); optional: COMMUNITY_TRANSLATION_MODEL, COMMUNITY_TRANSLATION_STYLE');
153
+ }
151
154
  log(" 3. Deploy the Edge Functions: supabase functions deploy");
152
155
  }
@@ -9,6 +9,8 @@ export interface UpgradeOptions {
9
9
  cwd?: string;
10
10
  /** Source of the migrations/ + functions/ trees. Defaults to the shipped templates. */
11
11
  templatesDir?: string;
12
+ /** Module names to install on top of what's already in the manifest — validated, ordered and merged via resolveModules, same rules as `init --modules` (unknown names throw, dependency gaps warn). */
13
+ addModules?: string[];
12
14
  /** Used to ask for projectUrl/anonKey when a newly copied migration needs them. */
13
15
  prompt?: (question: string) => Promise<string>;
14
16
  /** Called for non-fatal warnings — most importantly, functions overwritten with new content. */
@@ -23,6 +25,8 @@ export interface UpgradeResult {
23
25
  dir: string;
24
26
  /** True when nothing needed to change — no new migrations, no function drift. */
25
27
  upToDate: boolean;
28
+ /** Modules newly added by `addModules` (not already in the manifest). Empty when `addModules` was omitted or every named module was already installed. */
29
+ addedModules: string[];
26
30
  /** Relative (to `cwd`) paths of newly copied migration files, in copy order. */
27
31
  addedMigrations: string[];
28
32
  /** Function names copied because they weren't present on disk yet. */
@@ -54,10 +54,15 @@ async function runUpgrade(options = {}) {
54
54
  const dir = (0, install_shared_1.resolveContainedDir)(cwd, dirRel);
55
55
  const templatesDir = options.templatesDir ?? (0, install_shared_1.defaultTemplatesDir)();
56
56
  const { migrationsSrcRoot, functionsSrcRoot } = (0, install_shared_1.resolveTemplateRoots)(templatesDir);
57
- // Only modules the app actually has installed are diffed/re-synced —
58
- // upgrade never adds or removes modules (that's what re-running init on a
59
- // fresh dir, or a future dedicated command, would be for).
60
- const canonicalModules = install_shared_1.MODULE_ORDER.filter((m) => manifest.modules.includes(m));
57
+ // By default only modules the app already has installed are diffed/re-synced.
58
+ // `addModules` extends that list through the same validation/ordering/warning
59
+ // rules `init --modules` uses; left empty/undefined, behaviour here must stay
60
+ // byte-identical to the pre-`addModules` path (no resolveModules call, no
61
+ // spurious warnings for e.g. an existing inbox-without-reaction install).
62
+ const canonicalModules = options.addModules && options.addModules.length > 0
63
+ ? (0, install_shared_1.resolveModules)([...manifest.modules, ...options.addModules], onWarn)
64
+ : install_shared_1.MODULE_ORDER.filter((m) => manifest.modules.includes(m));
65
+ const addedModules = canonicalModules.filter((m) => !manifest.modules.includes(m));
61
66
  const installedTemplateIds = new Set(manifest.installedTemplates ?? reconstructInstalledTemplateIds(manifest.installedFiles));
62
67
  const pending = [];
63
68
  for (const moduleName of canonicalModules) {
@@ -101,6 +106,7 @@ async function runUpgrade(options = {}) {
101
106
  manifest,
102
107
  dir,
103
108
  upToDate: true,
109
+ addedModules,
104
110
  addedMigrations: [],
105
111
  newFunctions: [],
106
112
  overwrittenFunctions: [],
@@ -162,7 +168,10 @@ async function runUpgrade(options = {}) {
162
168
  }
163
169
  for (const fnName of newFunctions) {
164
170
  const destDir = path.join(functionsDestRoot, fnName);
165
- fs.cpSync(path.join(functionsSrcRoot, fnName), destDir, { recursive: true });
171
+ fs.cpSync(path.join(functionsSrcRoot, fnName), destDir, {
172
+ recursive: true,
173
+ filter: install_shared_1.isShippedFunctionFile,
174
+ });
166
175
  createdFunctionDirs.push(destDir);
167
176
  }
168
177
  for (const fnName of changedFunctions) {
@@ -171,7 +180,10 @@ async function runUpgrade(options = {}) {
171
180
  fs.cpSync(destDir, backupDir, { recursive: true });
172
181
  backups.push({ destDir, backupDir });
173
182
  fs.rmSync(destDir, { recursive: true, force: true });
174
- fs.cpSync(path.join(functionsSrcRoot, fnName), destDir, { recursive: true });
183
+ fs.cpSync(path.join(functionsSrcRoot, fnName), destDir, {
184
+ recursive: true,
185
+ filter: install_shared_1.isShippedFunctionFile,
186
+ });
175
187
  }
176
188
  }
177
189
  catch (err) {
@@ -205,12 +217,15 @@ async function runUpgrade(options = {}) {
205
217
  const updatedManifest = {
206
218
  schemaVersion: manifest_1.CURRENT_SCHEMA_VERSION,
207
219
  sdkVersion: (0, install_shared_1.readOwnPackageVersion)(),
208
- modules: manifest.modules,
220
+ // Only --add-modules rewrites the module list; a plain upgrade keeps the
221
+ // manifest's own (order and any entry this CLI doesn't know).
222
+ modules: options.addModules && options.addModules.length > 0 ? canonicalModules : manifest.modules,
209
223
  installedFiles,
210
224
  installedTemplates: [...newInstalledTemplateIds].sort(),
211
225
  };
212
226
  (0, manifest_1.writeManifest)(cwd, updatedManifest);
213
227
  printSummary(log, onWarn, {
228
+ addedModules,
214
229
  addedMigrations,
215
230
  newFunctions,
216
231
  overwrittenFunctions: changedFunctions,
@@ -219,6 +234,7 @@ async function runUpgrade(options = {}) {
219
234
  manifest: updatedManifest,
220
235
  dir,
221
236
  upToDate: false,
237
+ addedModules,
222
238
  addedMigrations,
223
239
  newFunctions,
224
240
  overwrittenFunctions: changedFunctions,
@@ -242,8 +258,11 @@ function reconstructInstalledTemplateIds(installedFiles) {
242
258
  return ids;
243
259
  }
244
260
  function functionDirDiffers(srcDir, destDir) {
245
- const srcRel = new Set((0, install_shared_1.listFilesRecursive)(srcDir).map((f) => path.relative(srcDir, f)));
246
- const destRel = new Set((0, install_shared_1.listFilesRecursive)(destDir).map((f) => path.relative(destDir, f)));
261
+ const rel = (dir) => new Set((0, install_shared_1.listFilesRecursive)(dir)
262
+ .filter(install_shared_1.isShippedFunctionFile)
263
+ .map((f) => path.relative(dir, f)));
264
+ const srcRel = rel(srcDir);
265
+ const destRel = rel(destDir);
247
266
  if (srcRel.size !== destRel.size)
248
267
  return true;
249
268
  for (const rel of srcRel) {
@@ -259,6 +278,9 @@ function functionDirDiffers(srcDir, destDir) {
259
278
  function printSummary(log, onWarn, summary) {
260
279
  log("");
261
280
  log("community-sdk upgraded.");
281
+ if (summary.addedModules.length > 0) {
282
+ log(`Added module(s): ${summary.addedModules.join(", ")}`);
283
+ }
262
284
  if (summary.addedMigrations.length > 0) {
263
285
  log(`Added ${summary.addedMigrations.length} migration(s):`);
264
286
  for (const f of summary.addedMigrations)
@@ -277,4 +299,7 @@ function printSummary(log, onWarn, summary) {
277
299
  if (summary.newFunctions.length > 0 || summary.overwrittenFunctions.length > 0) {
278
300
  log(" 2. Review the function changes, then: supabase functions deploy");
279
301
  }
302
+ if (summary.addedModules.includes("translation")) {
303
+ log(' 3. translation module: COMMUNITY_TRANSLATION_LOCALES="en,es-419,..." (required, same list as modules.translation.locales in the app); optional: COMMUNITY_TRANSLATION_MODEL, COMMUNITY_TRANSLATION_STYLE');
304
+ }
280
305
  }
package/lib/index.js CHANGED
@@ -81,12 +81,14 @@ program
81
81
  program
82
82
  .command("upgrade")
83
83
  .description("Copy any new migrations/Edge Functions shipped since this repo's community-sdk.json was last written, and overwrite changed function code (with a warning)")
84
+ .option("--add-modules <modules>", "comma-separated module list to install on top of what's already in community-sdk.json (e.g. translation)")
84
85
  .option("--project-url <url>", "Supabase project URL, e.g. https://<ref>.supabase.co (only needed if a new migration carries a placeholder)")
85
86
  .option("--anon-key <key>", "Supabase anon key (only needed if a new migration carries a placeholder)")
86
87
  .option("--dir <dir>", "target Supabase directory, relative to the current directory", "supabase")
87
88
  .action(async (opts) => {
88
89
  try {
89
90
  await (0, upgrade_1.runUpgrade)({
91
+ addModules: parseModulesFlag(opts.addModules),
90
92
  projectUrl: opts.projectUrl,
91
93
  anonKey: opts.anonKey,
92
94
  dir: opts.dir,
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Ruled install order (controller ruling, task 18): core -> push -> polls ->
3
- * reaction -> inbox. Reaction MUST land before inbox — inbox/001's reaction
4
- * trigger is guarded with `to_regclass('public.post_reactions')` at install
5
- * time and is never retroactively wired if the reaction module shows up
6
- * later, so `reaction` needs to already exist when `inbox` runs.
3
+ * reaction -> inbox -> translation. Reaction MUST land before inbox —
4
+ * inbox/001's reaction trigger is guarded with
5
+ * `to_regclass('public.post_reactions')` at install time and is never
6
+ * retroactively wired if the reaction module shows up later, so `reaction`
7
+ * needs to already exist when `inbox` runs.
7
8
  *
8
9
  * Shared between `init` (task 18) and `upgrade`/`adopt` (task 19) — the
9
10
  * single source of truth for module names, order, and the function sets
10
11
  * each module pulls in.
11
12
  */
12
- export declare const MODULE_ORDER: readonly ["core", "push", "polls", "reaction", "inbox"];
13
+ export declare const MODULE_ORDER: readonly ["core", "push", "polls", "reaction", "inbox", "translation"];
13
14
  export type Module = (typeof MODULE_ORDER)[number];
14
15
  export declare function functionsForModules(modules: readonly Module[]): string[];
15
16
  /**
@@ -66,6 +67,9 @@ export declare function resolveProjectCredentials(options: {
66
67
  }>;
67
68
  export declare function formatTimestamp(date: Date): string;
68
69
  export declare function addSeconds(date: Date, seconds: number): Date;
70
+ /** Deno test files (`*_test.ts`) stay in the repo: never copied into a host's
71
+ * functions, and ignored when diffing a host's function dir. */
72
+ export declare function isShippedFunctionFile(file: string): boolean;
69
73
  export declare function listFilesRecursive(dir: string): string[];
70
74
  export declare function toRelativePosix(base: string, target: string): string;
71
75
  /** Resolves `dirRel` against `cwd`, throwing if it would escape the project root. */
@@ -46,6 +46,7 @@ exports.deriveProjectUrlFromConfig = deriveProjectUrlFromConfig;
46
46
  exports.resolveProjectCredentials = resolveProjectCredentials;
47
47
  exports.formatTimestamp = formatTimestamp;
48
48
  exports.addSeconds = addSeconds;
49
+ exports.isShippedFunctionFile = isShippedFunctionFile;
49
50
  exports.listFilesRecursive = listFilesRecursive;
50
51
  exports.toRelativePosix = toRelativePosix;
51
52
  exports.resolveContainedDir = resolveContainedDir;
@@ -54,16 +55,17 @@ const path = __importStar(require("node:path"));
54
55
  const promises_1 = require("node:readline/promises");
55
56
  /**
56
57
  * Ruled install order (controller ruling, task 18): core -> push -> polls ->
57
- * reaction -> inbox. Reaction MUST land before inbox — inbox/001's reaction
58
- * trigger is guarded with `to_regclass('public.post_reactions')` at install
59
- * time and is never retroactively wired if the reaction module shows up
60
- * later, so `reaction` needs to already exist when `inbox` runs.
58
+ * reaction -> inbox -> translation. Reaction MUST land before inbox —
59
+ * inbox/001's reaction trigger is guarded with
60
+ * `to_regclass('public.post_reactions')` at install time and is never
61
+ * retroactively wired if the reaction module shows up later, so `reaction`
62
+ * needs to already exist when `inbox` runs.
61
63
  *
62
64
  * Shared between `init` (task 18) and `upgrade`/`adopt` (task 19) — the
63
65
  * single source of truth for module names, order, and the function sets
64
66
  * each module pulls in.
65
67
  */
66
- exports.MODULE_ORDER = ["core", "push", "polls", "reaction", "inbox"];
68
+ exports.MODULE_ORDER = ["core", "push", "polls", "reaction", "inbox", "translation"];
67
69
  const ALWAYS_FUNCTIONS = [
68
70
  "moderate-one",
69
71
  "daily-moderation",
@@ -73,6 +75,7 @@ const ALWAYS_FUNCTIONS = [
73
75
  ];
74
76
  const PUSH_FUNCTIONS = ["notify-like", "notify-comment", "broadcast-post"];
75
77
  const REACTION_FUNCTIONS = ["notify-reaction"];
78
+ const TRANSLATION_FUNCTIONS = ["translate-one", "daily-translation"];
76
79
  function functionsForModules(modules) {
77
80
  const set = new Set(ALWAYS_FUNCTIONS);
78
81
  if (modules.includes("push"))
@@ -81,6 +84,9 @@ function functionsForModules(modules) {
81
84
  if (modules.includes("reaction"))
82
85
  for (const f of REACTION_FUNCTIONS)
83
86
  set.add(f);
87
+ if (modules.includes("translation"))
88
+ for (const f of TRANSLATION_FUNCTIONS)
89
+ set.add(f);
84
90
  return [...set];
85
91
  }
86
92
  /**
@@ -102,6 +108,9 @@ function resolveModules(requested, onWarn) {
102
108
  if (canonicalModules.includes("inbox") && !canonicalModules.includes("reaction")) {
103
109
  onWarn("community-sdk: module 'inbox' selected without 'reaction' — reaction notifications won't appear in the inbox until the reaction module is also installed.");
104
110
  }
111
+ if (canonicalModules.includes("translation") && !canonicalModules.includes("polls")) {
112
+ onWarn("community-sdk: module 'translation' selected without 'polls' — poll option labels won't be translated (posts and comments are).");
113
+ }
105
114
  return canonicalModules;
106
115
  }
107
116
  /**
@@ -228,6 +237,11 @@ function formatTimestamp(date) {
228
237
  function addSeconds(date, seconds) {
229
238
  return new Date(date.getTime() + seconds * 1000);
230
239
  }
240
+ /** Deno test files (`*_test.ts`) stay in the repo: never copied into a host's
241
+ * functions, and ignored when diffing a host's function dir. */
242
+ function isShippedFunctionFile(file) {
243
+ return !file.endsWith("_test.ts");
244
+ }
231
245
  function listFilesRecursive(dir) {
232
246
  const entries = fs.readdirSync(dir, { withFileTypes: true });
233
247
  const files = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocapine/community",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "CLI to install, upgrade and adopt the Rocapine Community SDK's Supabase backend (migrations, Edge Functions) into a project.",
5
5
  "license": "MIT",
6
6
  "repository": {