@gigamusic/links 0.3.0 → 2.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/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"]}
@@ -1,29 +0,0 @@
1
- // Internal — exists ONLY so `prisma generate --schema=./prisma` in THIS
2
- // package can produce a PrismaClient with LinkPage + LinkPageItem on the
3
- // delegate. The `gigamusic-links sync` CLI skips files starting with `_`,
4
- // so this is never copied into a consumer app. The consumer's merged
5
- // schema gets `Release` from `@gigamusic/db`; here we declare a minimal
6
- // stub so the link-page.prisma relation compiles in isolation.
7
-
8
- datasource db {
9
- provider = "postgresql"
10
- url = env("DATABASE_URL")
11
- }
12
-
13
- generator client {
14
- provider = "prisma-client-js"
15
- binaryTargets = ["native"]
16
- }
17
-
18
- /// Minimal stub of @gigamusic/db's `Release` model — only the fields this
19
- /// package's queries select. The consumer's merged schema redefines `Release`
20
- /// with all its fields; this stub only needs to satisfy the link-page relation
21
- /// and the columns we read.
22
- model Release {
23
- id Int @id @default(autoincrement())
24
- name String
25
- slug String @unique
26
- coverImageUrl String?
27
- isPublished Boolean @default(false)
28
- linkPages LinkPage[]
29
- }
@@ -1,54 +0,0 @@
1
- // gigamusic link-page schema fragment
2
- //
3
- // Shipped by `@gigamusic/links` and copied into a consumer app's
4
- // `prisma/schema/` directory by `gigamusic-links sync` with a `20-` prefix
5
- // (after `10-` fragments from @gigamusic/db, before consumer-owned `99-`).
6
- //
7
- // `LinkPage` references the `Release` model from @gigamusic/db via the
8
- // `releaseId` scalar foreign key. Prisma requires *both* sides of every
9
- // relation to be declared, so for the `release Release?` relation to compile
10
- // the consumer must extend the upstream `Release` model with:
11
- //
12
- // model Release {
13
- // // ...existing fields...
14
- // linkPages LinkPage[]
15
- // }
16
- //
17
- // The demo + create-gigamusic-app scaffolding wire this up automatically.
18
- //
19
- // Reserved model names: LinkPage, LinkPageItem
20
- // Reserved table names: link_pages, link_page_items
21
-
22
- model LinkPage {
23
- id Int @id @default(autoincrement())
24
- slug String @unique
25
- title String
26
- description String?
27
- coverImageUrl String?
28
- releaseId Int?
29
- isPublished Boolean @default(true)
30
- createdAt DateTime @default(now())
31
- updatedAt DateTime @updatedAt
32
-
33
- release Release? @relation(fields: [releaseId], references: [id], onDelete: SetNull)
34
- items LinkPageItem[]
35
-
36
- @@index([releaseId])
37
- @@map("link_pages")
38
- }
39
-
40
- model LinkPageItem {
41
- id Int @id @default(autoincrement())
42
- pageId Int
43
- title String
44
- url String
45
- position Int @default(0)
46
- isVisible Boolean @default(true)
47
- createdAt DateTime @default(now())
48
- updatedAt DateTime @updatedAt
49
-
50
- page LinkPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
51
-
52
- @@index([pageId])
53
- @@map("link_page_items")
54
- }
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
- }
package/src/cli/sync.ts DELETED
@@ -1,169 +0,0 @@
1
- import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- readdirSync,
6
- statSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { join, resolve } from "node:path";
10
-
11
- export interface SyncOptions {
12
- /** Where the consumer's prisma schema folder lives. Defaults to `<cwd>/prisma/schema`. */
13
- target?: string;
14
- /** When true, log planned writes without touching the filesystem. */
15
- dryRun?: boolean;
16
- /** Override for testing — defaults to process.cwd(). */
17
- cwd?: string;
18
- }
19
-
20
- export interface SyncResult {
21
- written: string[];
22
- skipped: { file: string; reason: string }[];
23
- }
24
-
25
- export interface SyncDeps {
26
- sourceDir: string;
27
- version: string;
28
- today?: string;
29
- logger?: { info: (msg: string) => void; warn: (msg: string) => void };
30
- }
31
-
32
- /**
33
- * Sync prefix — `20-` orders after `10-` (gigamusic/db) and before `99-`
34
- * (consumer-owned). Keeping the prefix shared with @gigamusic/db's sort
35
- * convention so consumers don't need to learn a new rule.
36
- */
37
- const SYNC_PREFIX = "20-";
38
-
39
- const VERSION_RE = /AUTOGENERATED by @gigamusic\/links@([^\s]+) on (\d{4}-\d{2}-\d{2})/;
40
-
41
- export function listFragmentFiles(sourceDir: string): string[] {
42
- return readdirSync(sourceDir)
43
- .filter((f) => f.endsWith(".prisma") && !f.startsWith("_"))
44
- .sort();
45
- }
46
-
47
- function buildHeader(version: string, today: string): string {
48
- return `// AUTOGENERATED by @gigamusic/links@${version} on ${today} — do not edit; re-run \`pnpm gigamusic-links sync\`\n`;
49
- }
50
-
51
- function readExistingVersion(filePath: string): string | null {
52
- if (!existsSync(filePath)) return null;
53
- const head = readFileSync(filePath, "utf8").split("\n", 2).join("\n");
54
- const match = VERSION_RE.exec(head);
55
- return match?.[1] ?? null;
56
- }
57
-
58
- /**
59
- * Semver-ish comparison — accepts `x.y.z` or pre-release suffixes. Returns
60
- * 1/0/-1 like `Array.prototype.sort`. Workspace `workspace:*` and similar
61
- * non-numeric strings compare equal so we never refuse to overwrite during
62
- * local dev.
63
- */
64
- export function compareVersions(a: string, b: string): number {
65
- if (a === b) return 0;
66
- const aParts = a.split(/[.-]/);
67
- const bParts = b.split(/[.-]/);
68
- const len = Math.max(aParts.length, bParts.length);
69
- for (let i = 0; i < len; i++) {
70
- const ai = parseInt(aParts[i] ?? "0", 10);
71
- const bi = parseInt(bParts[i] ?? "0", 10);
72
- if (Number.isNaN(ai) || Number.isNaN(bi)) {
73
- const as = aParts[i] ?? "";
74
- const bs = bParts[i] ?? "";
75
- if (as === bs) continue;
76
- return as < bs ? -1 : 1;
77
- }
78
- if (ai !== bi) return ai < bi ? -1 : 1;
79
- }
80
- return 0;
81
- }
82
-
83
- export function syncFragments(opts: SyncOptions, deps: SyncDeps): SyncResult {
84
- const cwd = opts.cwd ?? process.cwd();
85
- const target = resolve(cwd, opts.target ?? join("prisma", "schema"));
86
- const today = deps.today ?? new Date().toISOString().slice(0, 10);
87
- const logger = deps.logger ?? console;
88
- const header = buildHeader(deps.version, today);
89
-
90
- const written: string[] = [];
91
- const skipped: { file: string; reason: string }[] = [];
92
-
93
- if (!opts.dryRun) {
94
- mkdirSync(target, { recursive: true });
95
- } else if (!existsSync(target) || !statSync(target).isDirectory()) {
96
- logger.info(`(dry-run) would create directory ${target}`);
97
- }
98
-
99
- for (const file of listFragmentFiles(deps.sourceDir)) {
100
- const srcPath = join(deps.sourceDir, file);
101
- const destPath = join(target, `${SYNC_PREFIX}${file}`);
102
- const existingVersion = readExistingVersion(destPath);
103
-
104
- if (existingVersion && compareVersions(existingVersion, deps.version) > 0) {
105
- skipped.push({
106
- file: destPath,
107
- reason: `existing version ${existingVersion} is newer than installed ${deps.version}`,
108
- });
109
- logger.warn(
110
- `skip ${destPath} — existing v${existingVersion} is newer than installed v${deps.version}`,
111
- );
112
- continue;
113
- }
114
-
115
- const body = readFileSync(srcPath, "utf8");
116
- const contents = header + body;
117
-
118
- if (opts.dryRun) {
119
- logger.info(`(dry-run) would write ${destPath}`);
120
- } else {
121
- writeFileSync(destPath, contents, "utf8");
122
- logger.info(`wrote ${destPath}`);
123
- }
124
- written.push(destPath);
125
- }
126
-
127
- return { written, skipped };
128
- }
129
-
130
- export interface DoctorOptions {
131
- target?: string;
132
- cwd?: string;
133
- }
134
-
135
- export interface DoctorResult {
136
- ok: boolean;
137
- stale: { file: string; installed: string; found: string }[];
138
- missing: string[];
139
- }
140
-
141
- export function doctorFragments(opts: DoctorOptions, deps: SyncDeps): DoctorResult {
142
- const cwd = opts.cwd ?? process.cwd();
143
- const target = resolve(cwd, opts.target ?? join("prisma", "schema"));
144
- const stale: { file: string; installed: string; found: string }[] = [];
145
- const missing: string[] = [];
146
- const logger = deps.logger ?? console;
147
-
148
- for (const file of listFragmentFiles(deps.sourceDir)) {
149
- const destPath = join(target, `${SYNC_PREFIX}${file}`);
150
- const existingVersion = readExistingVersion(destPath);
151
- if (existingVersion === null) {
152
- missing.push(destPath);
153
- logger.warn(`missing: ${destPath}`);
154
- continue;
155
- }
156
- if (compareVersions(existingVersion, deps.version) < 0) {
157
- stale.push({
158
- file: destPath,
159
- installed: deps.version,
160
- found: existingVersion,
161
- });
162
- logger.warn(
163
- `stale: ${destPath} is v${existingVersion}; installed @gigamusic/links is v${deps.version}`,
164
- );
165
- }
166
- }
167
-
168
- return { ok: stale.length === 0 && missing.length === 0, stale, missing };
169
- }
package/src/client.ts DELETED
@@ -1,10 +0,0 @@
1
- "use client";
2
-
3
- // Client-only entry — components that use React context or browser APIs.
4
- export { LinksSlotsProvider, useLinksSlot, defaultLinkSlots } from "./slots-context";
5
- export { LinkPageView } from "./components/LinkPageView";
6
- export { DefaultLinkButton } from "./components/LinkButton";
7
- export { DefaultLinkPageHeader } from "./components/LinkPageHeader";
8
- export { LinkPageForm } from "./components/admin/LinkPageForm";
9
- export { NewLinkPageForm } from "./components/admin/NewLinkPageForm";
10
- export { DeleteLinkPageButton } from "./components/admin/DeleteLinkPageButton";
@@ -1,20 +0,0 @@
1
- import { LinkPlatformIcon } from "../platforms/icon";
2
- import type { LinkButtonProps } from "../slots";
3
-
4
- /**
5
- * Default link-button: a pill anchor with the platform icon (if known) and the
6
- * link title. External URLs open in a new tab; same-origin URLs stay in-tab.
7
- * Consumers can override via `<GigamusicProvider components={{ LinkButton: ... }}>`.
8
- */
9
- export function DefaultLinkButton({ href, title, platform, internal }: LinkButtonProps) {
10
- return (
11
- <a
12
- href={href}
13
- {...(!internal && { target: "_blank", rel: "noopener noreferrer" })}
14
- className="flex items-center justify-center gap-3 w-full py-3.5 px-4 rounded-full border border-[var(--gm-color-primary)]/30 bg-[var(--gm-color-primary)]/10 backdrop-blur-sm text-[color:var(--gm-color-foreground,white)] font-medium text-sm uppercase tracking-wide hover:bg-[var(--gm-color-primary)]/20 hover:border-[var(--gm-color-primary)]/60 transition-all duration-200"
15
- >
16
- {platform && <LinkPlatformIcon platform={platform} size={18} />}
17
- <span>{title}</span>
18
- </a>
19
- );
20
- }
@@ -1,35 +0,0 @@
1
- import type { LinkPageHeaderProps } from "../slots";
2
-
3
- /**
4
- * Default header for `LinkPageView`: optional cover image, page title, and
5
- * description. Consumers can override via `<GigamusicProvider components={{
6
- * LinkPageHeader: ... }}>` to swap in a logo, custom typography, etc.
7
- */
8
- export function DefaultLinkPageHeader({ title, description, cover }: LinkPageHeaderProps) {
9
- return (
10
- <>
11
- {cover && (
12
- <div className="w-36 h-36 sm:w-44 sm:h-44 rounded-2xl overflow-hidden ring-2 ring-white/20">
13
- {/* Plain <img> to keep the component framework-agnostic; consumers
14
- wanting next/image can override this slot. */}
15
- {/* eslint-disable-next-line @next/next/no-img-element */}
16
- <img
17
- src={cover.src}
18
- alt={cover.alt}
19
- width={400}
20
- height={400}
21
- className="w-full h-full object-cover"
22
- loading="eager"
23
- decoding="async"
24
- />
25
- </div>
26
- )}
27
- <div className="text-center">
28
- <h1 className="text-2xl font-semibold tracking-tight">{title}</h1>
29
- {description && (
30
- <p className="text-white/60 text-sm mt-1 whitespace-pre-line">{description}</p>
31
- )}
32
- </div>
33
- </>
34
- );
35
- }