@gigamusic/links 0.2.0 → 1.0.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
@@ -1,25 +1,29 @@
1
1
  # @gigamusic/links
2
2
 
3
- Linktree-style per-release link pages for `@gigamusic/*` artist sites. Ships its own Prisma fragment (`LinkPage`, `LinkPageItem`), a public `LinkPageView` component, admin page bodies + form, admin API handler factories, and platform-detection helpers.
4
-
5
- ## Install
3
+ Linktree-style per-release link pages for the gigamusic platform. Ships its own Prisma fragment (`LinkPage`, `LinkPageItem`), a typed queries factory, admin API handler factories, platform-detection helpers, and a small SVG `LinkPlatformIcon` component.
6
4
 
7
5
  ```ts
8
6
  import {
9
7
  createLinkPageQueries,
10
- LinkPageView,
11
- createPublicLinkPage,
12
- createAdminLinkPagesHandlers,
13
8
  detectLinkPlatform,
9
+ PLATFORM_LABELS,
14
10
  LinkPlatformIcon,
11
+ type KnownLinkPlatform,
15
12
  } from "@gigamusic/links";
13
+
14
+ // Admin handler factories live on the /server subpath
15
+ import {
16
+ createAdminLinkPagesHandlers,
17
+ createAdminLinkPageByIdHandlers,
18
+ createAdminLinkPageItemsHandlers,
19
+ } from "@gigamusic/links/server";
16
20
  ```
17
21
 
18
- The package is source-shipped add `@gigamusic/links` to `transpilePackages` in your `next.config.ts` (the create-gigamusic-app scaffold does this automatically).
22
+ The public link-page UI is the consumer's responsibility fetch with `createLinkPageQueries(prisma).getPublicLinkPageBySlug(slug)` and render whatever you want.
19
23
 
20
24
  ## Prisma fragment
21
25
 
22
- Run `gigamusic-links sync` to copy the `link-page.prisma` fragment into `prisma/schema/20-link-page.prisma`. Run after every `@gigamusic/links` upgrade; `gigamusic-links doctor` checks for staleness.
26
+ The `link-page.prisma` fragment ships under `node_modules/@gigamusic/links/prisma/`. Copy it into your `prisma/schema/` directory (Prisma 6 `prismaSchemaFolder` mode) once per upgrade.
23
27
 
24
28
  The fragment references the `Release` model owned by `@gigamusic/db`. Prisma requires both sides of a relation to be declared, so consumers must add the inverse on their copy of `Release`:
25
29
 
@@ -30,5 +34,3 @@ model Release {
30
34
  linkPages LinkPage[]
31
35
  }
32
36
  ```
33
-
34
- If your `Release` is shipped verbatim from `@gigamusic/db`'s `10-release.prisma`, add a thin override file at `99-release-link-pages.prisma` that re-declares the model with the extra field, or extend the upstream fragment when re-syncing.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gigamusic/links",
3
- "version": "0.2.0",
4
- "description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a Prisma fragment, public + admin React components, admin API handler factories, and platform-detection helpers.",
3
+ "version": "1.0.0",
4
+ "description": "Editable Linktree-style link pages for gigamusic artist sites. Ships a Prisma fragment, admin API handler factories, and platform-detection helpers.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -9,39 +9,27 @@
9
9
  "directory": "packages/links"
10
10
  },
11
11
  "type": "module",
12
- "main": "./src/index.ts",
13
- "types": "./src/index.ts",
14
12
  "exports": {
15
13
  ".": {
16
14
  "types": "./src/index.ts",
17
15
  "default": "./src/index.ts"
18
16
  },
19
- "./client": {
20
- "types": "./src/client.ts",
21
- "default": "./src/client.ts"
22
- },
23
17
  "./server": {
24
18
  "types": "./src/server.ts",
25
19
  "default": "./src/server.ts"
26
20
  }
27
21
  },
28
- "bin": {
29
- "gigamusic-links": "./dist/cli.js"
30
- },
31
22
  "files": [
32
23
  "src",
33
24
  "prisma",
34
- "dist",
35
25
  "README.md"
36
26
  ],
37
27
  "publishConfig": {
38
28
  "access": "public"
39
29
  },
40
30
  "dependencies": {
41
- "@gigamusic/core": "0.2.0",
42
- "@gigamusic/ui": "0.2.0",
43
- "@gigamusic/config": "0.2.0",
44
- "@gigamusic/db": "0.2.0"
31
+ "@gigamusic/core": "1.0.0",
32
+ "@gigamusic/db": "1.0.0"
45
33
  },
46
34
  "peerDependencies": {
47
35
  "@prisma/client": ">=6",
@@ -51,26 +39,19 @@
51
39
  },
52
40
  "devDependencies": {
53
41
  "@prisma/client": "^6.1.0",
54
- "@testing-library/dom": "^10.4.0",
55
- "@testing-library/react": "^16.1.0",
56
42
  "@types/node": "^22.10.5",
57
43
  "@types/react": "^19.0.0",
58
- "@types/react-dom": "^19.0.0",
59
- "jsdom": "^25.0.1",
60
44
  "next": "^16.0.0",
45
+ "prisma": "^6.1.0",
61
46
  "react": "^19.0.0",
62
- "react-dom": "^19.0.0",
63
- "tsup": "^8.3.5",
64
47
  "typescript": "^5.7.3",
65
48
  "vitest": "^2.1.8"
66
49
  },
67
50
  "scripts": {
68
- "build": "pnpm run prisma:generate && tsup",
69
- "dev": "tsup --watch",
70
51
  "lint": "eslint src",
71
52
  "test": "pnpm run prisma:generate && vitest run",
72
53
  "typecheck": "pnpm run prisma:generate && tsc --noEmit",
73
54
  "prisma:generate": "prisma generate --schema=./prisma",
74
- "clean": "rm -rf dist .turbo *.tsbuildinfo"
55
+ "clean": "rm -rf .turbo *.tsbuildinfo"
75
56
  }
76
57
  }
package/src/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- // Server-safe public entry. Everything that requires the React client runtime
2
- // (context providers, the form components, etc.) lives in `./client`.
1
+ // Public entry server-safe types, queries, and platform helpers.
2
+ // Admin handler factories live on the `./server` subpath.
3
3
 
4
4
  // ── Types ────────────────────────────────────────────────────────────────
5
5
  export type {
@@ -20,56 +20,6 @@ export { detectLinkPlatform, PLATFORM_LABELS } from "./platforms/detect";
20
20
  export type { KnownLinkPlatform } from "./platforms/detect";
21
21
  export { LinkPlatformIcon } from "./platforms/icon";
22
22
 
23
- // ── Slots ────────────────────────────────────────────────────────────────
24
- export type {
25
- LinksSlots,
26
- LinkButtonProps,
27
- LinkPageHeaderProps,
28
- } from "./slots";
29
-
30
- // ── Public render surface ───────────────────────────────────────────────
31
- export { LinkPageView } from "./components/LinkPageView";
32
- export { DefaultLinkButton } from "./components/LinkButton";
33
- export { DefaultLinkPageHeader } from "./components/LinkPageHeader";
34
-
35
- // ── Public page (per-slug landing) ──────────────────────────────────────
36
- export {
37
- PublicLinkPage,
38
- createPublicLinkPage,
39
- createPublicLinkPageMetadata,
40
- publicLinkPageMetadata,
41
- registerLinkPageMetadata,
42
- } from "./pages/PublicLinkPage";
43
- export type {
44
- PublicLinkPageProps,
45
- CreatePublicLinkPageDeps,
46
- PublicLinkPageMetadataDeps,
47
- PublicLinkPageMetadataArgs,
48
- } from "./pages/PublicLinkPage";
49
-
50
- // ── Admin page bodies ───────────────────────────────────────────────────
51
- export {
52
- AdminLinkPagesIndexPage,
53
- createAdminLinkPagesIndexPage,
54
- } from "./pages/AdminLinkPagesIndexPage";
55
- export type { AdminLinkPagesIndexPageProps } from "./pages/AdminLinkPagesIndexPage";
56
- export { AdminNewLinkPagePage } from "./pages/AdminNewLinkPagePage";
57
- export type { AdminNewLinkPagePageProps } from "./pages/AdminNewLinkPagePage";
58
- export { AdminEditLinkPagePage } from "./pages/AdminEditLinkPagePage";
59
- export type { AdminEditLinkPagePageProps } from "./pages/AdminEditLinkPagePage";
60
-
61
- // ── Admin form types (so consumers can build their own page wrappers) ───
62
- export type {
63
- LinkPageFormPage,
64
- LinkPageFormItem,
65
- LinkPageFormReleaseOption,
66
- } from "./components/admin/LinkPageForm";
67
- export type { NewLinkPageReleaseOption } from "./components/admin/NewLinkPageForm";
68
-
69
- // Admin API handler factories live on the `./server` subpath — they import
70
- // `next/cache`, which Webpack/Turbopack refuse to bundle into a client tree.
71
- // Use `import { createAdminLinkPagesHandlers } from "@gigamusic/links/server"`.
72
-
73
23
  // ── Slug validation helpers ─────────────────────────────────────────────
74
24
  export {
75
25
  SLUG_PATTERN,
@@ -1,6 +1,13 @@
1
- import type { KnownPlatform } from "@gigamusic/config";
2
-
3
- export type KnownLinkPlatform = KnownPlatform;
1
+ export type KnownLinkPlatform =
2
+ | "spotify"
3
+ | "apple-music"
4
+ | "youtube"
5
+ | "soundcloud"
6
+ | "instagram"
7
+ | "facebook"
8
+ | "tiktok"
9
+ | "x"
10
+ | "bandcamp";
4
11
 
5
12
  interface HostPattern {
6
13
  platform: KnownLinkPlatform;
package/src/server.ts CHANGED
@@ -13,10 +13,3 @@ export type {
13
13
  LinkPagesAdminDeps,
14
14
  RouteHandler,
15
15
  } from "./api/handlers";
16
- export {
17
- PublicLinkPage,
18
- createPublicLinkPage,
19
- createPublicLinkPageMetadata,
20
- publicLinkPageMetadata,
21
- registerLinkPageMetadata,
22
- } from "./pages/PublicLinkPage";
package/dist/cli.js DELETED
@@ -1,191 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFileSync, mkdirSync, existsSync, statSync, writeFileSync, readdirSync } from 'fs';
3
- import { dirname, resolve, join } from 'path';
4
- import { fileURLToPath } from 'url';
5
-
6
- function fragmentsDir(metaUrl) {
7
- const here = dirname(fileURLToPath(metaUrl));
8
- return resolve(here, "..", "prisma");
9
- }
10
- var SYNC_PREFIX = "20-";
11
- var VERSION_RE = /AUTOGENERATED by @gigamusic\/links@([^\s]+) on (\d{4}-\d{2}-\d{2})/;
12
- function listFragmentFiles(sourceDir) {
13
- return readdirSync(sourceDir).filter((f) => f.endsWith(".prisma") && !f.startsWith("_")).sort();
14
- }
15
- function buildHeader(version, today) {
16
- return `// AUTOGENERATED by @gigamusic/links@${version} on ${today} \u2014 do not edit; re-run \`pnpm gigamusic-links sync\`
17
- `;
18
- }
19
- function readExistingVersion(filePath) {
20
- if (!existsSync(filePath)) return null;
21
- const head = readFileSync(filePath, "utf8").split("\n", 2).join("\n");
22
- const match = VERSION_RE.exec(head);
23
- return match?.[1] ?? null;
24
- }
25
- function compareVersions(a, b) {
26
- if (a === b) return 0;
27
- const aParts = a.split(/[.-]/);
28
- const bParts = b.split(/[.-]/);
29
- const len = Math.max(aParts.length, bParts.length);
30
- for (let i = 0; i < len; i++) {
31
- const ai = parseInt(aParts[i] ?? "0", 10);
32
- const bi = parseInt(bParts[i] ?? "0", 10);
33
- if (Number.isNaN(ai) || Number.isNaN(bi)) {
34
- const as = aParts[i] ?? "";
35
- const bs = bParts[i] ?? "";
36
- if (as === bs) continue;
37
- return as < bs ? -1 : 1;
38
- }
39
- if (ai !== bi) return ai < bi ? -1 : 1;
40
- }
41
- return 0;
42
- }
43
- function syncFragments(opts, deps) {
44
- const cwd = opts.cwd ?? process.cwd();
45
- const target = resolve(cwd, opts.target ?? join("prisma", "schema"));
46
- const today = deps.today ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
47
- const logger = deps.logger ?? console;
48
- const header = buildHeader(deps.version, today);
49
- const written = [];
50
- const skipped = [];
51
- if (!opts.dryRun) {
52
- mkdirSync(target, { recursive: true });
53
- } else if (!existsSync(target) || !statSync(target).isDirectory()) {
54
- logger.info(`(dry-run) would create directory ${target}`);
55
- }
56
- for (const file of listFragmentFiles(deps.sourceDir)) {
57
- const srcPath = join(deps.sourceDir, file);
58
- const destPath = join(target, `${SYNC_PREFIX}${file}`);
59
- const existingVersion = readExistingVersion(destPath);
60
- if (existingVersion && compareVersions(existingVersion, deps.version) > 0) {
61
- skipped.push({
62
- file: destPath,
63
- reason: `existing version ${existingVersion} is newer than installed ${deps.version}`
64
- });
65
- logger.warn(
66
- `skip ${destPath} \u2014 existing v${existingVersion} is newer than installed v${deps.version}`
67
- );
68
- continue;
69
- }
70
- const body = readFileSync(srcPath, "utf8");
71
- const contents = header + body;
72
- if (opts.dryRun) {
73
- logger.info(`(dry-run) would write ${destPath}`);
74
- } else {
75
- writeFileSync(destPath, contents, "utf8");
76
- logger.info(`wrote ${destPath}`);
77
- }
78
- written.push(destPath);
79
- }
80
- return { written, skipped };
81
- }
82
- function doctorFragments(opts, deps) {
83
- const cwd = opts.cwd ?? process.cwd();
84
- const target = resolve(cwd, opts.target ?? join("prisma", "schema"));
85
- const stale = [];
86
- const missing = [];
87
- const logger = deps.logger ?? console;
88
- for (const file of listFragmentFiles(deps.sourceDir)) {
89
- const destPath = join(target, `${SYNC_PREFIX}${file}`);
90
- const existingVersion = readExistingVersion(destPath);
91
- if (existingVersion === null) {
92
- missing.push(destPath);
93
- logger.warn(`missing: ${destPath}`);
94
- continue;
95
- }
96
- if (compareVersions(existingVersion, deps.version) < 0) {
97
- stale.push({
98
- file: destPath,
99
- installed: deps.version,
100
- found: existingVersion
101
- });
102
- logger.warn(
103
- `stale: ${destPath} is v${existingVersion}; installed @gigamusic/links is v${deps.version}`
104
- );
105
- }
106
- }
107
- return { ok: stale.length === 0 && missing.length === 0, stale, missing };
108
- }
109
-
110
- // src/cli/index.ts
111
- function parseArgs(argv) {
112
- const [command = "help", ...rest] = argv;
113
- const parsed = {
114
- command: ["sync", "doctor", "help", "version"].includes(command) ? command : "help",
115
- dryRun: false
116
- };
117
- for (let i = 0; i < rest.length; i++) {
118
- const arg = rest[i];
119
- if (arg === "--dry-run") parsed.dryRun = true;
120
- else if (arg === "--target") parsed.target = rest[++i];
121
- else if (arg?.startsWith("--target=")) parsed.target = arg.slice("--target=".length);
122
- }
123
- return parsed;
124
- }
125
- function readPackageVersion(metaUrl) {
126
- try {
127
- const pkgPath = resolve(fragmentsDir(metaUrl), "..", "package.json");
128
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
129
- return pkg.version ?? "0.0.0";
130
- } catch {
131
- return "0.0.0";
132
- }
133
- }
134
- function printHelp() {
135
- console.log(`gigamusic-links \u2014 Prisma fragment sync for @gigamusic/links
136
-
137
- Usage:
138
- gigamusic-links sync [--target <prismaSchemaFolder>] [--dry-run]
139
- gigamusic-links doctor [--target <prismaSchemaFolder>]
140
- gigamusic-links version
141
- gigamusic-links help
142
-
143
- Fragments are written with a "20-" prefix so they sort AFTER @gigamusic/db's
144
- "10-" fragments and BEFORE consumer-owned "99-" files. Default --target is
145
- ./prisma/schema (Prisma 6 prismaSchemaFolder layout).
146
- `);
147
- }
148
- async function main(argv, metaUrl) {
149
- const args = parseArgs(argv);
150
- const sourceDir = fragmentsDir(metaUrl);
151
- const version = readPackageVersion(metaUrl);
152
- if (args.command === "help") {
153
- printHelp();
154
- return 0;
155
- }
156
- if (args.command === "version") {
157
- console.log(version);
158
- return 0;
159
- }
160
- if (args.command === "sync") {
161
- syncFragments(
162
- { target: args.target, dryRun: args.dryRun },
163
- { sourceDir, version }
164
- );
165
- return 0;
166
- }
167
- if (args.command === "doctor") {
168
- const result = doctorFragments({ target: args.target }, { sourceDir, version });
169
- if (!result.ok) {
170
- console.error(
171
- `gigamusic-links doctor: ${result.stale.length} stale, ${result.missing.length} missing`
172
- );
173
- return 1;
174
- }
175
- console.log("gigamusic-links doctor: all fragments are up to date.");
176
- return 0;
177
- }
178
- printHelp();
179
- return 1;
180
- }
181
- main(process.argv.slice(2), import.meta.url).then(
182
- (code) => process.exit(code),
183
- (err) => {
184
- console.error(err);
185
- process.exit(1);
186
- }
187
- );
188
-
189
- export { main };
190
- //# sourceMappingURL=cli.js.map
191
- //# sourceMappingURL=cli.js.map
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/cli/paths.ts","../src/cli/sync.ts","../src/cli/index.ts"],"names":["resolve","readFileSync"],"mappings":"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","file":"cli.js","sourcesContent":["import { fileURLToPath } from \"node:url\";\nimport { dirname, resolve } from \"node:path\";\n\n/**\n * Locate the shipped `prisma/` fragments directory. At runtime the CLI bundle\n * lives at `<pkg>/dist/cli.js`; the fragments live at `<pkg>/prisma/`.\n */\nexport function fragmentsDir(metaUrl: string): string {\n const here = dirname(fileURLToPath(metaUrl));\n return resolve(here, \"..\", \"prisma\");\n}\n","import {\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n statSync,\n writeFileSync,\n} from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nexport interface SyncOptions {\n /** Where the consumer's prisma schema folder lives. Defaults to `<cwd>/prisma/schema`. */\n target?: string;\n /** When true, log planned writes without touching the filesystem. */\n dryRun?: boolean;\n /** Override for testing — defaults to process.cwd(). */\n cwd?: string;\n}\n\nexport interface SyncResult {\n written: string[];\n skipped: { file: string; reason: string }[];\n}\n\nexport interface SyncDeps {\n sourceDir: string;\n version: string;\n today?: string;\n logger?: { info: (msg: string) => void; warn: (msg: string) => void };\n}\n\n/**\n * Sync prefix — `20-` orders after `10-` (gigamusic/db) and before `99-`\n * (consumer-owned). Keeping the prefix shared with @gigamusic/db's sort\n * convention so consumers don't need to learn a new rule.\n */\nconst SYNC_PREFIX = \"20-\";\n\nconst VERSION_RE = /AUTOGENERATED by @gigamusic\\/links@([^\\s]+) on (\\d{4}-\\d{2}-\\d{2})/;\n\nexport function listFragmentFiles(sourceDir: string): string[] {\n return readdirSync(sourceDir)\n .filter((f) => f.endsWith(\".prisma\") && !f.startsWith(\"_\"))\n .sort();\n}\n\nfunction buildHeader(version: string, today: string): string {\n return `// AUTOGENERATED by @gigamusic/links@${version} on ${today} — do not edit; re-run \\`pnpm gigamusic-links sync\\`\\n`;\n}\n\nfunction readExistingVersion(filePath: string): string | null {\n if (!existsSync(filePath)) return null;\n const head = readFileSync(filePath, \"utf8\").split(\"\\n\", 2).join(\"\\n\");\n const match = VERSION_RE.exec(head);\n return match?.[1] ?? null;\n}\n\n/**\n * Semver-ish comparison — accepts `x.y.z` or pre-release suffixes. Returns\n * 1/0/-1 like `Array.prototype.sort`. Workspace `workspace:*` and similar\n * non-numeric strings compare equal so we never refuse to overwrite during\n * local dev.\n */\nexport function compareVersions(a: string, b: string): number {\n if (a === b) return 0;\n const aParts = a.split(/[.-]/);\n const bParts = b.split(/[.-]/);\n const len = Math.max(aParts.length, bParts.length);\n for (let i = 0; i < len; i++) {\n const ai = parseInt(aParts[i] ?? \"0\", 10);\n const bi = parseInt(bParts[i] ?? \"0\", 10);\n if (Number.isNaN(ai) || Number.isNaN(bi)) {\n const as = aParts[i] ?? \"\";\n const bs = bParts[i] ?? \"\";\n if (as === bs) continue;\n return as < bs ? -1 : 1;\n }\n if (ai !== bi) return ai < bi ? -1 : 1;\n }\n return 0;\n}\n\nexport function syncFragments(opts: SyncOptions, deps: SyncDeps): SyncResult {\n const cwd = opts.cwd ?? process.cwd();\n const target = resolve(cwd, opts.target ?? join(\"prisma\", \"schema\"));\n const today = deps.today ?? new Date().toISOString().slice(0, 10);\n const logger = deps.logger ?? console;\n const header = buildHeader(deps.version, today);\n\n const written: string[] = [];\n const skipped: { file: string; reason: string }[] = [];\n\n if (!opts.dryRun) {\n mkdirSync(target, { recursive: true });\n } else if (!existsSync(target) || !statSync(target).isDirectory()) {\n logger.info(`(dry-run) would create directory ${target}`);\n }\n\n for (const file of listFragmentFiles(deps.sourceDir)) {\n const srcPath = join(deps.sourceDir, file);\n const destPath = join(target, `${SYNC_PREFIX}${file}`);\n const existingVersion = readExistingVersion(destPath);\n\n if (existingVersion && compareVersions(existingVersion, deps.version) > 0) {\n skipped.push({\n file: destPath,\n reason: `existing version ${existingVersion} is newer than installed ${deps.version}`,\n });\n logger.warn(\n `skip ${destPath} — existing v${existingVersion} is newer than installed v${deps.version}`,\n );\n continue;\n }\n\n const body = readFileSync(srcPath, \"utf8\");\n const contents = header + body;\n\n if (opts.dryRun) {\n logger.info(`(dry-run) would write ${destPath}`);\n } else {\n writeFileSync(destPath, contents, \"utf8\");\n logger.info(`wrote ${destPath}`);\n }\n written.push(destPath);\n }\n\n return { written, skipped };\n}\n\nexport interface DoctorOptions {\n target?: string;\n cwd?: string;\n}\n\nexport interface DoctorResult {\n ok: boolean;\n stale: { file: string; installed: string; found: string }[];\n missing: string[];\n}\n\nexport function doctorFragments(opts: DoctorOptions, deps: SyncDeps): DoctorResult {\n const cwd = opts.cwd ?? process.cwd();\n const target = resolve(cwd, opts.target ?? join(\"prisma\", \"schema\"));\n const stale: { file: string; installed: string; found: string }[] = [];\n const missing: string[] = [];\n const logger = deps.logger ?? console;\n\n for (const file of listFragmentFiles(deps.sourceDir)) {\n const destPath = join(target, `${SYNC_PREFIX}${file}`);\n const existingVersion = readExistingVersion(destPath);\n if (existingVersion === null) {\n missing.push(destPath);\n logger.warn(`missing: ${destPath}`);\n continue;\n }\n if (compareVersions(existingVersion, deps.version) < 0) {\n stale.push({\n file: destPath,\n installed: deps.version,\n found: existingVersion,\n });\n logger.warn(\n `stale: ${destPath} is v${existingVersion}; installed @gigamusic/links is v${deps.version}`,\n );\n }\n }\n\n return { ok: stale.length === 0 && missing.length === 0, stale, missing };\n}\n","import { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { fragmentsDir } from \"./paths\";\nimport { doctorFragments, syncFragments } from \"./sync\";\n\ninterface ParsedArgs {\n command: \"sync\" | \"doctor\" | \"help\" | \"version\";\n target?: string;\n dryRun: boolean;\n}\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const [command = \"help\", ...rest] = argv;\n const parsed: ParsedArgs = {\n command: ([\"sync\", \"doctor\", \"help\", \"version\"].includes(command)\n ? command\n : \"help\") as ParsedArgs[\"command\"],\n dryRun: false,\n };\n for (let i = 0; i < rest.length; i++) {\n const arg = rest[i];\n if (arg === \"--dry-run\") parsed.dryRun = true;\n else if (arg === \"--target\") parsed.target = rest[++i];\n else if (arg?.startsWith(\"--target=\")) parsed.target = arg.slice(\"--target=\".length);\n }\n return parsed;\n}\n\nfunction readPackageVersion(metaUrl: string): string {\n try {\n const pkgPath = resolve(fragmentsDir(metaUrl), \"..\", \"package.json\");\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as { version?: string };\n return pkg.version ?? \"0.0.0\";\n } catch {\n return \"0.0.0\";\n }\n}\n\nfunction printHelp() {\n console.log(`gigamusic-links — Prisma fragment sync for @gigamusic/links\n\nUsage:\n gigamusic-links sync [--target <prismaSchemaFolder>] [--dry-run]\n gigamusic-links doctor [--target <prismaSchemaFolder>]\n gigamusic-links version\n gigamusic-links help\n\nFragments are written with a \"20-\" prefix so they sort AFTER @gigamusic/db's\n\"10-\" fragments and BEFORE consumer-owned \"99-\" files. Default --target is\n./prisma/schema (Prisma 6 prismaSchemaFolder layout).\n`);\n}\n\nexport async function main(argv: string[], metaUrl: string): Promise<number> {\n const args = parseArgs(argv);\n const sourceDir = fragmentsDir(metaUrl);\n const version = readPackageVersion(metaUrl);\n\n if (args.command === \"help\") {\n printHelp();\n return 0;\n }\n if (args.command === \"version\") {\n console.log(version);\n return 0;\n }\n if (args.command === \"sync\") {\n syncFragments(\n { target: args.target, dryRun: args.dryRun },\n { sourceDir, version },\n );\n return 0;\n }\n if (args.command === \"doctor\") {\n const result = doctorFragments({ target: args.target }, { sourceDir, version });\n if (!result.ok) {\n console.error(\n `gigamusic-links doctor: ${result.stale.length} stale, ${result.missing.length} missing`,\n );\n return 1;\n }\n console.log(\"gigamusic-links doctor: all fragments are up to date.\");\n return 0;\n }\n printHelp();\n return 1;\n}\n\nmain(process.argv.slice(2), import.meta.url).then(\n (code) => process.exit(code),\n (err) => {\n console.error(err);\n process.exit(1);\n },\n);\n"]}
package/src/cli/index.ts DELETED
@@ -1,95 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import { fragmentsDir } from "./paths";
4
- import { doctorFragments, syncFragments } from "./sync";
5
-
6
- interface ParsedArgs {
7
- command: "sync" | "doctor" | "help" | "version";
8
- target?: string;
9
- dryRun: boolean;
10
- }
11
-
12
- function parseArgs(argv: string[]): ParsedArgs {
13
- const [command = "help", ...rest] = argv;
14
- const parsed: ParsedArgs = {
15
- command: (["sync", "doctor", "help", "version"].includes(command)
16
- ? command
17
- : "help") as ParsedArgs["command"],
18
- dryRun: false,
19
- };
20
- for (let i = 0; i < rest.length; i++) {
21
- const arg = rest[i];
22
- if (arg === "--dry-run") parsed.dryRun = true;
23
- else if (arg === "--target") parsed.target = rest[++i];
24
- else if (arg?.startsWith("--target=")) parsed.target = arg.slice("--target=".length);
25
- }
26
- return parsed;
27
- }
28
-
29
- function readPackageVersion(metaUrl: string): string {
30
- try {
31
- const pkgPath = resolve(fragmentsDir(metaUrl), "..", "package.json");
32
- const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { version?: string };
33
- return pkg.version ?? "0.0.0";
34
- } catch {
35
- return "0.0.0";
36
- }
37
- }
38
-
39
- function printHelp() {
40
- console.log(`gigamusic-links — Prisma fragment sync for @gigamusic/links
41
-
42
- Usage:
43
- gigamusic-links sync [--target <prismaSchemaFolder>] [--dry-run]
44
- gigamusic-links doctor [--target <prismaSchemaFolder>]
45
- gigamusic-links version
46
- gigamusic-links help
47
-
48
- Fragments are written with a "20-" prefix so they sort AFTER @gigamusic/db's
49
- "10-" fragments and BEFORE consumer-owned "99-" files. Default --target is
50
- ./prisma/schema (Prisma 6 prismaSchemaFolder layout).
51
- `);
52
- }
53
-
54
- export async function main(argv: string[], metaUrl: string): Promise<number> {
55
- const args = parseArgs(argv);
56
- const sourceDir = fragmentsDir(metaUrl);
57
- const version = readPackageVersion(metaUrl);
58
-
59
- if (args.command === "help") {
60
- printHelp();
61
- return 0;
62
- }
63
- if (args.command === "version") {
64
- console.log(version);
65
- return 0;
66
- }
67
- if (args.command === "sync") {
68
- syncFragments(
69
- { target: args.target, dryRun: args.dryRun },
70
- { sourceDir, version },
71
- );
72
- return 0;
73
- }
74
- if (args.command === "doctor") {
75
- const result = doctorFragments({ target: args.target }, { sourceDir, version });
76
- if (!result.ok) {
77
- console.error(
78
- `gigamusic-links doctor: ${result.stale.length} stale, ${result.missing.length} missing`,
79
- );
80
- return 1;
81
- }
82
- console.log("gigamusic-links doctor: all fragments are up to date.");
83
- return 0;
84
- }
85
- printHelp();
86
- return 1;
87
- }
88
-
89
- main(process.argv.slice(2), import.meta.url).then(
90
- (code) => process.exit(code),
91
- (err) => {
92
- console.error(err);
93
- process.exit(1);
94
- },
95
- );
package/src/cli/paths.ts DELETED
@@ -1,11 +0,0 @@
1
- import { fileURLToPath } from "node:url";
2
- import { dirname, resolve } from "node:path";
3
-
4
- /**
5
- * Locate the shipped `prisma/` fragments directory. At runtime the CLI bundle
6
- * lives at `<pkg>/dist/cli.js`; the fragments live at `<pkg>/prisma/`.
7
- */
8
- export function fragmentsDir(metaUrl: string): string {
9
- const here = dirname(fileURLToPath(metaUrl));
10
- return resolve(here, "..", "prisma");
11
- }