sdnext 0.0.26 → 0.0.28
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/utils/createAction.js +7 -3
- package/dist/utils/createRoute.js +11 -5
- package/dist/utils/hook.js +11 -6
- package/dist/utils/resolveProjectImportPath.d.ts +1 -0
- package/dist/utils/resolveProjectImportPath.js +201 -0
- package/dist/utils/runCommand.js +23 -2
- package/package.json +1 -1
- package/src/utils/createAction.ts +8 -3
- package/src/utils/createRoute.ts +11 -5
- package/src/utils/hook.ts +11 -6
- package/src/utils/resolveProjectImportPath.ts +256 -0
- package/src/utils/runCommand.ts +31 -2
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath.js";
|
|
2
3
|
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact.js";
|
|
3
4
|
async function createAction(path) {
|
|
4
5
|
const info = getSharedModuleInfo(path);
|
|
5
6
|
if (!isScriptModule(info.relativePath)) return;
|
|
7
|
+
const actionPath = join("actions", info.relativePath);
|
|
8
|
+
const createResponseFnImportPath = await resolveProjectImportPath(actionPath, "server/createResponseFn");
|
|
9
|
+
const sharedImportPath = await resolveProjectImportPath(actionPath, `shared/${info.importPath}`);
|
|
6
10
|
await writeGeneratedFile({
|
|
7
|
-
path:
|
|
11
|
+
path: actionPath,
|
|
8
12
|
content: `"use server"
|
|
9
13
|
|
|
10
|
-
import { createResponseFn } from "
|
|
14
|
+
import { createResponseFn } from "${createResponseFnImportPath}"
|
|
11
15
|
|
|
12
|
-
import { ${info.name} } from "
|
|
16
|
+
import { ${info.name} } from "${sharedImportPath}"
|
|
13
17
|
|
|
14
18
|
export const ${info.name}Action = createResponseFn(${info.name})
|
|
15
19
|
`
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readdir } from "fs/promises";
|
|
2
2
|
import { join } from "path";
|
|
3
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath.js";
|
|
3
4
|
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact.js";
|
|
4
5
|
async function createRoute(path) {
|
|
5
6
|
if (path) {
|
|
@@ -7,9 +8,10 @@ async function createRoute(path) {
|
|
|
7
8
|
if (!isScriptModule(info.relativePath)) return;
|
|
8
9
|
}
|
|
9
10
|
const modules = await getSharedModules("shared");
|
|
11
|
+
const routePath = join("app", "api", "action", "[...action]", "route.ts");
|
|
10
12
|
await writeGeneratedFile({
|
|
11
|
-
path:
|
|
12
|
-
content: getRouteFileContent(modules)
|
|
13
|
+
path: routePath,
|
|
14
|
+
content: await getRouteFileContent(modules, routePath)
|
|
13
15
|
});
|
|
14
16
|
}
|
|
15
17
|
async function getSharedModules(dir) {
|
|
@@ -33,12 +35,16 @@ async function getSharedModules(dir) {
|
|
|
33
35
|
modules.sort((a, b)=>a.importPath.localeCompare(b.importPath));
|
|
34
36
|
return modules;
|
|
35
37
|
}
|
|
36
|
-
function getRouteFileContent(items) {
|
|
37
|
-
const
|
|
38
|
+
async function getRouteFileContent(items, routePath) {
|
|
39
|
+
const createRouteFnImportPath = await resolveProjectImportPath(routePath, "server/createResponseFn");
|
|
40
|
+
const importLines = (await Promise.all(items.map(async (item)=>{
|
|
41
|
+
const importPath = await resolveProjectImportPath(routePath, `shared/${item.importPath}`);
|
|
42
|
+
return `import { ${item.name} } from "${importPath}"`;
|
|
43
|
+
}))).join("\n");
|
|
38
44
|
const registerLines = items.map((item)=>`registerRoute(${item.name})`).join("\n");
|
|
39
45
|
return `import { NextRequest, NextResponse } from "next/server"
|
|
40
46
|
|
|
41
|
-
import { createRouteFn, OriginalResponseFn, RouteBodyType, RouteHandler } from "
|
|
47
|
+
import { createRouteFn, OriginalResponseFn, RouteBodyType, RouteHandler } from "${createRouteFnImportPath}"
|
|
42
48
|
${importLines ? `\n${importLines}\n` : ""}
|
|
43
49
|
const routeMap = new Map<string, RouteHandler>()
|
|
44
50
|
|
package/dist/utils/hook.js
CHANGED
|
@@ -3,6 +3,7 @@ import { join, parse, relative } from "path";
|
|
|
3
3
|
import { cwd } from "process";
|
|
4
4
|
import { checkbox as prompts_checkbox, select as prompts_select } from "@inquirer/prompts";
|
|
5
5
|
import { readSdNextSetting } from "./readSdNextSetting.js";
|
|
6
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath.js";
|
|
6
7
|
import { isScriptModule, normalizePathSeparator, writeGeneratedFile } from "./sharedArtifact.js";
|
|
7
8
|
import { writeSdNextSetting } from "./writeSdNextSetting.js";
|
|
8
9
|
function getHookTypeFromName(name) {
|
|
@@ -20,7 +21,7 @@ async function getHookTypeFromContent(path, content) {
|
|
|
20
21
|
const type = setting.hook?.[path];
|
|
21
22
|
if (void 0 !== type && "skip" !== type) return type;
|
|
22
23
|
if (content.includes("useMutation")) return "mutation";
|
|
23
|
-
if (content.includes("createUse") &&
|
|
24
|
+
if (content.includes("createUse") && /from\s+["'][^"']*\/presets\//.test(content)) return "mutation";
|
|
24
25
|
if (content.includes("ClientOptional")) return "get";
|
|
25
26
|
if (content.includes("useQuery")) return "query";
|
|
26
27
|
}
|
|
@@ -51,11 +52,15 @@ async function createHook(path, hookMap) {
|
|
|
51
52
|
const mutationPresetPath = join("presets", dir, mutationPresetName);
|
|
52
53
|
const mutationPresetImportPath = normalizePathSeparator(join(dir, `createUse${upName}`));
|
|
53
54
|
const clientInputType = `${upName}ClientInput`;
|
|
55
|
+
const actionPath = normalizePathSeparator(join("actions", actionImportPath));
|
|
56
|
+
const hookActionImportPath = await resolveProjectImportPath(hookPath, actionPath);
|
|
57
|
+
const hookPresetImportPath = await resolveProjectImportPath(hookPath, normalizePathSeparator(join("presets", mutationPresetImportPath)));
|
|
58
|
+
const mutationPresetSharedImportPath = await resolveProjectImportPath(mutationPresetPath, normalizePathSeparator(join("shared", actionImportPath)));
|
|
54
59
|
const mutationHook = `import { createRequestFn } from "deepsea-tools"
|
|
55
60
|
|
|
56
|
-
import { ${name}Action } from "
|
|
61
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
57
62
|
|
|
58
|
-
import { createUse${upName} } from "
|
|
63
|
+
import { createUse${upName} } from "${hookPresetImportPath}"
|
|
59
64
|
|
|
60
65
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
61
66
|
|
|
@@ -65,7 +70,7 @@ export const use${upName} = createUse${upName}(${name}Client)
|
|
|
65
70
|
|
|
66
71
|
import { withUseMutationDefaults } from "soda-tanstack-query"
|
|
67
72
|
|
|
68
|
-
import { ${name} } from "
|
|
73
|
+
import { ${name} } from "${mutationPresetSharedImportPath}"
|
|
69
74
|
|
|
70
75
|
export const createUse${upName} = withUseMutationDefaults<typeof ${name}>(() => {
|
|
71
76
|
const key = useId()
|
|
@@ -99,7 +104,7 @@ export const createUse${upName} = withUseMutationDefaults<typeof ${name}>(() =>
|
|
|
99
104
|
const getHook = `import { createRequestFn, isNonNullable } from "deepsea-tools"
|
|
100
105
|
import { createUseQuery } from "soda-tanstack-query"
|
|
101
106
|
|
|
102
|
-
import { ${name}Action } from "
|
|
107
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
103
108
|
|
|
104
109
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
105
110
|
|
|
@@ -117,7 +122,7 @@ export const use${upName} = createUseQuery({
|
|
|
117
122
|
const queryHook = `import { createRequestFn } from "deepsea-tools"
|
|
118
123
|
import { createUseQuery } from "soda-tanstack-query"
|
|
119
124
|
|
|
120
|
-
import { ${name}Action } from "
|
|
125
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
121
126
|
|
|
122
127
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
123
128
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveProjectImportPath(fromPath: string, targetPath: string): Promise<string>;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readFile } from "node:fs/promises";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path";
|
|
5
|
+
import { cwd } from "node:process";
|
|
6
|
+
import { normalizePathSeparator } from "./sharedArtifact.js";
|
|
7
|
+
const resolveProjectImportPath_require = createRequire(import.meta.url);
|
|
8
|
+
const projectRoot = cwd();
|
|
9
|
+
let rootAliasPromise;
|
|
10
|
+
async function resolveProjectImportPath(fromPath, targetPath) {
|
|
11
|
+
const normalizedTargetPath = normalizePathSeparator(targetPath).replace(/^\.?\//, "");
|
|
12
|
+
const rootAlias = await getProjectRootAlias();
|
|
13
|
+
if (rootAlias) return `${rootAlias}/${normalizedTargetPath}`;
|
|
14
|
+
return getRelativeImportPath(fromPath, normalizedTargetPath);
|
|
15
|
+
}
|
|
16
|
+
function getRelativeImportPath(fromPath, targetPath) {
|
|
17
|
+
const fromDir = dirname(fromPath);
|
|
18
|
+
let importPath = normalizePathSeparator(relative(fromDir, targetPath));
|
|
19
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`;
|
|
20
|
+
return importPath;
|
|
21
|
+
}
|
|
22
|
+
async function getProjectRootAlias() {
|
|
23
|
+
rootAliasPromise ??= resolveProjectRootAlias();
|
|
24
|
+
return rootAliasPromise;
|
|
25
|
+
}
|
|
26
|
+
async function resolveProjectRootAlias() {
|
|
27
|
+
const configPath = await findProjectConfigPath(projectRoot);
|
|
28
|
+
if (!configPath) return;
|
|
29
|
+
return readRootAliasFromConfig(configPath, new Set());
|
|
30
|
+
}
|
|
31
|
+
async function findProjectConfigPath(path) {
|
|
32
|
+
const candidates = [
|
|
33
|
+
join(path, "tsconfig.json"),
|
|
34
|
+
join(path, "jsconfig.json")
|
|
35
|
+
];
|
|
36
|
+
for (const candidate of candidates)if (await exists(candidate)) return candidate;
|
|
37
|
+
}
|
|
38
|
+
async function readRootAliasFromConfig(configPath, seen) {
|
|
39
|
+
const normalizedPath = normalize(configPath);
|
|
40
|
+
if (seen.has(normalizedPath)) return;
|
|
41
|
+
seen.add(normalizedPath);
|
|
42
|
+
const config = await readJsonConfig(configPath);
|
|
43
|
+
const compilerOptions = toObject(config.compilerOptions);
|
|
44
|
+
const configDir = dirname(configPath);
|
|
45
|
+
const baseDir = getBaseDir(configDir, compilerOptions.baseUrl);
|
|
46
|
+
const paths = toPathMap(compilerOptions.paths);
|
|
47
|
+
if (paths) {
|
|
48
|
+
const rootAlias = findRootAlias(paths, baseDir, projectRoot);
|
|
49
|
+
if (rootAlias) return rootAlias;
|
|
50
|
+
}
|
|
51
|
+
const extendsValue = "string" == typeof config.extends ? config.extends : void 0;
|
|
52
|
+
if (!extendsValue) return;
|
|
53
|
+
const extendsPath = await resolveExtendsPath(extendsValue, configDir);
|
|
54
|
+
if (!extendsPath) return;
|
|
55
|
+
return readRootAliasFromConfig(extendsPath, seen);
|
|
56
|
+
}
|
|
57
|
+
function getBaseDir(configDir, baseUrl) {
|
|
58
|
+
if ("string" != typeof baseUrl) return configDir;
|
|
59
|
+
return resolve(configDir, baseUrl);
|
|
60
|
+
}
|
|
61
|
+
function findRootAlias(paths, baseDir, rootDir) {
|
|
62
|
+
for (const [key, targets] of Object.entries(paths)){
|
|
63
|
+
const alias = getAliasName(key);
|
|
64
|
+
if (alias) {
|
|
65
|
+
for (const target of targets)if (isRootTarget(target, baseDir, rootDir)) return alias;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function getAliasName(pattern) {
|
|
70
|
+
const wildcard = splitWildcard(pattern);
|
|
71
|
+
if (!wildcard || wildcard.suffix.length > 0) return;
|
|
72
|
+
const aliasPrefix = wildcard.prefix.replace(/\/$/, "");
|
|
73
|
+
if (!aliasPrefix) return;
|
|
74
|
+
return aliasPrefix;
|
|
75
|
+
}
|
|
76
|
+
function isRootTarget(pattern, baseDir, rootDir) {
|
|
77
|
+
const wildcard = splitWildcard(pattern);
|
|
78
|
+
if (!wildcard || wildcard.suffix.length > 0) return false;
|
|
79
|
+
const targetPath = resolve(baseDir, wildcard.prefix || ".");
|
|
80
|
+
return normalizeForComparison(targetPath) === normalizeForComparison(rootDir);
|
|
81
|
+
}
|
|
82
|
+
function splitWildcard(value) {
|
|
83
|
+
const index = value.indexOf("*");
|
|
84
|
+
if (index < 0) return;
|
|
85
|
+
if (value.indexOf("*", index + 1) >= 0) return;
|
|
86
|
+
return {
|
|
87
|
+
prefix: value.slice(0, index),
|
|
88
|
+
suffix: value.slice(index + 1)
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function resolveExtendsPath(extendsValue, baseDir) {
|
|
92
|
+
if (isAbsolute(extendsValue) || extendsValue.startsWith(".")) {
|
|
93
|
+
const relativeCandidates = getRelativeCandidates(resolve(baseDir, extendsValue));
|
|
94
|
+
for (const candidate of relativeCandidates)if (await exists(candidate)) return candidate;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const moduleCandidates = getModuleCandidates(extendsValue);
|
|
98
|
+
for (const candidate of moduleCandidates)try {
|
|
99
|
+
return resolveProjectImportPath_require.resolve(candidate, {
|
|
100
|
+
paths: [
|
|
101
|
+
baseDir
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {}
|
|
105
|
+
}
|
|
106
|
+
function getRelativeCandidates(path) {
|
|
107
|
+
if (path.endsWith(".json")) return [
|
|
108
|
+
path
|
|
109
|
+
];
|
|
110
|
+
return [
|
|
111
|
+
path,
|
|
112
|
+
`${path}.json`
|
|
113
|
+
];
|
|
114
|
+
}
|
|
115
|
+
function getModuleCandidates(path) {
|
|
116
|
+
if (path.endsWith(".json")) return [
|
|
117
|
+
path
|
|
118
|
+
];
|
|
119
|
+
return [
|
|
120
|
+
path,
|
|
121
|
+
`${path}.json`
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
async function readJsonConfig(path) {
|
|
125
|
+
const content = await readFile(path, "utf-8");
|
|
126
|
+
const parsed = JSON.parse(stripTrailingCommas(stripComments(content)));
|
|
127
|
+
return toObject(parsed);
|
|
128
|
+
}
|
|
129
|
+
function stripComments(content) {
|
|
130
|
+
const output = [];
|
|
131
|
+
let inString = false;
|
|
132
|
+
let escaped = false;
|
|
133
|
+
for(let index = 0; index < content.length; index++){
|
|
134
|
+
const char = content[index];
|
|
135
|
+
const next = content[index + 1];
|
|
136
|
+
if (inString) {
|
|
137
|
+
output.push(char);
|
|
138
|
+
if (escaped) {
|
|
139
|
+
escaped = false;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if ("\\" === char) {
|
|
143
|
+
escaped = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if ("\"" === char) inString = false;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if ("\"" === char) {
|
|
150
|
+
inString = true;
|
|
151
|
+
output.push(char);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if ("/" === char && "/" === next) {
|
|
155
|
+
while(index < content.length && "\n" !== content[index])index++;
|
|
156
|
+
output.push("\n");
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if ("/" === char && "*" === next) {
|
|
160
|
+
index += 2;
|
|
161
|
+
while(index < content.length && !("*" === content[index] && "/" === content[index + 1]))index++;
|
|
162
|
+
index++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
output.push(char);
|
|
166
|
+
}
|
|
167
|
+
return output.join("").replace(/^\uFEFF/, "");
|
|
168
|
+
}
|
|
169
|
+
function stripTrailingCommas(content) {
|
|
170
|
+
return content.replace(/,\s*([}\]])/g, "$1");
|
|
171
|
+
}
|
|
172
|
+
function toPathMap(value) {
|
|
173
|
+
if (!value || "object" != typeof value || Array.isArray(value)) return;
|
|
174
|
+
const entries = [];
|
|
175
|
+
for (const [key, item] of Object.entries(value)){
|
|
176
|
+
if (!Array.isArray(item)) continue;
|
|
177
|
+
const list = item.filter((target)=>"string" == typeof target);
|
|
178
|
+
if (0 !== list.length) entries.push([
|
|
179
|
+
key,
|
|
180
|
+
list
|
|
181
|
+
]);
|
|
182
|
+
}
|
|
183
|
+
if (0 === entries.length) return;
|
|
184
|
+
return Object.fromEntries(entries);
|
|
185
|
+
}
|
|
186
|
+
function toObject(value) {
|
|
187
|
+
if (value && "object" == typeof value && !Array.isArray(value)) return value;
|
|
188
|
+
return {};
|
|
189
|
+
}
|
|
190
|
+
async function exists(path) {
|
|
191
|
+
try {
|
|
192
|
+
await access(path, constants.F_OK);
|
|
193
|
+
return true;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function normalizeForComparison(path) {
|
|
199
|
+
return normalize(path).replace(/\\/g, "/").replace(/\/$/, "").toLowerCase();
|
|
200
|
+
}
|
|
201
|
+
export { resolveProjectImportPath };
|
package/dist/utils/runCommand.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
-
function
|
|
2
|
+
function quotePosixArgument(arg) {
|
|
3
|
+
if (0 === arg.length) return "''";
|
|
4
|
+
return `'${arg.replaceAll("'", "'\"'\"'")}'`;
|
|
5
|
+
}
|
|
6
|
+
function quoteWindowsArgument(arg) {
|
|
7
|
+
if (0 === arg.length) return '""';
|
|
8
|
+
return `"${arg.replaceAll('"', '""')}"`;
|
|
9
|
+
}
|
|
10
|
+
function quoteWindowsCommand(command) {
|
|
11
|
+
if (/^[\w./:-]+$/.test(command)) return command;
|
|
12
|
+
return quoteWindowsArgument(command);
|
|
13
|
+
}
|
|
14
|
+
function formatShellCommand(args) {
|
|
3
15
|
const [command, ...commandArgs] = args;
|
|
4
|
-
|
|
16
|
+
const quoteArgument = "win32" === process.platform ? quoteWindowsArgument : quotePosixArgument;
|
|
17
|
+
if ("win32" === process.platform) return [
|
|
18
|
+
quoteWindowsCommand(command),
|
|
19
|
+
...commandArgs.map(quoteArgument)
|
|
20
|
+
].join(" ");
|
|
21
|
+
return args.map(quoteArgument).join(" ");
|
|
22
|
+
}
|
|
23
|
+
function spawnCommand({ args }) {
|
|
24
|
+
const command = formatShellCommand(args);
|
|
25
|
+
return spawn(command, {
|
|
5
26
|
shell: true,
|
|
6
27
|
stdio: "inherit"
|
|
7
28
|
});
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { join } from "path"
|
|
2
2
|
|
|
3
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath"
|
|
3
4
|
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact"
|
|
4
5
|
|
|
5
6
|
export async function createAction(path: string) {
|
|
@@ -7,13 +8,17 @@ export async function createAction(path: string) {
|
|
|
7
8
|
|
|
8
9
|
if (!isScriptModule(info.relativePath)) return
|
|
9
10
|
|
|
11
|
+
const actionPath = join("actions", info.relativePath)
|
|
12
|
+
const createResponseFnImportPath = await resolveProjectImportPath(actionPath, "server/createResponseFn")
|
|
13
|
+
const sharedImportPath = await resolveProjectImportPath(actionPath, `shared/${info.importPath}`)
|
|
14
|
+
|
|
10
15
|
await writeGeneratedFile({
|
|
11
|
-
path:
|
|
16
|
+
path: actionPath,
|
|
12
17
|
content: `"use server"
|
|
13
18
|
|
|
14
|
-
import { createResponseFn } from "
|
|
19
|
+
import { createResponseFn } from "${createResponseFnImportPath}"
|
|
15
20
|
|
|
16
|
-
import { ${info.name} } from "
|
|
21
|
+
import { ${info.name} } from "${sharedImportPath}"
|
|
17
22
|
|
|
18
23
|
export const ${info.name}Action = createResponseFn(${info.name})
|
|
19
24
|
`
|
package/src/utils/createRoute.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readdir } from "fs/promises"
|
|
2
2
|
import { join } from "path"
|
|
3
3
|
|
|
4
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath"
|
|
4
5
|
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact"
|
|
5
6
|
|
|
6
7
|
export async function createRoute(path?: string) {
|
|
@@ -10,10 +11,11 @@ export async function createRoute(path?: string) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
const modules = await getSharedModules("shared")
|
|
14
|
+
const routePath = join("app", "api", "action", "[...action]", "route.ts")
|
|
13
15
|
|
|
14
16
|
await writeGeneratedFile({
|
|
15
|
-
path:
|
|
16
|
-
content: getRouteFileContent(modules),
|
|
17
|
+
path: routePath,
|
|
18
|
+
content: await getRouteFileContent(modules, routePath),
|
|
17
19
|
})
|
|
18
20
|
}
|
|
19
21
|
|
|
@@ -55,13 +57,17 @@ export interface GetRouteFileContentParamsItem {
|
|
|
55
57
|
name: string
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
function getRouteFileContent(items: GetRouteFileContentParamsItem[]) {
|
|
59
|
-
const
|
|
60
|
+
async function getRouteFileContent(items: GetRouteFileContentParamsItem[], routePath: string) {
|
|
61
|
+
const createRouteFnImportPath = await resolveProjectImportPath(routePath, "server/createResponseFn")
|
|
62
|
+
const importLines = (await Promise.all(items.map(async (item) => {
|
|
63
|
+
const importPath = await resolveProjectImportPath(routePath, `shared/${item.importPath}`)
|
|
64
|
+
return `import { ${item.name} } from "${importPath}"`
|
|
65
|
+
}))).join("\n")
|
|
60
66
|
const registerLines = items.map(item => `registerRoute(${item.name})`).join("\n")
|
|
61
67
|
|
|
62
68
|
return `import { NextRequest, NextResponse } from "next/server"
|
|
63
69
|
|
|
64
|
-
import { createRouteFn, OriginalResponseFn, RouteBodyType, RouteHandler } from "
|
|
70
|
+
import { createRouteFn, OriginalResponseFn, RouteBodyType, RouteHandler } from "${createRouteFnImportPath}"
|
|
65
71
|
${importLines ? `\n${importLines}\n` : ""}
|
|
66
72
|
const routeMap = new Map<string, RouteHandler>()
|
|
67
73
|
|
package/src/utils/hook.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { checkbox, select } from "@inquirer/prompts"
|
|
|
6
6
|
import { Command } from "commander"
|
|
7
7
|
|
|
8
8
|
import { readSdNextSetting, SdNextSetting } from "./readSdNextSetting"
|
|
9
|
+
import { resolveProjectImportPath } from "./resolveProjectImportPath"
|
|
9
10
|
import { isScriptModule, normalizePathSeparator, writeGeneratedFile } from "./sharedArtifact"
|
|
10
11
|
import { writeSdNextSetting } from "./writeSdNextSetting"
|
|
11
12
|
|
|
@@ -33,7 +34,7 @@ async function getHookTypeFromContent(path: string, content: string): Promise<Ho
|
|
|
33
34
|
const type = setting.hook?.[path]
|
|
34
35
|
if (type !== undefined && type !== "skip") return type
|
|
35
36
|
if (content.includes("useMutation")) return "mutation"
|
|
36
|
-
if (content.includes("createUse") &&
|
|
37
|
+
if (content.includes("createUse") && /from\s+["'][^"']*\/presets\//.test(content)) return "mutation"
|
|
37
38
|
if (content.includes("ClientOptional")) return "get"
|
|
38
39
|
if (content.includes("useQuery")) return "query"
|
|
39
40
|
return undefined
|
|
@@ -81,12 +82,16 @@ export async function createHook(path: string, hookMap: Record<string, HookData>
|
|
|
81
82
|
const mutationPresetPath = join("presets", dir, mutationPresetName)
|
|
82
83
|
const mutationPresetImportPath = normalizePathSeparator(join(dir, `createUse${upName}`))
|
|
83
84
|
const clientInputType = `${upName}ClientInput`
|
|
85
|
+
const actionPath = normalizePathSeparator(join("actions", actionImportPath))
|
|
86
|
+
const hookActionImportPath = await resolveProjectImportPath(hookPath, actionPath)
|
|
87
|
+
const hookPresetImportPath = await resolveProjectImportPath(hookPath, normalizePathSeparator(join("presets", mutationPresetImportPath)))
|
|
88
|
+
const mutationPresetSharedImportPath = await resolveProjectImportPath(mutationPresetPath, normalizePathSeparator(join("shared", actionImportPath)))
|
|
84
89
|
|
|
85
90
|
const mutationHook = `import { createRequestFn } from "deepsea-tools"
|
|
86
91
|
|
|
87
|
-
import { ${name}Action } from "
|
|
92
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
88
93
|
|
|
89
|
-
import { createUse${upName} } from "
|
|
94
|
+
import { createUse${upName} } from "${hookPresetImportPath}"
|
|
90
95
|
|
|
91
96
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
92
97
|
|
|
@@ -97,7 +102,7 @@ export const use${upName} = createUse${upName}(${name}Client)
|
|
|
97
102
|
|
|
98
103
|
import { withUseMutationDefaults } from "soda-tanstack-query"
|
|
99
104
|
|
|
100
|
-
import { ${name} } from "
|
|
105
|
+
import { ${name} } from "${mutationPresetSharedImportPath}"
|
|
101
106
|
|
|
102
107
|
export const createUse${upName} = withUseMutationDefaults<typeof ${name}>(() => {
|
|
103
108
|
const key = useId()
|
|
@@ -132,7 +137,7 @@ export const createUse${upName} = withUseMutationDefaults<typeof ${name}>(() =>
|
|
|
132
137
|
const getHook = `import { createRequestFn, isNonNullable } from "deepsea-tools"
|
|
133
138
|
import { createUseQuery } from "soda-tanstack-query"
|
|
134
139
|
|
|
135
|
-
import { ${name}Action } from "
|
|
140
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
136
141
|
|
|
137
142
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
138
143
|
|
|
@@ -151,7 +156,7 @@ export const use${upName} = createUseQuery({
|
|
|
151
156
|
const queryHook = `import { createRequestFn } from "deepsea-tools"
|
|
152
157
|
import { createUseQuery } from "soda-tanstack-query"
|
|
153
158
|
|
|
154
|
-
import { ${name}Action } from "
|
|
159
|
+
import { ${name}Action } from "${hookActionImportPath}"
|
|
155
160
|
|
|
156
161
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
157
162
|
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { constants } from "node:fs"
|
|
2
|
+
import { access, readFile } from "node:fs/promises"
|
|
3
|
+
import { createRequire } from "node:module"
|
|
4
|
+
import { dirname, isAbsolute, join, normalize, relative, resolve } from "node:path"
|
|
5
|
+
import { cwd } from "node:process"
|
|
6
|
+
|
|
7
|
+
import { normalizePathSeparator } from "./sharedArtifact"
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url)
|
|
10
|
+
|
|
11
|
+
const projectRoot = cwd()
|
|
12
|
+
|
|
13
|
+
let rootAliasPromise: Promise<string | undefined> | undefined
|
|
14
|
+
|
|
15
|
+
export async function resolveProjectImportPath(fromPath: string, targetPath: string) {
|
|
16
|
+
const normalizedTargetPath = normalizePathSeparator(targetPath).replace(/^\.?\//, "")
|
|
17
|
+
const rootAlias = await getProjectRootAlias()
|
|
18
|
+
|
|
19
|
+
if (rootAlias) return `${rootAlias}/${normalizedTargetPath}`
|
|
20
|
+
|
|
21
|
+
return getRelativeImportPath(fromPath, normalizedTargetPath)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getRelativeImportPath(fromPath: string, targetPath: string) {
|
|
25
|
+
const fromDir = dirname(fromPath)
|
|
26
|
+
let importPath = normalizePathSeparator(relative(fromDir, targetPath))
|
|
27
|
+
|
|
28
|
+
if (!importPath.startsWith(".")) importPath = `./${importPath}`
|
|
29
|
+
|
|
30
|
+
return importPath
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getProjectRootAlias() {
|
|
34
|
+
rootAliasPromise ??= resolveProjectRootAlias()
|
|
35
|
+
return rootAliasPromise
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function resolveProjectRootAlias() {
|
|
39
|
+
const configPath = await findProjectConfigPath(projectRoot)
|
|
40
|
+
if (!configPath) return undefined
|
|
41
|
+
|
|
42
|
+
return readRootAliasFromConfig(configPath, new Set())
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function findProjectConfigPath(path: string) {
|
|
46
|
+
const candidates = [join(path, "tsconfig.json"), join(path, "jsconfig.json")]
|
|
47
|
+
|
|
48
|
+
for (const candidate of candidates) {
|
|
49
|
+
if (await exists(candidate)) return candidate
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return undefined
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function readRootAliasFromConfig(configPath: string, seen: Set<string>): Promise<string | undefined> {
|
|
56
|
+
const normalizedPath = normalize(configPath)
|
|
57
|
+
if (seen.has(normalizedPath)) return undefined
|
|
58
|
+
seen.add(normalizedPath)
|
|
59
|
+
|
|
60
|
+
const config = await readJsonConfig(configPath)
|
|
61
|
+
const compilerOptions = toObject(config.compilerOptions)
|
|
62
|
+
const configDir = dirname(configPath)
|
|
63
|
+
const baseDir = getBaseDir(configDir, compilerOptions.baseUrl)
|
|
64
|
+
const paths = toPathMap(compilerOptions.paths)
|
|
65
|
+
|
|
66
|
+
if (paths) {
|
|
67
|
+
const rootAlias = findRootAlias(paths, baseDir, projectRoot)
|
|
68
|
+
if (rootAlias) return rootAlias
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const extendsValue = typeof config.extends === "string" ? config.extends : undefined
|
|
72
|
+
if (!extendsValue) return undefined
|
|
73
|
+
|
|
74
|
+
const extendsPath = await resolveExtendsPath(extendsValue, configDir)
|
|
75
|
+
if (!extendsPath) return undefined
|
|
76
|
+
|
|
77
|
+
return readRootAliasFromConfig(extendsPath, seen)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getBaseDir(configDir: string, baseUrl: unknown) {
|
|
81
|
+
if (typeof baseUrl !== "string") return configDir
|
|
82
|
+
return resolve(configDir, baseUrl)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function findRootAlias(paths: Record<string, string[]>, baseDir: string, rootDir: string) {
|
|
86
|
+
for (const [key, targets] of Object.entries(paths)) {
|
|
87
|
+
const alias = getAliasName(key)
|
|
88
|
+
if (!alias) continue
|
|
89
|
+
|
|
90
|
+
for (const target of targets) {
|
|
91
|
+
if (!isRootTarget(target, baseDir, rootDir)) continue
|
|
92
|
+
return alias
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getAliasName(pattern: string) {
|
|
100
|
+
const wildcard = splitWildcard(pattern)
|
|
101
|
+
if (!wildcard || wildcard.suffix.length > 0) return undefined
|
|
102
|
+
|
|
103
|
+
const aliasPrefix = wildcard.prefix.replace(/\/$/, "")
|
|
104
|
+
if (!aliasPrefix) return undefined
|
|
105
|
+
|
|
106
|
+
return aliasPrefix
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isRootTarget(pattern: string, baseDir: string, rootDir: string) {
|
|
110
|
+
const wildcard = splitWildcard(pattern)
|
|
111
|
+
if (!wildcard || wildcard.suffix.length > 0) return false
|
|
112
|
+
|
|
113
|
+
const targetPath = resolve(baseDir, wildcard.prefix || ".")
|
|
114
|
+
|
|
115
|
+
return normalizeForComparison(targetPath) === normalizeForComparison(rootDir)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function splitWildcard(value: string) {
|
|
119
|
+
const index = value.indexOf("*")
|
|
120
|
+
if (index < 0) return undefined
|
|
121
|
+
if (value.indexOf("*", index + 1) >= 0) return undefined
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
prefix: value.slice(0, index),
|
|
125
|
+
suffix: value.slice(index + 1),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function resolveExtendsPath(extendsValue: string, baseDir: string) {
|
|
130
|
+
if (isAbsolute(extendsValue) || extendsValue.startsWith(".")) {
|
|
131
|
+
const relativeCandidates = getRelativeCandidates(resolve(baseDir, extendsValue))
|
|
132
|
+
for (const candidate of relativeCandidates) {
|
|
133
|
+
if (await exists(candidate)) return candidate
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const moduleCandidates = getModuleCandidates(extendsValue)
|
|
140
|
+
|
|
141
|
+
for (const candidate of moduleCandidates) {
|
|
142
|
+
try {
|
|
143
|
+
return require.resolve(candidate, { paths: [baseDir] })
|
|
144
|
+
} catch (error) {}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getRelativeCandidates(path: string) {
|
|
151
|
+
if (path.endsWith(".json")) return [path]
|
|
152
|
+
return [path, `${path}.json`]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getModuleCandidates(path: string) {
|
|
156
|
+
if (path.endsWith(".json")) return [path]
|
|
157
|
+
return [path, `${path}.json`]
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function readJsonConfig(path: string): Promise<JsonObject> {
|
|
161
|
+
const content = await readFile(path, "utf-8")
|
|
162
|
+
const parsed = JSON.parse(stripTrailingCommas(stripComments(content)))
|
|
163
|
+
return toObject(parsed)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function stripComments(content: string) {
|
|
167
|
+
const output: string[] = []
|
|
168
|
+
let inString = false
|
|
169
|
+
let escaped = false
|
|
170
|
+
|
|
171
|
+
for (let index = 0; index < content.length; index++) {
|
|
172
|
+
const char = content[index]
|
|
173
|
+
const next = content[index + 1]
|
|
174
|
+
|
|
175
|
+
if (inString) {
|
|
176
|
+
output.push(char)
|
|
177
|
+
if (escaped) {
|
|
178
|
+
escaped = false
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (char === "\\") {
|
|
183
|
+
escaped = true
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (char === "\"") inString = false
|
|
188
|
+
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (char === "\"") {
|
|
193
|
+
inString = true
|
|
194
|
+
output.push(char)
|
|
195
|
+
continue
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (char === "/" && next === "/") {
|
|
199
|
+
while (index < content.length && content[index] !== "\n") index++
|
|
200
|
+
output.push("\n")
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (char === "/" && next === "*") {
|
|
205
|
+
index += 2
|
|
206
|
+
while (index < content.length && !(content[index] === "*" && content[index + 1] === "/")) index++
|
|
207
|
+
index++
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
output.push(char)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return output.join("").replace(/^\uFEFF/, "")
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function stripTrailingCommas(content: string) {
|
|
218
|
+
return content.replace(/,\s*([}\]])/g, "$1")
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function toPathMap(value: unknown): Record<string, string[]> | undefined {
|
|
222
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined
|
|
223
|
+
|
|
224
|
+
const entries: [string, string[]][] = []
|
|
225
|
+
|
|
226
|
+
for (const [key, item] of Object.entries(value)) {
|
|
227
|
+
if (!Array.isArray(item)) continue
|
|
228
|
+
const list = item.filter((target): target is string => typeof target === "string")
|
|
229
|
+
if (list.length === 0) continue
|
|
230
|
+
entries.push([key, list])
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (entries.length === 0) return undefined
|
|
234
|
+
|
|
235
|
+
return Object.fromEntries(entries)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function toObject(value: unknown): JsonObject {
|
|
239
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value as JsonObject
|
|
240
|
+
return {}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function exists(path: string) {
|
|
244
|
+
try {
|
|
245
|
+
await access(path, constants.F_OK)
|
|
246
|
+
return true
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return false
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function normalizeForComparison(path: string) {
|
|
253
|
+
return normalize(path).replace(/\\/g, "/").replace(/\/$/, "").toLowerCase()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
type JsonObject = Record<string, unknown>
|
package/src/utils/runCommand.ts
CHANGED
|
@@ -4,10 +4,39 @@ export interface SpawnCommandParams {
|
|
|
4
4
|
args: string[]
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
function quotePosixArgument(arg: string) {
|
|
8
|
+
if (arg.length === 0) return "''"
|
|
9
|
+
|
|
10
|
+
return `'${arg.replaceAll("'", `'"'"'`)}'`
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function quoteWindowsArgument(arg: string) {
|
|
14
|
+
if (arg.length === 0) return '""'
|
|
15
|
+
|
|
16
|
+
return `"${arg.replaceAll('"', '""')}"`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function quoteWindowsCommand(command: string) {
|
|
20
|
+
if (/^[\w./:-]+$/.test(command)) return command
|
|
21
|
+
|
|
22
|
+
return quoteWindowsArgument(command)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatShellCommand(args: string[]) {
|
|
8
26
|
const [command, ...commandArgs] = args
|
|
27
|
+
const quoteArgument = process.platform === "win32" ? quoteWindowsArgument : quotePosixArgument
|
|
28
|
+
|
|
29
|
+
if (process.platform === "win32") {
|
|
30
|
+
return [quoteWindowsCommand(command), ...commandArgs.map(quoteArgument)].join(" ")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return args.map(quoteArgument).join(" ")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function spawnCommand({ args }: SpawnCommandParams) {
|
|
37
|
+
const command = formatShellCommand(args)
|
|
9
38
|
|
|
10
|
-
return spawn(command,
|
|
39
|
+
return spawn(command, {
|
|
11
40
|
shell: true,
|
|
12
41
|
stdio: "inherit",
|
|
13
42
|
})
|