Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions cli/build-cmd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,9 @@ it("unmodified dependencies", async () => {
expect(patchedPjson.patchedPackageJson).toEqual({
scripts: {},
version: "1.2.3",
publishConfig: {
access: "public",
},
dependencies: {
"@fireproof/vendor": "workspace:*",
"xcmd-ts": "^0.13.0",
Expand All @@ -244,6 +247,24 @@ it("unmodified dependencies", async () => {
});
});

it("patchPackageJson sets repository when repositoryUrl is provided", async () => {
const version = new Version("1.2.3", "");
const { patchedPackageJson } = await patchPackageJson("package.json", version, {
mock,
repositoryUrl: "https://github.com/fireproof-storage/fireproof.git",
});
expect(patchedPackageJson.repository).toEqual({
type: "git",
url: "https://github.com/fireproof-storage/fireproof.git",
});
});

it("patchPackageJson does not set repository when repositoryUrl is omitted", async () => {
const version = new Version("1.2.3", "");
const { patchedPackageJson } = await patchPackageJson("package.json", version, { mock });
expect(patchedPackageJson.repository).toBeUndefined();
});

it("sanitizeNpmrc with http lhs", async () => {
const npmrc = [
"; .npmrc",
Expand Down
30 changes: 29 additions & 1 deletion cli/build-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,13 @@ export interface PackageJson {
exports: Record<string, string | Record<string, string>>;
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
publishConfig?: Record<string, string>;
repository?: { type: string; url: string } | string;
}

export interface PatchPackageJsonOptions {
changeScope?: string;
repositoryUrl?: string;
readonly mock?: {
readonly readJSON: typeof fs.readJson;
};
Expand Down Expand Up @@ -155,6 +158,10 @@ export async function patchPackageJson(
patchedPackageJson.version = version.version;
delete patchedPackageJson.scripts["pack"];
delete patchedPackageJson.scripts["publish"];
patchedPackageJson.publishConfig = { ...patchedPackageJson.publishConfig, access: "public" };
if (opts.repositoryUrl) {
patchedPackageJson.repository = { type: "git", url: opts.repositoryUrl };
}

return { patchedPackageJson, originalPackageJson };
}
Expand Down Expand Up @@ -319,6 +326,14 @@ export async function buildJsrConf(
return jsrConf;
}

export async function getGitOriginUrl(): Promise<string> {
const res = await $`git config --get remote.origin.url`.nothrow();
if (res.exitCode !== 0) {
return "";
}
return res.stdout.trim();
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function buildCmd(sthis: SuperThis) {
const cmd = command({
Expand Down Expand Up @@ -476,6 +491,12 @@ export function buildCmd(sthis: SuperThis) {
defaultValueIsSerializable: true,
description: "Package manager to use (pnpm, npm, yarn, bun), defaults to 'pnpm'.",
}),
repositoryUrl: option({
long: "repository-url",
type: string,
defaultValue: () => "",
description: "The repository URL to set in package.json. Defaults to the origin URL read from .git/config.",
}),
},
handler: async (args) => {
const top = await findUp("tsconfig.dist.json");
Expand Down Expand Up @@ -546,7 +567,14 @@ export function buildCmd(sthis: SuperThis) {
$.verbose = true;
cd(jsrDstDir);

let packageJson = await patchPackageJson("package.json", version, { changeScope: args.changeScope });
if (!args.repositoryUrl) {
args.repositoryUrl = await getGitOriginUrl();
}

let packageJson = await patchPackageJson("package.json", version, {
changeScope: args.changeScope,
repositoryUrl: args.repositoryUrl,
});
if (!args.noPinned) {
console.log(
"Prepared package.json with pinning for",
Expand Down
1 change: 1 addition & 0 deletions cloud/3rd-party/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@fireproof/core-keybag": "workspace:*",
"@fireproof/core-protocols-dashboard": "workspace:*",
"@fireproof/core-runtime": "workspace:*",
"@fireproof/core-quick-silver": "workspace:*",
"@fireproof/core-types-protocols-cloud": "workspace:*",
"@fireproof/core-types-protocols-dashboard": "workspace:*",
"jose": "^6.1.3",
Expand Down
38 changes: 21 additions & 17 deletions cloud/3rd-party/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
import { DocWithId, useFireproof, toCloud, RedirectStrategy } from "use-fireproof";
import { DocWithId, useFireproof } from "use-fireproof";
import React, { useState, useEffect } from "react";
import "./App.css";
import { QuickSilver } from "../../../core/quick-silver/quick-silver.js";
// import { URI } from "@adviser/cement";

function App() {
const { database, attach } = useFireproof("fireproof-5-party", {
attach: toCloud({
strategy: new RedirectStrategy({
// overlayCss: defaultOverlayCss,
overlayHtml: (url: string) => `<div class="fpOverlayContent">
<div class="fpCloseButton">&times;</div>
Fireproof Dashboard<br />
Sign in to Fireproof Dashboard
<a href="${url}" target="_blank">Redirect to Fireproof</a>
</div>`,
}),
// dashboardURI: "http://localhost:7370/fp/cloud/api/token",
// tokenApiURI: "http://localhost:7370/api",
// urls: { base: "fpcloud://localhost:8787?protocol=ws" },
// tenant: "3rd-party",
// ledger: "vibes",
}),
databaseFactory: (name) => {
return new QuickSilver({ name });
},
Comment on lines +9 to +11
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

QuickSilver constructor requires sthis — build-breaking pipeline failure.

QuickSilverOpts has sthis: SuperThis as a required field, but the factory only provides { name }. The build fails with TS2741.

🐛 Proposed fix
+import { ensureSuperThis } from "@fireproof/core-runtime";
 import { QuickSilver } from "../../../core/quick-silver/quick-silver.js";

+const sthis = ensureSuperThis();
+
 function App() {
   const { database, attach } = useFireproof("fireproof-5-party", {
     databaseFactory: (name) => {
-      return new QuickSilver({ name });
+      return new QuickSilver({ name, sthis });
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
databaseFactory: (name) => {
return new QuickSilver({ name });
},
import { ensureSuperThis } from "@fireproof/core-runtime";
import { QuickSilver } from "../../../core/quick-silver/quick-silver.js";
const sthis = ensureSuperThis();
function App() {
const { database, attach } = useFireproof("fireproof-5-party", {
databaseFactory: (name) => {
return new QuickSilver({ name, sthis });
},
🧰 Tools
🪛 GitHub Actions: `@fireproof/core`

[error] 10-10: TypeScript error TS2741: Property 'sthis' is missing in type '{ name: string; }' but required in type 'QuickSilverOpts'.

🪛 GitHub Check: CI Core

[failure] 10-10:
Property 'sthis' is missing in type '{ name: string; }' but required in type 'QuickSilverOpts'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/3rd-party/src/App.tsx` around lines 9 - 11, The QuickSilver constructor
requires a QuickSilverOpts with a required sthis: SuperThis, but databaseFactory
currently constructs new QuickSilver({ name }) causing TS2741; update the
databaseFactory implementation to provide a valid sthis when constructing
QuickSilver (e.g., obtain or create the SuperThis instance from the surrounding
context or dependency injection and pass new QuickSilver({ name, sthis })) so
QuickSilverOpts is satisfied; ensure the symbol names QuickSilver,
QuickSilverOpts, sthis and databaseFactory are used to locate and update the
code.

// attach: toCloud({
// strategy: new RedirectStrategy({
// // overlayCss: defaultOverlayCss,
// overlayHtml: (url: string) => `<div class="fpOverlayContent">
// <div class="fpCloseButton">&times;</div>
// Fireproof Dashboard<br />
// Sign in to Fireproof Dashboard
// <a href="${url}" target="_blank">Redirect to Fireproof</a>
// </div>`,
// }),
// dashboardURI: "http://localhost:7370/fp/cloud/api/token",
// tokenApiURI: "http://localhost:7370/api",
// urls: { base: "fpcloud://localhost:8787?protocol=ws" },
// tenant: "3rd-party",
// ledger: "vibes",
// }),
});
const [rows, setRows] = useState([] as DocWithId<{ value: string }>[]);
// const [token, setToken] = useState("");
Expand Down
161 changes: 161 additions & 0 deletions cloud/quick-silver/api/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/* eslint-disable no-console */
import { command, option, multioption, string, run, subcommands, array } from "cmd-ts";
import { QSApi } from "./qs-api.js";
import type { QSGet, QSPut } from "@fireproof/cloud-quick-silver-types";
import { ensureSuperThis } from "@fireproof/core-runtime";
import { processStream } from "@adviser/cement";

const sthis = ensureSuperThis();

// ── shared connection options ─────────────────────────────────────────────────

function connectionOpts() {
return {
url: option({
long: "url",
short: "u",
description: "WebSocket URL of the quick-silver worker",
type: string,
}),
db: option({
long: "db",
short: "d",
description: "Database name",
type: string,
}),
authType: option({
long: "auth-type",
description: "Auth type",
type: string,
defaultValue: () => "anon",
}),
authToken: option({
long: "auth-token",
short: "t",
description: "Auth token",
type: string,
defaultValue: () => "",
}),
};
}

function makeApi(args: { url: string; db: string; authType: string; authToken: string }): ReturnType<typeof QSApi> {
return QSApi({
url: args.url,
db: args.db,
auth: () => ({ type: args.authType, token: args.authToken }),
});
}

// ── get ───────────────────────────────────────────────────────────────────────

const getCmd = command({
name: "get",
description: "Fetch one or more docs by key (comma-separated)",
args: {
...connectionOpts(),
keys: multioption({
long: "keys",
short: "k",
description: "Key to fetch (repeatable)",
type: array(string),
}),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
},
handler: async (args) => {
const api = await makeApi(args);
const ops: QSGet[] = args.keys.map((key) => ({ key }));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Implicit any in .map() callbacks — two static analysis failures.

key (line 65) and pair (line 88) are implicitly typed as any because TypeScript cannot infer them in strict mode. Add explicit annotations.

🐛 Proposed fix
-  const ops: QSGet[] = args.keys.map((key) => ({ key }));
+  const ops: QSGet[] = args.keys.map((key: string) => ({ key }));
-    const ops: QSPut[] = args.pkg.map((pair) => {
+    const ops: QSPut[] = args.pkg.map((pair: string) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ops: QSGet[] = args.keys.map((key) => ({ key }));
const ops: QSGet[] = args.keys.map((key: string) => ({ key }));
🧰 Tools
🪛 GitHub Check: CI Core

[failure] 65-65:
Parameter 'key' implicitly has an 'any' type.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/quick-silver/api/cli.ts` at line 65, The map callbacks are implicitly
typed causing TS errors; explicitly annotate the parameters: for the line
creating ops use args.keys.map((key: string) => ({ key })) so key is string and
the result matches QSGet[], and for the other map use (pair: [string, unknown])
or destructure ( [k, v]: [string, unknown] ) in that callback so the pair
parameter is explicitly typed (replace unknown with a more specific type if
available in scope).

await processStream(api.get(ops), async (r) => {
console.log(JSON.stringify(r));
});
await api.close();
},
});

// ── put ───────────────────────────────────────────────────────────────────────

const putCmd = command({
name: "put",
description: "Write docs by key (repeatable: --pkg key,{json})",
args: {
...connectionOpts(),
pkg: multioption({
long: "pkg",
short: "p",
description: "key,{json} pair to store (repeatable)",
type: array(string),
}),
},
handler: async (args) => {
const ops: QSPut[] = args.pkg.map((pair) => {
const comma = pair.indexOf(",");
if (comma === -1) throw new Error(`invalid --pkg "${pair}": expected key,{json}`);
const key = pair.slice(0, comma);
const data = sthis.ende.cbor.encodeToUint8(JSON.parse(pair.slice(comma + 1))) as Uint8Array<ArrayBuffer>;
return { key, data } satisfies QSPut;
Comment on lines +89 to +94
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

JSON.parse in put handler is unguarded — invalid JSON crashes with no user-friendly message.

JSON.parse(pair.slice(comma + 1)) throws SyntaxError for malformed input. The error propagates uncaught through args.pkg.map(...) and the handler, producing a noisy stack trace.

🛡️ Proposed fix
-     const data = sthis.ende.cbor.encodeToUint8(JSON.parse(pair.slice(comma + 1))) as Uint8Array<ArrayBuffer>;
+     let parsed: unknown;
+     try {
+       parsed = JSON.parse(pair.slice(comma + 1));
+     } catch {
+       throw new Error(`invalid --pkg "${pair}": JSON parse failed`);
+     }
+     const data = sthis.ende.cbor.encodeToUint8(parsed) as Uint8Array<ArrayBuffer>;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ops: QSPut[] = args.pkg.map((pair) => {
const comma = pair.indexOf(",");
if (comma === -1) throw new Error(`invalid --pkg "${pair}": expected key,{json}`);
const key = pair.slice(0, comma);
const data = sthis.ende.cbor.encodeToUint8(JSON.parse(pair.slice(comma + 1))) as Uint8Array<ArrayBuffer>;
return { key, data } satisfies QSPut;
const ops: QSPut[] = args.pkg.map((pair) => {
const comma = pair.indexOf(",");
if (comma === -1) throw new Error(`invalid --pkg "${pair}": expected key,{json}`);
const key = pair.slice(0, comma);
let parsed: unknown;
try {
parsed = JSON.parse(pair.slice(comma + 1));
} catch {
throw new Error(`invalid --pkg "${pair}": JSON parse failed`);
}
const data = sthis.ende.cbor.encodeToUint8(parsed) as Uint8Array<ArrayBuffer>;
return { key, data } satisfies QSPut;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/quick-silver/api/cli.ts` around lines 89 - 94, The JSON.parse call
inside the args.pkg.map in the put handler is unguarded and will throw a
SyntaxError for malformed JSON; update the mapping in the ops construction (the
args.pkg.map callback that returns QSPut objects and uses
sthis.ende.cbor.encodeToUint8) to catch JSON.parse errors for each pair,
validate/try-catch parsing the slice(comma + 1), and when parsing fails throw a
new Error with a clear, user-friendly message that includes the offending
key/pair and the parse error message so the CLI reports a helpful error instead
of a raw stack trace.

});
const api = await makeApi(args);
await processStream(api.put(ops), async (r) => {
console.log(JSON.stringify(r));
});
await api.close();
},
});

// ── all ───────────────────────────────────────────────────────────────────────

const queryCmd = command({
name: "query",
description: "Stream all docs in the database",
args: {
...connectionOpts(),
},
handler: async (args) => {
const api = await makeApi(args);
await processStream(api.query(), async (r) => {
console.log(JSON.stringify(r));
});
await api.close();
},
});

// ── subscribe ─────────────────────────────────────────────────────────────────

const subscribeCmd = command({
name: "subscribe",
description: "Subscribe to events for a subscribeId (streams until Ctrl+C)",
args: {
...connectionOpts(),
},
handler: async (args) => {
const api = await makeApi(args);
const handle = api.subscribe();

const cleanup = () => {
handle.close();
api
.close()
.then(() => process.exit(0))
.catch(() => process.exit(1));
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);

await processStream(handle.events, async (r) => {
console.log(JSON.stringify(r));
});
await api.close();
},
});

// ── entry point ───────────────────────────────────────────────────────────────

const cmd = subcommands({
name: "qs",
description: "quick-silver CLI",
version: "0.0.0",
cmds: { get: getCmd, put: putCmd, query: queryCmd, subscribe: subscribeCmd },
});

run(cmd, process.argv.slice(2))
.then(() => process.exit(0))
.catch(console.error);
Comment on lines +159 to +161
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

catch(console.error) doesn't exit with a non-zero code — CLI errors appear as successful exits.

Scripts and CI pipelines relying on exit codes will not detect CLI failures.

🛡️ Proposed fix
 run(cmd, process.argv.slice(2))
   .then(() => process.exit(0))
-  .catch(console.error);
+  .catch((e) => { console.error(e); process.exit(1); });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run(cmd, process.argv.slice(2))
.then(() => process.exit(0))
.catch(console.error);
run(cmd, process.argv.slice(2))
.then(() => process.exit(0))
.catch((e) => { console.error(e); process.exit(1); });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cloud/quick-silver/api/cli.ts` around lines 159 - 161, The current promise
rejection handler for run(...) uses .catch(console.error) which logs errors but
leaves the process exit code as zero; change the catch to explicitly log the
error and exit non‑zero (e.g., .catch(err => { console.error(err);
process.exit(1); })) so CLI failures produce a failing exit code. Make this
change where run(...) is invoked.

16 changes: 16 additions & 0 deletions cloud/quick-silver/api/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "@fireproof/cloud-quick-silver-api",
"version": "0.0.0",
"type": "module",
"scripts": {
"build": "core-cli tsc",
"pack": "core-cli build --doPack",
"publish": "core-cli build"
},
"dependencies": {
"@adviser/cement": "^0.5.22",
"@fireproof/cloud-quick-silver-types": "workspace:*",
"@fireproof/core-runtime": "workspace:*",
"cmd-ts": "^0.15.0"
}
}
Loading
Loading