@paramour-js/next 1.0.0-rc.0 → 1.0.0-rc.1
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/emit.d.ts +7 -0
- package/dist/emit.js +9 -0
- package/dist/init/agents-md.js +7 -5
- package/dist/init/scaffold.d.ts +3 -3
- package/dist/init/scaffold.js +10 -5
- package/dist/init/wrap-next-config.js +8 -1
- package/dist/list/discover-route-defs.d.ts +4 -3
- package/dist/list/discover-route-defs.js +27 -7
- package/package.json +2 -2
package/dist/emit.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ export interface WriteIfChangedResult {
|
|
|
11
11
|
*/
|
|
12
12
|
written: boolean;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* The line ending a user-owned file already uses. Init edits files the user
|
|
16
|
+
* owns (package.json, next.config, AGENTS.md), and writing LF text into a CRLF file
|
|
17
|
+
* leaves mixed endings, so those edits keep the file's own ending. Generated
|
|
18
|
+
* files are always written LF instead.
|
|
19
|
+
*/
|
|
20
|
+
export declare function lineEnding(text: string): "\n" | "\r\n";
|
|
14
21
|
/**
|
|
15
22
|
* Content equality that ignores CRLF vs LF. A consumer repo with git
|
|
16
23
|
* `core.autocrlf=true` checks the committed LF artifact out as CRLF; that
|
package/dist/emit.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* The line ending a user-owned file already uses. Init edits files the user
|
|
5
|
+
* owns (package.json, next.config, AGENTS.md), and writing LF text into a CRLF file
|
|
6
|
+
* leaves mixed endings, so those edits keep the file's own ending. Generated
|
|
7
|
+
* files are always written LF instead.
|
|
8
|
+
*/
|
|
9
|
+
export function lineEnding(text) {
|
|
10
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* Content equality that ignores CRLF vs LF. A consumer repo with git
|
|
5
14
|
* `core.autocrlf=true` checks the committed LF artifact out as CRLF; that
|
package/dist/init/agents-md.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { lineEnding, sameContent } from "../emit.js";
|
|
3
4
|
export const AGENTS_MARKER_START = "<!-- paramour:start -->";
|
|
4
5
|
export const AGENTS_MARKER_END = "<!-- paramour:end -->";
|
|
5
6
|
/**
|
|
@@ -53,21 +54,22 @@ export function findAgentsFile(projectRoot) {
|
|
|
53
54
|
* would mean guessing where the user's own text begins.
|
|
54
55
|
*/
|
|
55
56
|
export function upsertAgentsSection(text) {
|
|
57
|
+
const eol = lineEnding(text);
|
|
58
|
+
const snippet = agentsSnippet().replaceAll("\n", eol);
|
|
56
59
|
const start = text.indexOf(AGENTS_MARKER_START);
|
|
57
60
|
if (start === -1) {
|
|
58
|
-
const base = text === "" || text.endsWith("\n") ? text : `${text}
|
|
59
|
-
const separator = base === "" ? "" :
|
|
61
|
+
const base = text === "" || text.endsWith("\n") ? text : `${text}${eol}`;
|
|
62
|
+
const separator = base === "" ? "" : eol;
|
|
60
63
|
return {
|
|
61
64
|
status: "added",
|
|
62
|
-
text: `${base}${separator}${
|
|
65
|
+
text: `${base}${separator}${snippet}${eol}`,
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
const end = text.indexOf(AGENTS_MARKER_END, start);
|
|
66
69
|
if (end === -1)
|
|
67
70
|
return { status: "unterminated", text };
|
|
68
71
|
const current = text.slice(start, end + AGENTS_MARKER_END.length);
|
|
69
|
-
|
|
70
|
-
if (current === snippet)
|
|
72
|
+
if (sameContent(current, snippet))
|
|
71
73
|
return { status: "unchanged", text };
|
|
72
74
|
return {
|
|
73
75
|
status: "updated",
|
package/dist/init/scaffold.d.ts
CHANGED
|
@@ -6,9 +6,9 @@ export interface SetupCheck {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* Insert `"paramour": "paramour generate"` into a package.json's scripts,
|
|
9
|
-
* preserving the file's own indentation and trailing-newline
|
|
10
|
-
* Throws on malformed JSON — a broken package.json is init's one
|
|
11
|
-
* prerequisite failure.
|
|
9
|
+
* preserving the file's own indentation, line ending, and trailing-newline
|
|
10
|
+
* choice. Throws on malformed JSON — a broken package.json is init's one
|
|
11
|
+
* hard prerequisite failure.
|
|
12
12
|
*/
|
|
13
13
|
export declare function addPackageScript(text: string): {
|
|
14
14
|
changed: boolean;
|
package/dist/init/scaffold.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join, relative } from "node:path";
|
|
3
|
+
import { lineEnding } from "../emit.js";
|
|
3
4
|
import { resolveRouteDirs } from "../scan.js";
|
|
4
5
|
import { loadPackagedSkill } from "../skills/packaged.js";
|
|
5
6
|
import { auditTarget, isOutdated } from "../skills/sync.js";
|
|
6
7
|
import { detectTargets } from "../skills/targets.js";
|
|
7
8
|
/**
|
|
8
9
|
* Insert `"paramour": "paramour generate"` into a package.json's scripts,
|
|
9
|
-
* preserving the file's own indentation and trailing-newline
|
|
10
|
-
* Throws on malformed JSON — a broken package.json is init's one
|
|
11
|
-
* prerequisite failure.
|
|
10
|
+
* preserving the file's own indentation, line ending, and trailing-newline
|
|
11
|
+
* choice. Throws on malformed JSON — a broken package.json is init's one
|
|
12
|
+
* hard prerequisite failure.
|
|
12
13
|
*/
|
|
13
14
|
export function addPackageScript(text) {
|
|
14
15
|
const parsed = JSON.parse(text);
|
|
@@ -28,8 +29,12 @@ export function addPackageScript(text) {
|
|
|
28
29
|
return { changed: false, text };
|
|
29
30
|
pkg.scripts = { ...scripts, paramour: "paramour generate" };
|
|
30
31
|
const indent = /^([ \t]+)"/m.exec(text)?.[1] ?? " ";
|
|
31
|
-
const
|
|
32
|
-
|
|
32
|
+
const eol = lineEnding(text);
|
|
33
|
+
const trailing = text.endsWith("\n") ? eol : "";
|
|
34
|
+
return {
|
|
35
|
+
changed: true,
|
|
36
|
+
text: JSON.stringify(pkg, null, indent).replaceAll("\n", eol) + trailing,
|
|
37
|
+
};
|
|
33
38
|
}
|
|
34
39
|
/**
|
|
35
40
|
* The detect-and-verify summary: route dirs discoverable, both packages
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { lineEnding } from "../emit.js";
|
|
3
4
|
const IMPORT_LINE = `import { withTypedRoutes } from "@paramour-js/next";`;
|
|
4
5
|
/** {@link writtenBindings}' name for `module.exports` (no identifier has a dot). */
|
|
5
6
|
const MODULE_EXPORTS = "module.exports";
|
|
@@ -131,7 +132,13 @@ export async function wrapNextConfigSource(source) {
|
|
|
131
132
|
});
|
|
132
133
|
}
|
|
133
134
|
mod.exports.default = builders.functionCall(local ?? "withTypedRoutes", current);
|
|
134
|
-
|
|
135
|
+
// recast prints new nodes with LF and drops the final newline; keep the
|
|
136
|
+
// user's file as it was on both counts.
|
|
137
|
+
const eol = lineEnding(source);
|
|
138
|
+
let code = generateCode(mod).code.replaceAll("\r\n", "\n");
|
|
139
|
+
if (source.endsWith("\n") && !code.endsWith("\n"))
|
|
140
|
+
code += "\n";
|
|
141
|
+
return { code: code.replaceAll("\n", eol), status: "wrapped" };
|
|
135
142
|
}
|
|
136
143
|
catch {
|
|
137
144
|
return { snippet: manualSnippet(), status: "manual" };
|
|
@@ -38,9 +38,10 @@ export interface RouteDefinition {
|
|
|
38
38
|
* config globs replace the default patterns when the heuristic misfires.
|
|
39
39
|
*
|
|
40
40
|
* jiti evaluates matched files (the loader carry-over from config loading).
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* Imports resolve through the project's tsconfig `paths`, like Next's own
|
|
42
|
+
* resolution, so definitions that import shared codecs via `@/…` load. One
|
|
43
|
+
* known limit is left to the per-module degrade: `server-only`-style
|
|
44
|
+
* imports throw outside Next.
|
|
44
45
|
*/
|
|
45
46
|
export declare function discoverRouteDefinitions(projectRoot: string, options?: {
|
|
46
47
|
routeFiles?: readonly string[] | undefined;
|
|
@@ -15,6 +15,11 @@ const IGNORE_PATTERNS = [
|
|
|
15
15
|
"**/node_modules/**",
|
|
16
16
|
"**/out/**",
|
|
17
17
|
];
|
|
18
|
+
// Test files often mention defineAppRoute (they test route codecs), and
|
|
19
|
+
// evaluating them outside the runner executes their top-level
|
|
20
|
+
// describe/it calls and any setup side effects. These are ignored only in
|
|
21
|
+
// the automatic scan: explicit `routeFiles` globs are taken as written.
|
|
22
|
+
const TEST_IGNORE_PATTERNS = ["**/*.{test,spec}.*", "**/__tests__/**"];
|
|
18
23
|
/** Files past this size skip the content pre-filter (bundles, lockfiles). */
|
|
19
24
|
const MAX_PREFILTER_BYTES = 512 * 1024;
|
|
20
25
|
/**
|
|
@@ -24,17 +29,24 @@ const MAX_PREFILTER_BYTES = 512 * 1024;
|
|
|
24
29
|
* config globs replace the default patterns when the heuristic misfires.
|
|
25
30
|
*
|
|
26
31
|
* jiti evaluates matched files (the loader carry-over from config loading).
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
32
|
+
* Imports resolve through the project's tsconfig `paths`, like Next's own
|
|
33
|
+
* resolution, so definitions that import shared codecs via `@/…` load. One
|
|
34
|
+
* known limit is left to the per-module degrade: `server-only`-style
|
|
35
|
+
* imports throw outside Next.
|
|
30
36
|
*/
|
|
31
37
|
export async function discoverRouteDefinitions(projectRoot, options = {}) {
|
|
32
38
|
// Dynamic imports, same stance as config.ts: only commands that actually
|
|
33
39
|
// discover definitions pay for tinyglobby/jiti.
|
|
34
40
|
const { glob } = await import("tinyglobby");
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
const { routeFiles } = options;
|
|
42
|
+
const [patterns, ignore] = routeFiles === undefined
|
|
43
|
+
? [DEFAULT_PATTERNS, [...IGNORE_PATTERNS, ...TEST_IGNORE_PATTERNS]]
|
|
44
|
+
: [[...routeFiles], IGNORE_PATTERNS];
|
|
45
|
+
const files = await glob(patterns, {
|
|
46
|
+
absolute: true,
|
|
47
|
+
cwd: projectRoot,
|
|
48
|
+
ignore,
|
|
49
|
+
});
|
|
38
50
|
// Deterministic load order — dedupe's "first wins" must not depend on
|
|
39
51
|
// filesystem enumeration order.
|
|
40
52
|
files.sort();
|
|
@@ -43,6 +55,11 @@ export async function discoverRouteDefinitions(projectRoot, options = {}) {
|
|
|
43
55
|
const jiti = createJiti(import.meta.url, {
|
|
44
56
|
fsCache: false,
|
|
45
57
|
interopDefault: true,
|
|
58
|
+
// A directory, not `true`: `true` searches upward from this package's
|
|
59
|
+
// own install location inside node_modules, not from the user's project.
|
|
60
|
+
// jiti's bundled get-tsconfig finds the governing tsconfig.json from
|
|
61
|
+
// here and handles `extends`, `baseUrl`, and wildcard patterns.
|
|
62
|
+
tsconfigPaths: projectRoot,
|
|
46
63
|
});
|
|
47
64
|
const definitions = [];
|
|
48
65
|
const duplicates = [];
|
|
@@ -55,7 +72,10 @@ export async function discoverRouteDefinitions(projectRoot, options = {}) {
|
|
|
55
72
|
mod = await jiti.import(file);
|
|
56
73
|
}
|
|
57
74
|
catch (error) {
|
|
58
|
-
|
|
75
|
+
// First line only: Node appends a multi-line "Require stack" to
|
|
76
|
+
// MODULE_NOT_FOUND, which would bury the one-line-per-module report.
|
|
77
|
+
const [firstLine = ""] = message(error).split(/\r?\n/, 1);
|
|
78
|
+
loadFailures.push({ file: relFile, message: firstLine });
|
|
59
79
|
continue;
|
|
60
80
|
}
|
|
61
81
|
if (typeof mod !== "object" || mod === null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@paramour-js/next",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"react-dom": "^19.2.0",
|
|
52
52
|
"typescript": "^6.0.3",
|
|
53
53
|
"zod": "^4.4.3",
|
|
54
|
-
"paramour": "1.0.0-rc.
|
|
54
|
+
"paramour": "1.0.0-rc.1"
|
|
55
55
|
},
|
|
56
56
|
"description": "Next.js integration for paramour: withTypedRoutes, App and Pages Router hooks, PageProps glue, and the codegen CLI.",
|
|
57
57
|
"author": "Jason Paff <jasonpaff@gmail.com>",
|