sdnext 0.0.23 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -1
- package/dist/index.js +145 -14
- package/dist/utils/build.js +7 -8
- package/dist/utils/createAction.js +10 -17
- package/dist/utils/createRoute.d.ts +5 -0
- package/dist/utils/createRoute.js +31 -0
- package/dist/utils/dev.js +24 -9
- package/dist/utils/excludeGeneratedFiles.d.ts +1 -0
- package/dist/utils/{excludeActions.js → excludeGeneratedFiles.js} +4 -3
- package/dist/utils/hook.d.ts +5 -1
- package/dist/utils/hook.js +42 -35
- package/dist/utils/readSdNextSetting.d.ts +3 -0
- package/dist/utils/readSdNextSetting.js +8 -3
- package/dist/utils/runCommand.d.ts +7 -0
- package/dist/utils/runCommand.js +25 -0
- package/dist/utils/sharedArtifact.d.ts +23 -0
- package/dist/utils/sharedArtifact.js +67 -0
- package/dist/utils/syncSharedArtifacts.d.ts +3 -0
- package/dist/utils/syncSharedArtifacts.js +34 -0
- package/dist/utils/watch.js +5 -21
- package/dist/utils/writeVsCodeSetting.d.ts +7 -1
- package/dist/utils/writeVsCodeSetting.js +4 -0
- package/package.json +2 -2
- package/src/index.ts +5 -4
- package/src/utils/build.ts +6 -9
- package/src/utils/createAction.ts +12 -19
- package/src/utils/createRoute.ts +44 -0
- package/src/utils/dev.ts +26 -9
- package/src/utils/{excludeActions.ts → excludeGeneratedFiles.ts} +2 -1
- package/src/utils/hook.ts +49 -44
- package/src/utils/readSdNextSetting.ts +14 -4
- package/src/utils/runCommand.ts +35 -0
- package/src/utils/sharedArtifact.ts +90 -0
- package/src/utils/syncSharedArtifacts.ts +44 -0
- package/src/utils/watch.ts +5 -16
- package/src/utils/writeVsCodeSetting.ts +20 -4
- package/dist/utils/excludeActions.d.ts +0 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ChildProcess } from "child_process";
|
|
2
|
+
export interface SpawnCommandParams {
|
|
3
|
+
args: string[];
|
|
4
|
+
}
|
|
5
|
+
export declare function spawnCommand({ args }: SpawnCommandParams): ChildProcess;
|
|
6
|
+
export declare function runCommand({ args }: SpawnCommandParams): Promise<number>;
|
|
7
|
+
export declare function waitForChild(child: ChildProcess): Promise<number>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
function spawnCommand({ args }) {
|
|
3
|
+
const [command, ...commandArgs] = args;
|
|
4
|
+
return spawn(command, commandArgs, {
|
|
5
|
+
shell: true,
|
|
6
|
+
stdio: "inherit"
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
async function runCommand({ args }) {
|
|
10
|
+
if (0 === args.length) return 0;
|
|
11
|
+
const child = spawnCommand({
|
|
12
|
+
args
|
|
13
|
+
});
|
|
14
|
+
return waitForChild(child);
|
|
15
|
+
}
|
|
16
|
+
async function waitForChild(child) {
|
|
17
|
+
return await new Promise((resolve, reject)=>{
|
|
18
|
+
child.once("error", reject);
|
|
19
|
+
child.once("close", (code, signal)=>{
|
|
20
|
+
if ("number" == typeof code) return void resolve(code);
|
|
21
|
+
resolve(signal ? 1 : 0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export { runCommand, spawnCommand, waitForChild };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SharedModuleInfo {
|
|
2
|
+
dir: string;
|
|
3
|
+
ext: string;
|
|
4
|
+
importPath: string;
|
|
5
|
+
name: string;
|
|
6
|
+
relativePath: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GeneratedFileParams {
|
|
9
|
+
content: string;
|
|
10
|
+
path: string;
|
|
11
|
+
}
|
|
12
|
+
export interface RemoveGeneratedFileParams {
|
|
13
|
+
path: string;
|
|
14
|
+
stopPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const scriptModuleExtensions: string[];
|
|
17
|
+
export declare function normalizePathSeparator(path: string): string;
|
|
18
|
+
export declare function isScriptModule(path: string): boolean;
|
|
19
|
+
export declare function normalizeSharedPath(path: string): string;
|
|
20
|
+
export declare function getSharedModuleInfo(path: string): SharedModuleInfo;
|
|
21
|
+
export declare function toKebabCase(name: string): string;
|
|
22
|
+
export declare function writeGeneratedFile({ content, path }: GeneratedFileParams): Promise<void>;
|
|
23
|
+
export declare function removeGeneratedFile({ path, stopPath }: RemoveGeneratedFileParams): Promise<void>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
|
|
2
|
+
import { dirname, join, parse, relative } from "path";
|
|
3
|
+
const scriptModuleExtensions = [
|
|
4
|
+
".ts",
|
|
5
|
+
".tsx",
|
|
6
|
+
".js",
|
|
7
|
+
".jsx"
|
|
8
|
+
];
|
|
9
|
+
function normalizePathSeparator(path) {
|
|
10
|
+
return path.replace(/\\/g, "/");
|
|
11
|
+
}
|
|
12
|
+
function isScriptModule(path) {
|
|
13
|
+
const { ext } = parse(path);
|
|
14
|
+
return scriptModuleExtensions.includes(ext);
|
|
15
|
+
}
|
|
16
|
+
function normalizeSharedPath(path) {
|
|
17
|
+
return normalizePathSeparator(relative("shared", path));
|
|
18
|
+
}
|
|
19
|
+
function getSharedModuleInfo(path) {
|
|
20
|
+
const relativePath = normalizeSharedPath(path);
|
|
21
|
+
const { dir, name, ext } = parse(relativePath);
|
|
22
|
+
return {
|
|
23
|
+
dir,
|
|
24
|
+
ext,
|
|
25
|
+
importPath: normalizePathSeparator(join(dir, name)),
|
|
26
|
+
name,
|
|
27
|
+
relativePath
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function toKebabCase(name) {
|
|
31
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").toLowerCase();
|
|
32
|
+
}
|
|
33
|
+
async function writeGeneratedFile({ content, path }) {
|
|
34
|
+
try {
|
|
35
|
+
const current = await readFile(path, "utf-8");
|
|
36
|
+
if (current === content) return;
|
|
37
|
+
} catch (error) {}
|
|
38
|
+
await mkdir(dirname(path), {
|
|
39
|
+
recursive: true
|
|
40
|
+
});
|
|
41
|
+
await writeFile(path, content);
|
|
42
|
+
}
|
|
43
|
+
async function removeGeneratedFile({ path, stopPath }) {
|
|
44
|
+
await rm(path, {
|
|
45
|
+
force: true,
|
|
46
|
+
recursive: true
|
|
47
|
+
});
|
|
48
|
+
if (!stopPath) return;
|
|
49
|
+
await removeEmptyDirectories(dirname(path), stopPath);
|
|
50
|
+
}
|
|
51
|
+
async function removeEmptyDirectories(path, stopPath) {
|
|
52
|
+
let currentPath = path;
|
|
53
|
+
while(currentPath !== stopPath && "." !== currentPath && currentPath !== dirname(currentPath)){
|
|
54
|
+
try {
|
|
55
|
+
const content = await readdir(currentPath);
|
|
56
|
+
if (content.length > 0) return;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
await rm(currentPath, {
|
|
61
|
+
force: true,
|
|
62
|
+
recursive: true
|
|
63
|
+
});
|
|
64
|
+
currentPath = dirname(currentPath);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
export { getSharedModuleInfo, isScriptModule, normalizePathSeparator, normalizeSharedPath, removeGeneratedFile, scriptModuleExtensions, toKebabCase, writeGeneratedFile };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { createAction } from "./createAction.js";
|
|
3
|
+
import { createRoute } from "./createRoute.js";
|
|
4
|
+
import { getSharedModuleInfo, isScriptModule, normalizeSharedPath, removeGeneratedFile, toKebabCase } from "./sharedArtifact.js";
|
|
5
|
+
async function syncSharedArtifacts(path) {
|
|
6
|
+
const info = getSharedModuleInfo(path);
|
|
7
|
+
if (!isScriptModule(info.relativePath)) return;
|
|
8
|
+
await createAction(path);
|
|
9
|
+
await createRoute(path);
|
|
10
|
+
}
|
|
11
|
+
async function removeSharedArtifacts(path) {
|
|
12
|
+
const info = getSharedModuleInfo(path);
|
|
13
|
+
if (!isScriptModule(info.relativePath)) return;
|
|
14
|
+
await removeGeneratedFile({
|
|
15
|
+
path: join("actions", info.relativePath),
|
|
16
|
+
stopPath: "actions"
|
|
17
|
+
});
|
|
18
|
+
await removeGeneratedFile({
|
|
19
|
+
path: join("app", "api", "actions", info.dir, toKebabCase(info.name)),
|
|
20
|
+
stopPath: join("app", "api", "actions")
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function removeSharedArtifactDirectory(path) {
|
|
24
|
+
const relativePath = normalizeSharedPath(path);
|
|
25
|
+
await removeGeneratedFile({
|
|
26
|
+
path: join("actions", relativePath),
|
|
27
|
+
stopPath: "actions"
|
|
28
|
+
});
|
|
29
|
+
await removeGeneratedFile({
|
|
30
|
+
path: join("app", "api", "actions", relativePath),
|
|
31
|
+
stopPath: join("app", "api", "actions")
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export { removeSharedArtifactDirectory, removeSharedArtifacts, syncSharedArtifacts };
|
package/dist/utils/watch.js
CHANGED
|
@@ -1,26 +1,10 @@
|
|
|
1
|
-
import { rm } from "fs/promises";
|
|
2
|
-
import { join, relative } from "path";
|
|
3
1
|
import { watch } from "chokidar";
|
|
4
|
-
import {
|
|
2
|
+
import { removeSharedArtifactDirectory, removeSharedArtifacts, syncSharedArtifacts } from "./syncSharedArtifacts.js";
|
|
5
3
|
const watcher = watch("shared", {
|
|
6
4
|
awaitWriteFinish: true,
|
|
7
5
|
persistent: true
|
|
8
6
|
});
|
|
9
|
-
watcher.on("add",
|
|
10
|
-
watcher.on("change",
|
|
11
|
-
watcher.on("unlink",
|
|
12
|
-
|
|
13
|
-
const actionPath = join("actions", path);
|
|
14
|
-
await rm(actionPath, {
|
|
15
|
-
recursive: true,
|
|
16
|
-
force: true
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
watcher.on("unlinkDir", async (path)=>{
|
|
20
|
-
path = relative("shared", path).replace(/\\/g, "/");
|
|
21
|
-
const actionPath = join("actions", path);
|
|
22
|
-
await rm(actionPath, {
|
|
23
|
-
recursive: true,
|
|
24
|
-
force: true
|
|
25
|
-
});
|
|
26
|
-
});
|
|
7
|
+
watcher.on("add", syncSharedArtifacts);
|
|
8
|
+
watcher.on("change", syncSharedArtifacts);
|
|
9
|
+
watcher.on("unlink", removeSharedArtifacts);
|
|
10
|
+
watcher.on("unlinkDir", removeSharedArtifactDirectory);
|
|
@@ -1 +1,7 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface VsCodeSetting {
|
|
2
|
+
[key: string]: unknown;
|
|
3
|
+
}
|
|
4
|
+
export declare function writeVsCodeSetting(config: VsCodeSetting): Promise<VsCodeSetting>;
|
|
5
|
+
export interface NodeError {
|
|
6
|
+
code?: string;
|
|
7
|
+
}
|
|
@@ -9,10 +9,14 @@ async function writeVsCodeSetting(config) {
|
|
|
9
9
|
const json = await readFile(".vscode/settings.json", "utf-8");
|
|
10
10
|
data = JSON.parse(json);
|
|
11
11
|
} catch (error) {
|
|
12
|
+
if (!isNodeError(error) || "ENOENT" !== error.code) throw new Error("Failed to read .vscode/settings.json. Please ensure it is a valid JSON file.");
|
|
12
13
|
data = {};
|
|
13
14
|
}
|
|
14
15
|
data = merge(data, config);
|
|
15
16
|
await writeFile(".vscode/settings.json", JSON.stringify(data, null, 4));
|
|
16
17
|
return data;
|
|
17
18
|
}
|
|
19
|
+
function isNodeError(error) {
|
|
20
|
+
return "object" == typeof error && null !== error;
|
|
21
|
+
}
|
|
18
22
|
export { writeVsCodeSetting };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdnext",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@inquirer/prompts": "^8.1.0",
|
|
42
42
|
"chokidar": "^5.0.0",
|
|
43
43
|
"commander": "^14.0.2",
|
|
44
|
-
"deepsea-tools": "5.47.
|
|
44
|
+
"deepsea-tools": "5.47.8"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@types/node": "^24.10.14",
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { readFile } from "fs/promises"
|
|
4
4
|
import { join } from "path"
|
|
5
|
+
import { fileURLToPath } from "url"
|
|
5
6
|
|
|
6
7
|
import { Command } from "commander"
|
|
7
8
|
|
|
@@ -11,11 +12,11 @@ import { hook } from "./utils/hook"
|
|
|
11
12
|
|
|
12
13
|
const program = new Command()
|
|
13
14
|
|
|
14
|
-
const path =
|
|
15
|
+
const path = fileURLToPath(new URL("../", import.meta.url))
|
|
15
16
|
|
|
16
|
-
const
|
|
17
|
+
const packageJson = JSON.parse(await readFile(join(path, "package.json"), "utf-8"))
|
|
17
18
|
|
|
18
|
-
program.name("soda next").version(
|
|
19
|
+
program.name("soda next").version(packageJson.version)
|
|
19
20
|
|
|
20
21
|
program.command("build").allowUnknownOption(true).allowExcessArguments(true).action(build)
|
|
21
22
|
|
package/src/utils/build.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { spawn } from "child_process"
|
|
2
1
|
import { readdir, stat } from "fs/promises"
|
|
3
2
|
import { join } from "path"
|
|
4
3
|
|
|
5
4
|
import { Command } from "commander"
|
|
6
5
|
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { excludeGeneratedFiles } from "./excludeGeneratedFiles"
|
|
7
|
+
import { runCommand } from "./runCommand"
|
|
8
|
+
import { syncSharedArtifacts } from "./syncSharedArtifacts"
|
|
9
9
|
|
|
10
10
|
export async function buildFolder(dir: string) {
|
|
11
11
|
const content = await readdir(dir)
|
|
@@ -15,19 +15,16 @@ export async function buildFolder(dir: string) {
|
|
|
15
15
|
const stats = await stat(path)
|
|
16
16
|
|
|
17
17
|
if (stats.isDirectory()) await buildFolder(path)
|
|
18
|
-
else await
|
|
18
|
+
else await syncSharedArtifacts(path)
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export async function build(options: Record<string, string>, { args }: Command) {
|
|
23
|
-
await
|
|
23
|
+
await excludeGeneratedFiles()
|
|
24
24
|
|
|
25
25
|
await buildFolder("shared")
|
|
26
26
|
|
|
27
27
|
if (args.length === 0) return
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
stdio: "inherit",
|
|
31
|
-
shell: true,
|
|
32
|
-
})
|
|
29
|
+
process.exitCode = await runCommand({ args })
|
|
33
30
|
}
|
|
@@ -1,28 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { join } from "path"
|
|
2
|
+
|
|
3
|
+
import { getSharedModuleInfo, isScriptModule, writeGeneratedFile } from "./sharedArtifact"
|
|
3
4
|
|
|
4
5
|
export async function createAction(path: string) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (
|
|
6
|
+
const info = getSharedModuleInfo(path)
|
|
7
|
+
|
|
8
|
+
if (!isScriptModule(info.relativePath)) return
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
await writeGeneratedFile({
|
|
11
|
+
path: join("actions", info.relativePath),
|
|
12
|
+
content: `"use server"
|
|
10
13
|
|
|
11
14
|
import { createResponseFn } from "@/server/createResponseFn"
|
|
12
15
|
|
|
13
|
-
import { ${name} } from "@/shared/${
|
|
16
|
+
import { ${info.name} } from "@/shared/${info.importPath}"
|
|
14
17
|
|
|
15
|
-
export const ${name}Action = createResponseFn(${name})
|
|
18
|
+
export const ${info.name}Action = createResponseFn(${info.name})
|
|
16
19
|
`
|
|
17
|
-
|
|
18
|
-
const actionPath = join("actions", path)
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const current = await readFile(actionPath, "utf-8")
|
|
22
|
-
if (current === content) return
|
|
23
|
-
} catch (error) {}
|
|
24
|
-
|
|
25
|
-
await mkdir(join("actions", dir), { recursive: true })
|
|
26
|
-
|
|
27
|
-
await writeFile(actionPath, content)
|
|
20
|
+
})
|
|
28
21
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFile } from "fs/promises"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
|
|
4
|
+
import { getSharedModuleInfo, isScriptModule, removeGeneratedFile, toKebabCase, writeGeneratedFile } from "./sharedArtifact"
|
|
5
|
+
|
|
6
|
+
export async function createRoute(path: string) {
|
|
7
|
+
const info = getSharedModuleInfo(path)
|
|
8
|
+
|
|
9
|
+
if (!isScriptModule(info.relativePath)) return
|
|
10
|
+
|
|
11
|
+
const routeDirPath = join("app", "api", "actions", info.dir, toKebabCase(info.name))
|
|
12
|
+
const routePath = join(routeDirPath, "route.ts")
|
|
13
|
+
|
|
14
|
+
const content = await readFile(join("shared", info.relativePath), "utf-8")
|
|
15
|
+
|
|
16
|
+
if (!isRouteEnabled({ content, name: info.name })) {
|
|
17
|
+
await removeGeneratedFile({
|
|
18
|
+
path: routeDirPath,
|
|
19
|
+
stopPath: join("app", "api", "actions"),
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
await writeGeneratedFile({
|
|
26
|
+
path: routePath,
|
|
27
|
+
content: `import { createRoute } from "@/server/createResponseFn"
|
|
28
|
+
|
|
29
|
+
import { ${info.name} } from "@/shared/${info.importPath}"
|
|
30
|
+
|
|
31
|
+
export const { POST } = createRoute(${info.name})
|
|
32
|
+
`,
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface IsRouteEnabledParams {
|
|
37
|
+
content: string
|
|
38
|
+
name: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isRouteEnabled({ content, name }: IsRouteEnabledParams) {
|
|
42
|
+
const routeRegExp = new RegExp(`\\b${name}\\.route\\s*=\\s*(true\\b|\\{)`)
|
|
43
|
+
return routeRegExp.test(content)
|
|
44
|
+
}
|
package/src/utils/dev.ts
CHANGED
|
@@ -1,27 +1,44 @@
|
|
|
1
1
|
import { spawn } from "child_process"
|
|
2
|
+
import { fileURLToPath } from "url"
|
|
2
3
|
|
|
3
4
|
import { Command } from "commander"
|
|
4
5
|
|
|
5
6
|
import { buildFolder } from "./build"
|
|
6
|
-
import {
|
|
7
|
+
import { excludeGeneratedFiles } from "./excludeGeneratedFiles"
|
|
8
|
+
import { spawnCommand } from "./runCommand"
|
|
7
9
|
|
|
8
10
|
export async function dev(options: Record<string, string>, { args }: Command) {
|
|
9
|
-
await
|
|
11
|
+
await excludeGeneratedFiles()
|
|
10
12
|
|
|
11
13
|
await buildFolder("shared")
|
|
12
14
|
|
|
13
15
|
if (args.length === 0) return
|
|
14
16
|
|
|
15
|
-
const watchPath =
|
|
17
|
+
const watchPath = fileURLToPath(new URL("./watch.js", import.meta.url))
|
|
16
18
|
|
|
17
|
-
const child = spawn(process.execPath, [watchPath]
|
|
18
|
-
|
|
19
|
-
const child2 = spawn(args.join(" "), {
|
|
19
|
+
const child = spawn(process.execPath, [watchPath], {
|
|
20
20
|
stdio: "inherit",
|
|
21
|
-
shell: true,
|
|
22
21
|
})
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
const child2 = spawnCommand({
|
|
24
|
+
args,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
process.exitCode = await new Promise<number>((resolve, reject) => {
|
|
28
|
+
let settled = false
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
function onClose(code: number) {
|
|
31
|
+
if (settled) return
|
|
32
|
+
|
|
33
|
+
settled = true
|
|
34
|
+
child.kill()
|
|
35
|
+
child2.kill()
|
|
36
|
+
resolve(code)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
child.once("error", reject)
|
|
40
|
+
child2.once("error", reject)
|
|
41
|
+
child.once("close", (code, signal) => onClose(typeof code === "number" ? code : signal ? 1 : 0))
|
|
42
|
+
child2.once("close", (code, signal) => onClose(typeof code === "number" ? code : signal ? 1 : 0))
|
|
43
|
+
})
|
|
27
44
|
}
|
package/src/utils/hook.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readdir, readFile, stat } from "fs/promises"
|
|
2
2
|
import { join, parse, relative } from "path"
|
|
3
3
|
import { cwd } from "process"
|
|
4
4
|
|
|
@@ -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 { isScriptModule, normalizePathSeparator, writeGeneratedFile } from "./sharedArtifact"
|
|
9
10
|
import { writeSdNextSetting } from "./writeSdNextSetting"
|
|
10
11
|
|
|
11
12
|
export type HookType = "get" | "query" | "mutation"
|
|
@@ -32,37 +33,41 @@ async function getHookTypeFromContent(path: string, content: string): Promise<Ho
|
|
|
32
33
|
const type = setting.hook?.[path]
|
|
33
34
|
if (type !== undefined && type !== "skip") return type
|
|
34
35
|
if (content.includes("useMutation")) return "mutation"
|
|
35
|
-
if (content.includes("
|
|
36
|
+
if (content.includes("ClientOptional")) return "get"
|
|
36
37
|
if (content.includes("useQuery")) return "query"
|
|
37
38
|
return undefined
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface HookData extends HookContentMap {
|
|
42
|
+
hookPath: string
|
|
41
43
|
overwrite: boolean
|
|
42
44
|
type: HookType
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
export async function createHook(path: string, hookMap: Record<string, HookData>) {
|
|
46
48
|
path = relative("actions", path).replace(/\\/g, "/")
|
|
47
|
-
const { dir, name,
|
|
48
|
-
if (
|
|
49
|
-
const serverContent = await readFile(join("shared", path), "utf-8")
|
|
50
|
-
const match = serverContent.match(new RegExp(`export async function ${name}\\(.+?: (.+?)Params\\)`, "s"))
|
|
51
|
-
const hasSchema = !!match
|
|
49
|
+
const { dir, name, base } = parse(path)
|
|
50
|
+
if (!isScriptModule(path)) return
|
|
52
51
|
const upName = name.replace(/^./, char => char.toUpperCase())
|
|
53
52
|
const key = name.replace(/[A-Z]/g, char => `-${char.toLowerCase()}`)
|
|
53
|
+
const actionImportPath = normalizePathSeparator(join(dir, name))
|
|
54
|
+
const hookName = base.replace(/^./, char => `use${char.toUpperCase()}`)
|
|
55
|
+
const hookPath = join("hooks", dir, hookName)
|
|
56
|
+
const clientInputType = `${upName}ClientInput`
|
|
54
57
|
|
|
55
58
|
const mutationHook = `import { useId } from "react"
|
|
56
59
|
|
|
57
60
|
import { useMutation, UseMutationOptions } from "@tanstack/react-query"
|
|
58
61
|
import { createRequestFn } from "deepsea-tools"
|
|
59
62
|
|
|
60
|
-
import { ${name}Action } from "@/actions/${
|
|
63
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
61
64
|
|
|
62
65
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
63
66
|
|
|
67
|
+
export type ${clientInputType} = Parameters<typeof ${name}Client> extends [] ? void : Parameters<typeof ${name}Client>[0]
|
|
68
|
+
|
|
64
69
|
export interface Use${upName}Params<TOnMutateResult = unknown> extends Omit<
|
|
65
|
-
UseMutationOptions<Awaited<ReturnType<typeof ${name}Client>>, Error,
|
|
70
|
+
UseMutationOptions<Awaited<ReturnType<typeof ${name}Client>>, Error, ${clientInputType}, TOnMutateResult>,
|
|
66
71
|
"mutationFn"
|
|
67
72
|
> {}
|
|
68
73
|
|
|
@@ -109,11 +114,13 @@ export function use${upName}<TOnMutateResult = unknown>({ onMutate, onSuccess, o
|
|
|
109
114
|
const getHook = `import { createRequestFn, isNonNullable } from "deepsea-tools"
|
|
110
115
|
import { createUseQuery } from "soda-tanstack-query"
|
|
111
116
|
|
|
112
|
-
import { ${name}Action } from "@/actions/${
|
|
117
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
113
118
|
|
|
114
119
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
115
120
|
|
|
116
|
-
export
|
|
121
|
+
export type ${clientInputType} = Parameters<typeof ${name}Client> extends [] ? undefined : Parameters<typeof ${name}Client>[0]
|
|
122
|
+
|
|
123
|
+
export function ${name}ClientOptional(id?: ${clientInputType} | null) {
|
|
117
124
|
return isNonNullable(id) ? ${name}Client(id) : null
|
|
118
125
|
}
|
|
119
126
|
|
|
@@ -126,7 +133,7 @@ export const use${upName} = createUseQuery({
|
|
|
126
133
|
const queryHook = `import { createRequestFn } from "deepsea-tools"
|
|
127
134
|
import { createUseQuery } from "soda-tanstack-query"
|
|
128
135
|
|
|
129
|
-
import { ${name}Action } from "@/actions/${
|
|
136
|
+
import { ${name}Action } from "@/actions/${actionImportPath}"
|
|
130
137
|
|
|
131
138
|
export const ${name}Client = createRequestFn(${name}Action)
|
|
132
139
|
|
|
@@ -142,10 +149,6 @@ export const use${upName} = createUseQuery({
|
|
|
142
149
|
mutation: mutationHook,
|
|
143
150
|
}
|
|
144
151
|
|
|
145
|
-
const hookName = base.replace(/^./, char => `use${char.toUpperCase()}`)
|
|
146
|
-
|
|
147
|
-
const hookPath = join("hooks", dir, hookName)
|
|
148
|
-
|
|
149
152
|
let hookType = getHookTypeFromName(name)
|
|
150
153
|
let overwrite = true
|
|
151
154
|
|
|
@@ -160,34 +163,40 @@ export const use${upName} = createUseQuery({
|
|
|
160
163
|
}
|
|
161
164
|
|
|
162
165
|
hookMap[path] = {
|
|
166
|
+
hookPath,
|
|
163
167
|
overwrite,
|
|
164
168
|
type: hookType,
|
|
165
169
|
...map,
|
|
166
170
|
}
|
|
167
171
|
}
|
|
168
172
|
|
|
169
|
-
export async function
|
|
173
|
+
export async function createHookFromFolder() {
|
|
170
174
|
const map: Record<string, HookData> = {}
|
|
171
175
|
|
|
172
|
-
async function
|
|
176
|
+
async function _createHookFromFolder(dir: string) {
|
|
173
177
|
const content = await readdir(dir)
|
|
174
178
|
|
|
175
179
|
for (const item of content) {
|
|
176
180
|
const path = join(dir, item)
|
|
177
181
|
const stats = await stat(path)
|
|
178
182
|
|
|
179
|
-
if (stats.isDirectory()) await
|
|
183
|
+
if (stats.isDirectory()) await _createHookFromFolder(path)
|
|
180
184
|
if (stats.isFile()) await createHook(path, map)
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
|
|
188
|
+
try {
|
|
189
|
+
await _createHookFromFolder("actions")
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (isNodeError(error) && error.code === "ENOENT") return map
|
|
192
|
+
throw error
|
|
193
|
+
}
|
|
185
194
|
|
|
186
195
|
return map
|
|
187
196
|
}
|
|
188
197
|
|
|
189
198
|
export async function hook(options: Record<string, string>, { args }: Command) {
|
|
190
|
-
const map = await
|
|
199
|
+
const map = await createHookFromFolder()
|
|
191
200
|
|
|
192
201
|
const entires = Object.entries(map)
|
|
193
202
|
|
|
@@ -204,8 +213,8 @@ export async function hook(options: Record<string, string>, { args }: Command) {
|
|
|
204
213
|
|
|
205
214
|
const setting = await getSetting()
|
|
206
215
|
|
|
207
|
-
for
|
|
208
|
-
const resolved = join(root,
|
|
216
|
+
for (const [path, { hookPath, overwrite, type, ...map }] of newEntires) {
|
|
217
|
+
const resolved = join(root, hookPath)
|
|
209
218
|
|
|
210
219
|
const answer = await select<OperationType>({
|
|
211
220
|
message: path,
|
|
@@ -218,16 +227,10 @@ export async function hook(options: Record<string, string>, { args }: Command) {
|
|
|
218
227
|
|
|
219
228
|
if (answer === "skip") continue
|
|
220
229
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
"hooks",
|
|
226
|
-
dir,
|
|
227
|
-
base.replace(/^./, char => `use${char.toUpperCase()}`),
|
|
228
|
-
),
|
|
229
|
-
map[answer],
|
|
230
|
-
)
|
|
230
|
+
await writeGeneratedFile({
|
|
231
|
+
path: hookPath,
|
|
232
|
+
content: map[answer],
|
|
233
|
+
})
|
|
231
234
|
}
|
|
232
235
|
|
|
233
236
|
await writeSdNextSetting(setting)
|
|
@@ -237,18 +240,20 @@ export async function hook(options: Record<string, string>, { args }: Command) {
|
|
|
237
240
|
choices: oldEntires.map(([key]) => key),
|
|
238
241
|
})
|
|
239
242
|
|
|
240
|
-
for (const [path, { overwrite, type, ...map }] of oldEntires) {
|
|
243
|
+
for (const [path, { hookPath, overwrite, type, ...map }] of oldEntires) {
|
|
241
244
|
if (!overwrites.includes(path)) continue
|
|
242
245
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
"hooks",
|
|
248
|
-
dir,
|
|
249
|
-
base.replace(/^./, char => `use${char.toUpperCase()}`),
|
|
250
|
-
),
|
|
251
|
-
map[type],
|
|
252
|
-
)
|
|
246
|
+
await writeGeneratedFile({
|
|
247
|
+
path: hookPath,
|
|
248
|
+
content: map[type],
|
|
249
|
+
})
|
|
253
250
|
}
|
|
254
251
|
}
|
|
252
|
+
|
|
253
|
+
export interface NodeError {
|
|
254
|
+
code?: string
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function isNodeError(error: unknown): error is NodeError {
|
|
258
|
+
return typeof error === "object" && error !== null
|
|
259
|
+
}
|