@fragno-dev/cli 0.2.0 → 0.2.3
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/.turbo/turbo-build.log +11 -7
- package/CHANGELOG.md +77 -0
- package/dist/cli.d.ts +4 -36
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +57 -626
- package/dist/cli.js.map +1 -1
- package/dist/find-fragno-databases-Depht1jV.js +184 -0
- package/dist/find-fragno-databases-Depht1jV.js.map +1 -0
- package/dist/serve-eh3Tpjhc.js +87 -0
- package/dist/serve-eh3Tpjhc.js.map +1 -0
- package/package.json +28 -29
- package/src/cli.ts +14 -12
- package/src/commands/db/generate.ts +3 -1
- package/src/commands/db/info.ts +2 -0
- package/src/commands/db/migrate.ts +3 -1
- package/src/commands/search.ts +1 -0
- package/src/commands/serve.ts +151 -0
- package/src/utils/find-fragno-databases.ts +44 -6
- package/src/utils/load-config.test.ts +42 -36
- package/src/utils/load-config.ts +5 -9
- package/tsconfig.json +1 -1
- package/vitest.config.ts +1 -0
- package/src/commands/corpus.test.ts +0 -1129
- package/src/commands/corpus.ts +0 -632
package/package.json
CHANGED
|
@@ -1,49 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fragno-dev/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
|
+
"homepage": "https://fragno.dev",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/rejot-dev/fragno.git",
|
|
9
|
+
"directory": "apps/fragno-cli"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"fragno-cli": "./bin/run.js"
|
|
13
|
+
},
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/cli.js",
|
|
16
|
+
"module": "./dist/cli.js",
|
|
17
|
+
"types": "./dist/cli.d.ts",
|
|
4
18
|
"exports": {
|
|
5
19
|
".": {
|
|
6
|
-
"development": "./src/cli.ts",
|
|
7
20
|
"types": "./dist/cli.d.ts",
|
|
8
21
|
"default": "./dist/cli.js"
|
|
9
22
|
}
|
|
10
23
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@clack/prompts": "^0.11.0",
|
|
26
|
+
"c12": "^3.3.3",
|
|
27
|
+
"gunshi": "^0.26.3",
|
|
28
|
+
"jsonc-parser": "^3.3.1",
|
|
29
|
+
"@fragno-dev/core": "0.2.2",
|
|
30
|
+
"@fragno-dev/db": "0.4.1",
|
|
31
|
+
"@fragno-dev/node": "0.0.9"
|
|
17
32
|
},
|
|
18
33
|
"devDependencies": {
|
|
19
34
|
"@types/node": "^22.19.7",
|
|
20
|
-
"@vitest/coverage-istanbul": "^
|
|
35
|
+
"@vitest/coverage-istanbul": "^4.1.0",
|
|
21
36
|
"@fragno-private/typescript-config": "0.0.1",
|
|
22
37
|
"@fragno-private/vitest-config": "0.0.0"
|
|
23
38
|
},
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"c12": "^3.3.3",
|
|
27
|
-
"gunshi": "^0.26.3",
|
|
28
|
-
"marked": "^15.0.12",
|
|
29
|
-
"marked-terminal": "^7.3.0",
|
|
30
|
-
"@fragno-dev/core": "0.2.0",
|
|
31
|
-
"@fragno-dev/corpus": "0.0.7",
|
|
32
|
-
"@fragno-dev/db": "0.3.0"
|
|
33
|
-
},
|
|
34
|
-
"main": "./dist/cli.js",
|
|
35
|
-
"module": "./dist/cli.js",
|
|
36
|
-
"types": "./dist/cli.d.ts",
|
|
37
|
-
"repository": {
|
|
38
|
-
"type": "git",
|
|
39
|
-
"url": "https://github.com/rejot-dev/fragno.git",
|
|
40
|
-
"directory": "apps/fragno-cli"
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
41
41
|
},
|
|
42
|
-
"homepage": "https://fragno.dev",
|
|
43
|
-
"license": "MIT",
|
|
44
42
|
"scripts": {
|
|
45
43
|
"build": "tsdown",
|
|
46
44
|
"build:watch": "tsdown --watch",
|
|
47
|
-
"types:check": "tsc --noEmit"
|
|
45
|
+
"types:check": "tsc --noEmit",
|
|
46
|
+
"test": "vitest run"
|
|
48
47
|
}
|
|
49
48
|
}
|
package/src/cli.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
3
7
|
import { cli, define } from "gunshi";
|
|
8
|
+
|
|
4
9
|
import { generateCommand } from "./commands/db/generate.js";
|
|
5
|
-
import { migrateCommand } from "./commands/db/migrate.js";
|
|
6
10
|
import { infoCommand } from "./commands/db/info.js";
|
|
11
|
+
import { migrateCommand } from "./commands/db/migrate.js";
|
|
7
12
|
import { searchCommand } from "./commands/search.js";
|
|
8
|
-
import { corpusCommand } from "./commands/corpus.js";
|
|
9
|
-
import { readFileSync } from "node:fs";
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { dirname, join } from "node:path";
|
|
12
13
|
|
|
13
14
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
15
|
const packageJson = JSON.parse(readFileSync(join(__dirname, "../package.json"), "utf-8"));
|
|
@@ -43,10 +44,11 @@ export async function run() {
|
|
|
43
44
|
name: "fragno-cli search",
|
|
44
45
|
version,
|
|
45
46
|
});
|
|
46
|
-
} else if (args[0] === "
|
|
47
|
-
// Run
|
|
48
|
-
await
|
|
49
|
-
|
|
47
|
+
} else if (args[0] === "serve") {
|
|
48
|
+
// Run serve command directly
|
|
49
|
+
const { serveCommand } = await import("./commands/serve.js");
|
|
50
|
+
await cli(args.slice(1), serveCommand, {
|
|
51
|
+
name: "fragno-cli serve",
|
|
50
52
|
version,
|
|
51
53
|
});
|
|
52
54
|
} else if (args[0] === "db") {
|
|
@@ -102,14 +104,14 @@ export async function run() {
|
|
|
102
104
|
console.log(" fragno-cli <COMMAND>");
|
|
103
105
|
console.log("");
|
|
104
106
|
console.log("COMMANDS:");
|
|
107
|
+
console.log(" serve Start a local HTTP server to serve fragments");
|
|
105
108
|
console.log(" db Database management commands");
|
|
106
109
|
console.log(" search Search the Fragno documentation");
|
|
107
|
-
console.log(" corpus View code examples and documentation for Fragno");
|
|
108
110
|
console.log("");
|
|
109
111
|
console.log("For more info, run any command with the `--help` flag:");
|
|
112
|
+
console.log(" fragno-cli serve --help");
|
|
110
113
|
console.log(" fragno-cli db --help");
|
|
111
114
|
console.log(" fragno-cli search --help");
|
|
112
|
-
console.log(" fragno-cli corpus --help");
|
|
113
115
|
console.log("");
|
|
114
116
|
console.log("OPTIONS:");
|
|
115
117
|
console.log(" -h, --help Display this help message");
|
|
@@ -133,4 +135,4 @@ if (import.meta.main) {
|
|
|
133
135
|
await run();
|
|
134
136
|
}
|
|
135
137
|
|
|
136
|
-
export { generateCommand, migrateCommand, infoCommand, searchCommand
|
|
138
|
+
export { generateCommand, migrateCommand, infoCommand, searchCommand };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { writeFile, mkdir } from "node:fs/promises";
|
|
2
2
|
import { resolve, dirname } from "node:path";
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
import { generateSchemaArtifacts } from "@fragno-dev/db/generation-engine";
|
|
5
|
+
import { define } from "gunshi";
|
|
6
|
+
|
|
5
7
|
import { importFragmentFiles } from "../../utils/find-fragno-databases";
|
|
6
8
|
|
|
7
9
|
// Define the db generate command with type safety
|
package/src/commands/db/info.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { executeMigrations, type ExecuteMigrationResult } from "@fragno-dev/db/generation-engine";
|
|
2
4
|
import { define } from "gunshi";
|
|
5
|
+
|
|
3
6
|
import { importFragmentFiles } from "../../utils/find-fragno-databases";
|
|
4
|
-
import { executeMigrations, type ExecuteMigrationResult } from "@fragno-dev/db/generation-engine";
|
|
5
7
|
|
|
6
8
|
export const migrateCommand = define({
|
|
7
9
|
name: "migrate",
|
package/src/commands/search.ts
CHANGED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createServer, type Server } from "node:http";
|
|
2
|
+
import { resolve, relative } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { define } from "gunshi";
|
|
5
|
+
|
|
6
|
+
import type { FragnoInstantiatedFragment } from "@fragno-dev/core";
|
|
7
|
+
import { toNodeHandler } from "@fragno-dev/node";
|
|
8
|
+
|
|
9
|
+
import { findFragnoFragments } from "../utils/find-fragno-databases";
|
|
10
|
+
import { loadConfig } from "../utils/load-config";
|
|
11
|
+
|
|
12
|
+
export const serveCommand = define({
|
|
13
|
+
name: "serve",
|
|
14
|
+
description: "Start a local HTTP server to serve one or more Fragno fragments",
|
|
15
|
+
args: {
|
|
16
|
+
port: {
|
|
17
|
+
type: "number",
|
|
18
|
+
short: "p",
|
|
19
|
+
description: "Port to listen on",
|
|
20
|
+
default: 8080,
|
|
21
|
+
},
|
|
22
|
+
host: {
|
|
23
|
+
type: "string",
|
|
24
|
+
short: "H",
|
|
25
|
+
description: "Host to bind to",
|
|
26
|
+
default: "localhost",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
run: async (ctx) => {
|
|
30
|
+
const targets = ctx.positionals;
|
|
31
|
+
const port = ctx.values.port ?? 8080;
|
|
32
|
+
const host = ctx.values.host ?? "localhost";
|
|
33
|
+
const cwd = process.cwd();
|
|
34
|
+
|
|
35
|
+
if (targets.length === 0) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
"No fragment files specified.\n\n" +
|
|
38
|
+
"Usage: fragno-cli serve <fragment-file> [fragment-file...]\n\n" +
|
|
39
|
+
"Example: fragno-cli serve ./src/my-fragment.ts",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const targetPaths = targets.map((target) => resolve(cwd, target));
|
|
44
|
+
|
|
45
|
+
// Import all fragment files and find instantiated fragments
|
|
46
|
+
const allFragments: FragnoInstantiatedFragment<
|
|
47
|
+
[],
|
|
48
|
+
unknown,
|
|
49
|
+
Record<string, unknown>,
|
|
50
|
+
Record<string, unknown>,
|
|
51
|
+
Record<string, unknown>,
|
|
52
|
+
unknown,
|
|
53
|
+
Record<string, unknown>
|
|
54
|
+
>[] = [];
|
|
55
|
+
|
|
56
|
+
for (const targetPath of targetPaths) {
|
|
57
|
+
const relativePath = relative(cwd, targetPath);
|
|
58
|
+
const config = await loadConfig(targetPath);
|
|
59
|
+
const fragments = findFragnoFragments(config);
|
|
60
|
+
|
|
61
|
+
if (fragments.length === 0) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`Warning: No instantiated fragments found in ${relativePath}.\n` +
|
|
64
|
+
`Make sure you export an instantiated fragment (e.g., the return value of createMyFragment()).\n`,
|
|
65
|
+
);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
allFragments.push(...fragments);
|
|
70
|
+
console.log(
|
|
71
|
+
` Found ${fragments.length} fragment(s) in ${relativePath}: ${fragments.map((f) => f.name).join(", ")}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (allFragments.length === 0) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
"No instantiated fragments found in any of the specified files.\n" +
|
|
78
|
+
"Make sure your files export instantiated fragments.",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build handlers mapped by mountRoute
|
|
83
|
+
const handlers = allFragments.map((fragment) => ({
|
|
84
|
+
mountRoute: fragment.mountRoute,
|
|
85
|
+
handler: toNodeHandler(fragment.handler.bind(fragment)),
|
|
86
|
+
fragment,
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
const server = createServer((req, res) => {
|
|
90
|
+
const url = req.url ?? "";
|
|
91
|
+
|
|
92
|
+
for (const { mountRoute, handler } of handlers) {
|
|
93
|
+
if (url.startsWith(mountRoute)) {
|
|
94
|
+
return handler(req, res);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
res.statusCode = 404;
|
|
99
|
+
res.setHeader("Content-Type", "application/json");
|
|
100
|
+
res.end(
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
error: "Not Found",
|
|
103
|
+
availableRoutes: handlers.map((h) => h.mountRoute),
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
server.listen(port, host, () => {
|
|
109
|
+
const hostStr = addressToString(server);
|
|
110
|
+
console.log(`\nFragno server is running on: ${hostStr}\n`);
|
|
111
|
+
|
|
112
|
+
for (const { fragment } of handlers) {
|
|
113
|
+
console.log(`Fragment: ${fragment.name}`);
|
|
114
|
+
console.log(` Mount: ${hostStr}${fragment.mountRoute}`);
|
|
115
|
+
|
|
116
|
+
const routes = fragment.routes as unknown as { method: string; path: string }[];
|
|
117
|
+
if (routes.length > 0) {
|
|
118
|
+
console.log(" Routes:");
|
|
119
|
+
for (const route of routes) {
|
|
120
|
+
console.log(` ${route.method} ${fragment.mountRoute}${route.path}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
console.log("");
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function addressToString(server: Server, protocol: "http" | "https" = "http"): string {
|
|
131
|
+
const addr = server.address();
|
|
132
|
+
if (!addr) {
|
|
133
|
+
throw new Error("Address invalid");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof addr === "string") {
|
|
137
|
+
return addr;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let host = addr.address;
|
|
141
|
+
|
|
142
|
+
if (host === "::" || host === "0.0.0.0") {
|
|
143
|
+
host = "localhost";
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (addr.family === "IPv6" && host !== "localhost") {
|
|
147
|
+
host = `[${host}]`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return `${protocol}://${host}:${addr.port}`;
|
|
151
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { instantiatedFragmentFakeSymbol } from "@fragno-dev/core/internal/symbols";
|
|
2
4
|
import {
|
|
3
5
|
fragnoDatabaseAdapterNameFakeSymbol,
|
|
4
6
|
fragnoDatabaseAdapterVersionFakeSymbol,
|
|
5
7
|
} from "@fragno-dev/db/adapters";
|
|
6
8
|
import type { AnySchema } from "@fragno-dev/db/schema";
|
|
7
|
-
|
|
9
|
+
|
|
8
10
|
import { type FragnoInstantiatedFragment } from "@fragno-dev/core";
|
|
11
|
+
import { isFragnoDatabase, type DatabaseAdapter, FragnoDatabase } from "@fragno-dev/db";
|
|
12
|
+
|
|
9
13
|
import { loadConfig } from "./load-config";
|
|
10
|
-
import { relative } from "node:path";
|
|
11
14
|
|
|
12
15
|
export async function importFragmentFile(path: string): Promise<Record<string, unknown>> {
|
|
13
16
|
// Enable dry run mode for database schema extraction
|
|
@@ -149,6 +152,39 @@ function isNewFragnoInstantiatedFragment(
|
|
|
149
152
|
);
|
|
150
153
|
}
|
|
151
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Finds all instantiated Fragno fragments in a module's exports.
|
|
157
|
+
*/
|
|
158
|
+
export function findFragnoFragments(
|
|
159
|
+
targetModule: Record<string, unknown>,
|
|
160
|
+
): FragnoInstantiatedFragment<
|
|
161
|
+
[],
|
|
162
|
+
unknown,
|
|
163
|
+
Record<string, unknown>,
|
|
164
|
+
Record<string, unknown>,
|
|
165
|
+
Record<string, unknown>,
|
|
166
|
+
unknown,
|
|
167
|
+
Record<string, unknown>
|
|
168
|
+
>[] {
|
|
169
|
+
const fragments: FragnoInstantiatedFragment<
|
|
170
|
+
[],
|
|
171
|
+
unknown,
|
|
172
|
+
Record<string, unknown>,
|
|
173
|
+
Record<string, unknown>,
|
|
174
|
+
Record<string, unknown>,
|
|
175
|
+
unknown,
|
|
176
|
+
Record<string, unknown>
|
|
177
|
+
>[] = [];
|
|
178
|
+
|
|
179
|
+
for (const [_key, value] of Object.entries(targetModule)) {
|
|
180
|
+
if (isNewFragnoInstantiatedFragment(value)) {
|
|
181
|
+
fragments.push(value);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return fragments;
|
|
186
|
+
}
|
|
187
|
+
|
|
152
188
|
/**
|
|
153
189
|
* Finds all FragnoDatabase instances in a module, including those embedded
|
|
154
190
|
* in instantiated fragments.
|
|
@@ -168,13 +204,15 @@ export function findFragnoDatabases(
|
|
|
168
204
|
const options = internal.options as Record<string, unknown>;
|
|
169
205
|
|
|
170
206
|
// Check if this is a database fragment by looking for implicit database dependencies
|
|
171
|
-
if (!deps["
|
|
207
|
+
if (!deps["schema"]) {
|
|
172
208
|
continue;
|
|
173
209
|
}
|
|
174
210
|
|
|
175
211
|
const schema = deps["schema"] as AnySchema;
|
|
176
|
-
const namespace = deps["namespace"] as string;
|
|
177
|
-
const databaseAdapter =
|
|
212
|
+
const namespace = deps["namespace"] as string | null;
|
|
213
|
+
const databaseAdapter =
|
|
214
|
+
(deps["databaseAdapter"] as DatabaseAdapter | undefined) ??
|
|
215
|
+
(options["databaseAdapter"] as DatabaseAdapter | undefined);
|
|
178
216
|
|
|
179
217
|
if (!databaseAdapter) {
|
|
180
218
|
console.warn(
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest";
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
|
|
5
|
+
import { stripJsonComments, convertTsconfigPathsToJitiAlias } from "./load-config";
|
|
6
|
+
|
|
5
7
|
describe("stripJsonComments", () => {
|
|
6
8
|
it("should strip single-line comments", () => {
|
|
7
9
|
const input = `{
|
|
8
10
|
// This is a comment
|
|
9
11
|
"key": "value"
|
|
10
12
|
}`;
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
const result = stripJsonComments(input);
|
|
14
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
15
|
+
expect(JSON.parse(result)).toEqual({ key: "value" });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("does not strip double slashes in strings", () => {
|
|
19
|
+
const input = `{
|
|
20
|
+
"key": "https://example.com/foo",
|
|
21
|
+
"another": "value"
|
|
14
22
|
}`;
|
|
15
|
-
expect(stripJsonComments(input)).toBe(
|
|
23
|
+
expect(stripJsonComments(input)).toBe(input);
|
|
16
24
|
});
|
|
17
25
|
|
|
18
26
|
it("should strip multiple single-line comments", () => {
|
|
@@ -22,13 +30,21 @@ describe("stripJsonComments", () => {
|
|
|
22
30
|
// Second comment
|
|
23
31
|
"key2": "value2"
|
|
24
32
|
}`;
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
const result = stripJsonComments(input);
|
|
34
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
35
|
+
expect(JSON.parse(result)).toEqual({ key1: "value1", key2: "value2" });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("does not strip multi line comments chars in strings", () => {
|
|
39
|
+
const input = `{
|
|
40
|
+
"compilerOptions": {
|
|
41
|
+
"paths": {
|
|
42
|
+
"~/*": ["./src/*"]
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
46
|
+
}`;
|
|
47
|
+
expect(stripJsonComments(input)).toBe(input);
|
|
32
48
|
});
|
|
33
49
|
|
|
34
50
|
it("should strip multi-line comments", () => {
|
|
@@ -37,11 +53,9 @@ describe("stripJsonComments", () => {
|
|
|
37
53
|
multi-line comment */
|
|
38
54
|
"key": "value"
|
|
39
55
|
}`;
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}`;
|
|
44
|
-
expect(stripJsonComments(input)).toBe(expected);
|
|
56
|
+
const result = stripJsonComments(input);
|
|
57
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
58
|
+
expect(JSON.parse(result)).toEqual({ key: "value" });
|
|
45
59
|
});
|
|
46
60
|
|
|
47
61
|
it("should strip multiple multi-line comments", () => {
|
|
@@ -52,13 +66,9 @@ describe("stripJsonComments", () => {
|
|
|
52
66
|
spanning lines */
|
|
53
67
|
"key2": "value2"
|
|
54
68
|
}`;
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"key2": "value2"
|
|
60
|
-
}`;
|
|
61
|
-
expect(stripJsonComments(input)).toBe(expected);
|
|
69
|
+
const result = stripJsonComments(input);
|
|
70
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
71
|
+
expect(JSON.parse(result)).toEqual({ key1: "value1", key2: "value2" });
|
|
62
72
|
});
|
|
63
73
|
|
|
64
74
|
it("should strip both single-line and multi-line comments", () => {
|
|
@@ -69,13 +79,9 @@ describe("stripJsonComments", () => {
|
|
|
69
79
|
comment */
|
|
70
80
|
"key2": "value2" // Another single line
|
|
71
81
|
}`;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
"key2": "value2"
|
|
77
|
-
}`;
|
|
78
|
-
expect(stripJsonComments(input)).toBe(expected);
|
|
82
|
+
const result = stripJsonComments(input);
|
|
83
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
84
|
+
expect(JSON.parse(result)).toEqual({ key1: "value1", key2: "value2" });
|
|
79
85
|
});
|
|
80
86
|
|
|
81
87
|
it("should handle strings with comment-like content", () => {
|
|
@@ -83,12 +89,12 @@ describe("stripJsonComments", () => {
|
|
|
83
89
|
"url": "https://example.com",
|
|
84
90
|
"comment": "This // is not a comment"
|
|
85
91
|
}`;
|
|
86
|
-
// Note: This is a known limitation - the simple regex approach
|
|
87
|
-
// will strip what looks like comments even inside strings
|
|
88
|
-
// For tsconfig.json files this is typically fine since URLs/strings
|
|
89
|
-
// with comment syntax are rare
|
|
90
92
|
const result = stripJsonComments(input);
|
|
91
|
-
expect(result).
|
|
93
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
94
|
+
expect(JSON.parse(result)).toEqual({
|
|
95
|
+
url: "https://example.com",
|
|
96
|
+
comment: "This // is not a comment",
|
|
97
|
+
});
|
|
92
98
|
});
|
|
93
99
|
|
|
94
100
|
it("should handle empty input", () => {
|
package/src/utils/load-config.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
2
|
import { readFile, access } from "node:fs/promises";
|
|
3
3
|
import { dirname, resolve, join } from "node:path";
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
import { loadConfig as c12LoadConfig } from "c12";
|
|
6
|
+
import { stripComments } from "jsonc-parser";
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Checks if a file exists using async API.
|
|
@@ -37,13 +39,7 @@ async function findTsconfig(startPath: string): Promise<string | null> {
|
|
|
37
39
|
* Strips comments from JSONC (JSON with Comments) content.
|
|
38
40
|
*/
|
|
39
41
|
export function stripJsonComments(jsonc: string): string {
|
|
40
|
-
|
|
41
|
-
let result = jsonc.replace(/\/\/[^\n]*/g, "");
|
|
42
|
-
|
|
43
|
-
// Remove multi-line comments (/* ... */)
|
|
44
|
-
result = result.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
45
|
-
|
|
46
|
-
return result;
|
|
42
|
+
return stripComments(jsonc);
|
|
47
43
|
}
|
|
48
44
|
|
|
49
45
|
/**
|
package/tsconfig.json
CHANGED