@aas-dev/stack 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/aas.js +2 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +15 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +75 -0
- package/dist/cursor-hook.d.ts +1 -0
- package/dist/cursor-hook.js +28 -0
- package/dist/errors.d.ts +12 -0
- package/dist/errors.js +5 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/policy.d.ts +1 -0
- package/dist/policy.js +124 -0
- package/dist/program.d.ts +3 -0
- package/dist/program.js +35 -0
- package/dist/run.d.ts +18 -0
- package/dist/run.js +99 -0
- package/dist/scaffold.d.ts +5 -0
- package/dist/scaffold.js +55 -0
- package/dist/templates.d.ts +6 -0
- package/dist/templates.js +37 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.js +166 -0
- package/package.json +54 -3
- package/plugin/aas.d.mts +8 -0
- package/plugin/aas.mjs +567 -0
- package/templates/AGENTS.md +132 -0
- package/templates/hooks.json +11 -0
- package/templates/pnpm-workspace.yaml +12 -0
- package/templates/pre-commit +3 -0
- package/templates/tsconfig.json +8 -0
- package/templates/verify.yml +20 -0
- package/templates/vite.config.ts +3 -0
- package/tsconfig.base.json +71 -0
- package/README.md +0 -3
package/bin/aas.js
ADDED
package/dist/bin.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/bin.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { main } from "./program.js";
|
|
2
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
3
|
+
import { Effect, Exit } from "effect";
|
|
4
|
+
// src/bin.ts
|
|
5
|
+
const teardown = (exit, onExit) => {
|
|
6
|
+
onExit(Exit.match(exit, {
|
|
7
|
+
onFailure: () => 1,
|
|
8
|
+
onSuccess: (code) => typeof code === "number" ? code : 1
|
|
9
|
+
}));
|
|
10
|
+
};
|
|
11
|
+
NodeRuntime.runMain(main(process.cwd()).pipe(Effect.provide(NodeServices.layer)), {
|
|
12
|
+
disableErrorReporting: true,
|
|
13
|
+
teardown
|
|
14
|
+
});
|
|
15
|
+
export {};
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { defineConfig as defineConfig$1 } from "vite-plus";
|
|
2
|
+
// src/config.ts
|
|
3
|
+
const pluginPath = `${import.meta.dirname}/../plugin/aas.mjs`;
|
|
4
|
+
const errors = (rules) => rules;
|
|
5
|
+
const rules = errors({
|
|
6
|
+
"aas/no-throw": "error",
|
|
7
|
+
"aas/no-try": "error",
|
|
8
|
+
"aas/no-error-class": "error",
|
|
9
|
+
"aas/no-process-env": "error",
|
|
10
|
+
"aas/no-type-assertion": "error",
|
|
11
|
+
"aas/no-explicit-any": "error",
|
|
12
|
+
"aas/no-non-null-assertion": "error",
|
|
13
|
+
"aas/no-bypass-comment": "error",
|
|
14
|
+
"aas/no-comments": "error",
|
|
15
|
+
"aas/effect-tests": "error",
|
|
16
|
+
"aas/no-raw-io": "error",
|
|
17
|
+
"aas/no-effect-run": "error",
|
|
18
|
+
"aas/no-module-let": "error",
|
|
19
|
+
"aas/no-plain-class": "error"
|
|
20
|
+
});
|
|
21
|
+
const defaultIgnore = [
|
|
22
|
+
"dist",
|
|
23
|
+
"vendor",
|
|
24
|
+
"node_modules",
|
|
25
|
+
"coverage",
|
|
26
|
+
".vite-hooks/_",
|
|
27
|
+
"packages/fence/fixtures"
|
|
28
|
+
];
|
|
29
|
+
const defineConfig = (options) => defineConfig$1({
|
|
30
|
+
lint: {
|
|
31
|
+
jsPlugins: [pluginPath],
|
|
32
|
+
options: {
|
|
33
|
+
denyWarnings: true,
|
|
34
|
+
respectEslintDisableDirectives: false
|
|
35
|
+
},
|
|
36
|
+
ignorePatterns: [...options?.ignorePatterns ?? defaultIgnore],
|
|
37
|
+
rules
|
|
38
|
+
},
|
|
39
|
+
fmt: {
|
|
40
|
+
printWidth: 100,
|
|
41
|
+
semi: false,
|
|
42
|
+
ignorePatterns: [
|
|
43
|
+
"node_modules",
|
|
44
|
+
"dist",
|
|
45
|
+
"vendor",
|
|
46
|
+
"coverage",
|
|
47
|
+
".vitest",
|
|
48
|
+
".vite-hooks/_"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
test: {
|
|
52
|
+
include: ["**/*.test.ts"],
|
|
53
|
+
exclude: [
|
|
54
|
+
"**/node_modules/**",
|
|
55
|
+
"**/dist/**",
|
|
56
|
+
"**/fixtures/**",
|
|
57
|
+
"**/vendor/**"
|
|
58
|
+
],
|
|
59
|
+
passWithNoTests: false,
|
|
60
|
+
server: { deps: { inline: ["@effect/vitest"] } }
|
|
61
|
+
},
|
|
62
|
+
pack: {
|
|
63
|
+
entry: ["src/**/*.ts", "!src/**/*.test.ts"],
|
|
64
|
+
dts: true,
|
|
65
|
+
unbundle: true,
|
|
66
|
+
fixedExtension: false,
|
|
67
|
+
hash: false,
|
|
68
|
+
clean: true,
|
|
69
|
+
logLevel: "error",
|
|
70
|
+
failOnWarn: false,
|
|
71
|
+
report: false,
|
|
72
|
+
deps: { neverBundle: true }
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
export { defineConfig };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BadHookInput } from "./errors.js";
|
|
2
|
+
import { writeStdout } from "./run.js";
|
|
3
|
+
import { commandBypassesVerify } from "./policy.js";
|
|
4
|
+
import { NodeRuntime, NodeServices } from "@effect/platform-node";
|
|
5
|
+
import { Effect, Result, Schema, Stream } from "effect";
|
|
6
|
+
import { Stdio } from "effect/Stdio";
|
|
7
|
+
// src/cursor-hook.ts
|
|
8
|
+
const HookInput = Schema.Struct({ command: Schema.optionalKey(Schema.String) });
|
|
9
|
+
const deny = JSON.stringify({
|
|
10
|
+
permission: "deny",
|
|
11
|
+
user_message: "pnpm verify cannot be skipped.",
|
|
12
|
+
agent_message: "Run pnpm verify. Do not pass --no-verify, a short -n on commit, LEFTHOOK=0, HUSKY=0, VP_GIT_HOOKS=0, VITE_GIT_HOOKS=0, or vp hooks disable."
|
|
13
|
+
});
|
|
14
|
+
const allow = JSON.stringify({ permission: "allow" });
|
|
15
|
+
const hook = Effect.gen(function* () {
|
|
16
|
+
const stdio = yield* Stdio;
|
|
17
|
+
const input = yield* Stream.mkString(Stream.decodeText(stdio.stdin));
|
|
18
|
+
const decoded = Schema.decodeResult(Schema.fromJsonString(HookInput))(input);
|
|
19
|
+
if (Result.isFailure(decoded)) return yield* new BadHookInput({});
|
|
20
|
+
const command = decoded.success.command ?? "";
|
|
21
|
+
if (command === "" || commandBypassesVerify(command)) {
|
|
22
|
+
yield* writeStdout(deny);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
yield* writeStdout(allow);
|
|
26
|
+
});
|
|
27
|
+
NodeRuntime.runMain(hook.pipe(Effect.provide(NodeServices.layer)), { disableErrorReporting: true });
|
|
28
|
+
export {};
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const CommandFailed_base: Schema.Class<CommandFailed, Schema.TaggedStruct<"CommandFailed", {
|
|
3
|
+
readonly lines: Schema.$Array<Schema.String>;
|
|
4
|
+
}>, import("effect/Cause").YieldableError & {
|
|
5
|
+
readonly brand: unique symbol;
|
|
6
|
+
}>;
|
|
7
|
+
export declare class CommandFailed extends CommandFailed_base {}
|
|
8
|
+
declare const BadHookInput_base: Schema.Class<BadHookInput, Schema.TaggedStruct<"BadHookInput", {}>, import("effect/Cause").YieldableError & {
|
|
9
|
+
readonly brand: unique symbol;
|
|
10
|
+
}>;
|
|
11
|
+
export declare class BadHookInput extends BadHookInput_base {}
|
|
12
|
+
export {};
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
// src/errors.ts
|
|
3
|
+
var CommandFailed = class extends Schema.TaggedError()("CommandFailed", { lines: Schema.Array(Schema.String) }) {};
|
|
4
|
+
var BadHookInput = class extends Schema.TaggedError()("BadHookInput", {}) {};
|
|
5
|
+
export { BadHookInput, CommandFailed };
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const commandBypassesVerify: (command: string) => boolean;
|
package/dist/policy.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// src/policy.ts
|
|
2
|
+
const noVerifySubcommands = /* @__PURE__ */ new Set([
|
|
3
|
+
"am",
|
|
4
|
+
"cherry-pick",
|
|
5
|
+
"commit",
|
|
6
|
+
"merge",
|
|
7
|
+
"pull",
|
|
8
|
+
"rebase"
|
|
9
|
+
]);
|
|
10
|
+
const globalOptionsWithValue = /* @__PURE__ */ new Set([
|
|
11
|
+
"-C",
|
|
12
|
+
"-c",
|
|
13
|
+
"--config-env",
|
|
14
|
+
"--exec-path",
|
|
15
|
+
"--git-dir",
|
|
16
|
+
"--list-cmds",
|
|
17
|
+
"--namespace",
|
|
18
|
+
"--super-prefix",
|
|
19
|
+
"--work-tree"
|
|
20
|
+
]);
|
|
21
|
+
const isGitToken = (token) => token === "git" || token.endsWith("/git");
|
|
22
|
+
const tokenAt = (tokens, index) => {
|
|
23
|
+
if (index < 0 || index >= tokens.length) return "";
|
|
24
|
+
const token = tokens[index];
|
|
25
|
+
if (token === void 0 || token === "") return "";
|
|
26
|
+
return token;
|
|
27
|
+
};
|
|
28
|
+
const tokenize = (command) => {
|
|
29
|
+
const tokens = [];
|
|
30
|
+
let current = "";
|
|
31
|
+
let quote = "";
|
|
32
|
+
let index = 0;
|
|
33
|
+
while (index < command.length) {
|
|
34
|
+
const char = command.charAt(index);
|
|
35
|
+
if (quote === "" && (char === " " || char === " " || char === "\n")) {
|
|
36
|
+
if (current !== "") {
|
|
37
|
+
tokens.push(current);
|
|
38
|
+
current = "";
|
|
39
|
+
}
|
|
40
|
+
index += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (quote === "" && (char === "'" || char === "\"")) {
|
|
44
|
+
quote = char;
|
|
45
|
+
index += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (quote !== "" && char === quote) {
|
|
49
|
+
quote = "";
|
|
50
|
+
index += 1;
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
current += char;
|
|
54
|
+
index += 1;
|
|
55
|
+
}
|
|
56
|
+
if (current !== "") tokens.push(current);
|
|
57
|
+
return tokens;
|
|
58
|
+
};
|
|
59
|
+
const subcommandOf = (tokens) => {
|
|
60
|
+
let gitAt = -1;
|
|
61
|
+
let index = 0;
|
|
62
|
+
while (index < tokens.length) {
|
|
63
|
+
if (isGitToken(tokenAt(tokens, index))) {
|
|
64
|
+
gitAt = index;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
index += 1;
|
|
68
|
+
}
|
|
69
|
+
if (gitAt < 0) return {
|
|
70
|
+
name: "",
|
|
71
|
+
at: -1
|
|
72
|
+
};
|
|
73
|
+
index = gitAt + 1;
|
|
74
|
+
while (index < tokens.length) {
|
|
75
|
+
const token = tokenAt(tokens, index);
|
|
76
|
+
if (token === "--") return {
|
|
77
|
+
name: "",
|
|
78
|
+
at: -1
|
|
79
|
+
};
|
|
80
|
+
if (token.startsWith("-")) {
|
|
81
|
+
const option = token.includes("=") ? token.slice(0, token.indexOf("=")) : token;
|
|
82
|
+
if (globalOptionsWithValue.has(option) && !token.includes("=")) {
|
|
83
|
+
index += 2;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
index += 1;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
name: token,
|
|
91
|
+
at: index
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
name: "",
|
|
96
|
+
at: -1
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
const shortClusterHasN = (tokens, from) => {
|
|
100
|
+
let index = from;
|
|
101
|
+
while (index < tokens.length) {
|
|
102
|
+
const token = tokenAt(tokens, index);
|
|
103
|
+
if (token.startsWith("-") && !token.startsWith("--") && token.includes("n")) return true;
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
};
|
|
108
|
+
const tokensBypass = (tokens) => {
|
|
109
|
+
const sub = subcommandOf(tokens);
|
|
110
|
+
if (noVerifySubcommands.has(sub.name) && shortClusterHasN(tokens, sub.at + 1)) return true;
|
|
111
|
+
let index = 0;
|
|
112
|
+
while (index < tokens.length) {
|
|
113
|
+
const token = tokenAt(tokens, index);
|
|
114
|
+
if ((token.includes(" ") || token.includes("\n")) && commandBypassesVerify(token)) return true;
|
|
115
|
+
index += 1;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
};
|
|
119
|
+
const skipsHook = (command) => command.includes("--no-verify") || command.includes("LEFTHOOK=0") || command.includes("HUSKY=0") || command.includes("VP_GIT_HOOKS=0") || command.includes("VITE_GIT_HOOKS=0") || command.includes("vp hooks disable") || command.includes("vp.hooks.disabled");
|
|
120
|
+
const commandBypassesVerify = (command) => {
|
|
121
|
+
if (skipsHook(command)) return true;
|
|
122
|
+
return tokensBypass(tokenize(command));
|
|
123
|
+
};
|
|
124
|
+
export { commandBypassesVerify };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { Stdio } from "effect/Stdio";
|
|
3
|
+
export declare const main: (cwd: string) => Effect.Effect<number, import("effect/PlatformError").PlatformError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/FileSystem").FileSystem | Stdio>;
|
package/dist/program.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { writeStdout } from "./run.js";
|
|
3
|
+
import { init, sync } from "./scaffold.js";
|
|
4
|
+
import { verify } from "./verify.js";
|
|
5
|
+
import { Cause, Effect } from "effect";
|
|
6
|
+
import { Stdio } from "effect/Stdio";
|
|
7
|
+
// src/program.ts
|
|
8
|
+
const usage = "usage: aas verify | aas init | aas sync";
|
|
9
|
+
const main = (cwd) => Effect.gen(function* () {
|
|
10
|
+
const command = (yield* (yield* Stdio).args)[0];
|
|
11
|
+
return yield* dispatch(command, cwd).pipe(Effect.catchTag("CommandFailed", (error) => Effect.gen(function* () {
|
|
12
|
+
yield* writeStdout(error.lines.join("\n"));
|
|
13
|
+
return 1;
|
|
14
|
+
})), Effect.catchCause((cause) => Effect.gen(function* () {
|
|
15
|
+
yield* writeStdout(Cause.pretty(cause));
|
|
16
|
+
return 1;
|
|
17
|
+
})));
|
|
18
|
+
});
|
|
19
|
+
const dispatch = (command, cwd) => Effect.gen(function* () {
|
|
20
|
+
if (command === "verify") {
|
|
21
|
+
yield* verify(cwd);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (command === "init") {
|
|
25
|
+
yield* init(cwd);
|
|
26
|
+
return 0;
|
|
27
|
+
}
|
|
28
|
+
if (command === "sync") {
|
|
29
|
+
yield* sync(cwd);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
yield* writeStdout(usage);
|
|
33
|
+
return yield* new CommandFailed({ lines: [] });
|
|
34
|
+
});
|
|
35
|
+
export { main };
|
package/dist/run.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { Effect, FileSystem } from "effect";
|
|
3
|
+
import { Stdio } from "effect/Stdio";
|
|
4
|
+
import { ChildProcessSpawner } from "effect/process";
|
|
5
|
+
export declare const succeeded: (code: ChildProcessSpawner.ExitCode) => boolean;
|
|
6
|
+
export declare const findBin: (name: string) => Effect.Effect<string, CommandFailed | import("effect/PlatformError").PlatformError, FileSystem.FileSystem>;
|
|
7
|
+
export declare const runTool: (input: {
|
|
8
|
+
readonly bin: string;
|
|
9
|
+
readonly args: ReadonlyArray<string>;
|
|
10
|
+
readonly cwd: string;
|
|
11
|
+
}) => Effect.Effect<{
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
exitCode: ChildProcessSpawner.ExitCode;
|
|
15
|
+
}, import("effect/PlatformError").PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
16
|
+
export declare const writeStdout: (text: string) => Effect.Effect<void, import("effect/PlatformError").PlatformError, Stdio>;
|
|
17
|
+
export declare const diagnosticsFrom: (text: string) => string[];
|
|
18
|
+
export declare const failLines: (name: string, text: string) => string[];
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { Effect, FileSystem, Stream } from "effect";
|
|
3
|
+
import { Stdio } from "effect/Stdio";
|
|
4
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
5
|
+
// src/run.ts
|
|
6
|
+
const succeeded = (code) => code === ChildProcessSpawner.ExitCode(0);
|
|
7
|
+
const findBin = (name) => Effect.gen(function* () {
|
|
8
|
+
const fs = yield* FileSystem.FileSystem;
|
|
9
|
+
const segments = import.meta.dirname.split("/");
|
|
10
|
+
let index = segments.length;
|
|
11
|
+
while (index > 1) {
|
|
12
|
+
index -= 1;
|
|
13
|
+
const candidate = `${segments.slice(0, index).join("/")}/node_modules/.bin/${name}`;
|
|
14
|
+
if (yield* fs.exists(candidate)) return candidate;
|
|
15
|
+
}
|
|
16
|
+
return yield* new CommandFailed({ lines: [`${name}:1: missing executable ${name}`] });
|
|
17
|
+
});
|
|
18
|
+
const runTool = (input) => Effect.gen(function* () {
|
|
19
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
20
|
+
return yield* Effect.scoped(Effect.gen(function* () {
|
|
21
|
+
const handle = yield* spawner.spawn(ChildProcess.make(input.bin, Array.from(input.args), {
|
|
22
|
+
cwd: input.cwd,
|
|
23
|
+
env: {
|
|
24
|
+
CI: "1",
|
|
25
|
+
NO_COLOR: "1"
|
|
26
|
+
},
|
|
27
|
+
extendEnv: true
|
|
28
|
+
}));
|
|
29
|
+
const [stdout, stderr] = yield* Effect.all([Stream.mkString(Stream.decodeText(handle.stdout)), Stream.mkString(Stream.decodeText(handle.stderr))], { concurrency: 2 });
|
|
30
|
+
return {
|
|
31
|
+
stdout,
|
|
32
|
+
stderr,
|
|
33
|
+
exitCode: yield* handle.exitCode
|
|
34
|
+
};
|
|
35
|
+
}));
|
|
36
|
+
});
|
|
37
|
+
const writeStdout = (text) => Effect.gen(function* () {
|
|
38
|
+
if (text.length === 0) return;
|
|
39
|
+
const stdio = yield* Stdio;
|
|
40
|
+
const body = text.endsWith("\n") ? text : `${text}\n`;
|
|
41
|
+
yield* Stream.run(Stream.make(body), stdio.stdout());
|
|
42
|
+
});
|
|
43
|
+
const lintLine = /^(.*?):(\d+):\d+:\s*(.*)$/u;
|
|
44
|
+
const tscLine = /^(.*?)\((\d+),\d+\):\s*(.*)$/u;
|
|
45
|
+
const formatLine = /^(\S+\.[A-Za-z0-9]+)(?:\s+\(\d+ms\))?$/u;
|
|
46
|
+
const escape = String.fromCharCode(27);
|
|
47
|
+
const stripAnsi = (text) => {
|
|
48
|
+
let result = "";
|
|
49
|
+
let index = 0;
|
|
50
|
+
while (index < text.length) {
|
|
51
|
+
if (text.charAt(index) === escape && text.charAt(index + 1) === "[") {
|
|
52
|
+
index += 2;
|
|
53
|
+
while (index < text.length) {
|
|
54
|
+
const code = text.charCodeAt(index);
|
|
55
|
+
index += 1;
|
|
56
|
+
if (code >= 64 && code <= 126) break;
|
|
57
|
+
}
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
result += text.charAt(index);
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
return result;
|
|
64
|
+
};
|
|
65
|
+
const pushUnique = (lines, line) => {
|
|
66
|
+
if (!lines.includes(line)) lines.push(line);
|
|
67
|
+
};
|
|
68
|
+
const diagnosticsFrom = (text) => {
|
|
69
|
+
const lines = [];
|
|
70
|
+
for (const raw of stripAnsi(text).split("\n")) {
|
|
71
|
+
const trimmed = raw.trim();
|
|
72
|
+
if (trimmed === "") continue;
|
|
73
|
+
const lint = lintLine.exec(trimmed);
|
|
74
|
+
const lintFile = lint?.[1];
|
|
75
|
+
const lintNo = lint?.[2];
|
|
76
|
+
const lintMessage = lint?.[3];
|
|
77
|
+
if (lintFile !== void 0 && lintNo !== void 0 && lintMessage !== void 0) {
|
|
78
|
+
pushUnique(lines, `${lintFile}:${lintNo}: ${lintMessage}`);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const tsc = tscLine.exec(trimmed);
|
|
82
|
+
const tscFile = tsc?.[1];
|
|
83
|
+
const tscNo = tsc?.[2];
|
|
84
|
+
const tscMessage = tsc?.[3];
|
|
85
|
+
if (tscFile !== void 0 && tscNo !== void 0 && tscMessage !== void 0) {
|
|
86
|
+
pushUnique(lines, `${tscFile}:${tscNo}: ${tscMessage}`);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const formattedFile = formatLine.exec(trimmed)?.[1];
|
|
90
|
+
if (formattedFile !== void 0 && !formattedFile.includes("://")) pushUnique(lines, `${formattedFile}:1: format`);
|
|
91
|
+
}
|
|
92
|
+
return lines;
|
|
93
|
+
};
|
|
94
|
+
const failLines = (name, text) => {
|
|
95
|
+
const lines = diagnosticsFrom(text);
|
|
96
|
+
if (lines.length > 0) return lines;
|
|
97
|
+
return [`${name}:1: ${text.split("\n").map((line) => line.trim()).find((line) => line !== "") ?? "failed"}`];
|
|
98
|
+
};
|
|
99
|
+
export { diagnosticsFrom, failLines, findBin, runTool, succeeded, writeStdout };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { Effect, FileSystem } from "effect";
|
|
3
|
+
export declare const writeTemplates: (cwd: string) => Effect.Effect<void, import("effect/PlatformError").PlatformError, FileSystem.FileSystem>;
|
|
4
|
+
export declare const init: (cwd: string) => Effect.Effect<void, CommandFailed | import("effect/PlatformError").PlatformError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | FileSystem.FileSystem>;
|
|
5
|
+
export declare const sync: typeof writeTemplates;
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { findBin, runTool, succeeded } from "./run.js";
|
|
3
|
+
import { managedFiles, readTemplate } from "./templates.js";
|
|
4
|
+
import { Effect, FileSystem } from "effect";
|
|
5
|
+
// src/scaffold.ts
|
|
6
|
+
const parentOf = (path) => {
|
|
7
|
+
const slash = path.lastIndexOf("/");
|
|
8
|
+
if (slash <= 0) return "";
|
|
9
|
+
return path.slice(0, slash);
|
|
10
|
+
};
|
|
11
|
+
const writeTemplates = (cwd) => Effect.gen(function* () {
|
|
12
|
+
const fs = yield* FileSystem.FileSystem;
|
|
13
|
+
for (const file of managedFiles) {
|
|
14
|
+
const text = yield* readTemplate(file.template);
|
|
15
|
+
const target = `${cwd}/${file.path}`;
|
|
16
|
+
const parent = parentOf(target);
|
|
17
|
+
if (parent !== "") yield* fs.makeDirectory(parent, { recursive: true });
|
|
18
|
+
yield* fs.writeFileString(target, text);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
const installHooks = (cwd) => Effect.gen(function* () {
|
|
22
|
+
const git = yield* runTool({
|
|
23
|
+
bin: "git",
|
|
24
|
+
args: ["rev-parse", "--git-dir"],
|
|
25
|
+
cwd
|
|
26
|
+
});
|
|
27
|
+
if (!succeeded(git.exitCode)) return;
|
|
28
|
+
const hooksPath = yield* runTool({
|
|
29
|
+
bin: "git",
|
|
30
|
+
args: [
|
|
31
|
+
"config",
|
|
32
|
+
"--get",
|
|
33
|
+
"core.hooksPath"
|
|
34
|
+
],
|
|
35
|
+
cwd
|
|
36
|
+
});
|
|
37
|
+
if (succeeded(hooksPath.exitCode)) return;
|
|
38
|
+
const vp = yield* findBin("vp");
|
|
39
|
+
const installed = yield* runTool({
|
|
40
|
+
bin: vp,
|
|
41
|
+
args: ["hooks", "enable"],
|
|
42
|
+
cwd
|
|
43
|
+
});
|
|
44
|
+
if (!succeeded(installed.exitCode)) return yield* new CommandFailed({ lines: failOr(installed.stdout, installed.stderr) });
|
|
45
|
+
});
|
|
46
|
+
const failOr = (stdout, stderr) => {
|
|
47
|
+
const text = `${stdout}\n${stderr}`.trim();
|
|
48
|
+
return [`vp:1: ${text === "" ? "vp hooks enable failed" : text.split("\n")[0] ?? "failed"}`];
|
|
49
|
+
};
|
|
50
|
+
const init = (cwd) => Effect.gen(function* () {
|
|
51
|
+
yield* writeTemplates(cwd);
|
|
52
|
+
yield* installHooks(cwd);
|
|
53
|
+
});
|
|
54
|
+
const sync = writeTemplates;
|
|
55
|
+
export { init, sync, writeTemplates };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Effect, FileSystem } from "effect";
|
|
2
|
+
export declare const managedFiles: ReadonlyArray<{
|
|
3
|
+
readonly template: string;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const readTemplate: (name: string) => Effect.Effect<string, import("effect/PlatformError").PlatformError, FileSystem.FileSystem>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Effect, FileSystem } from "effect";
|
|
2
|
+
// src/templates.ts
|
|
3
|
+
const managedFiles = [
|
|
4
|
+
{
|
|
5
|
+
template: "hooks.json",
|
|
6
|
+
path: ".cursor/hooks.json"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
template: "pre-commit",
|
|
10
|
+
path: ".vite-hooks/pre-commit"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
template: "pnpm-workspace.yaml",
|
|
14
|
+
path: "pnpm-workspace.yaml"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
template: "AGENTS.md",
|
|
18
|
+
path: "AGENTS.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
template: "verify.yml",
|
|
22
|
+
path: ".github/workflows/verify.yml"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
template: "vite.config.ts",
|
|
26
|
+
path: "vite.config.ts"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
template: "tsconfig.json",
|
|
30
|
+
path: "tsconfig.json"
|
|
31
|
+
}
|
|
32
|
+
];
|
|
33
|
+
const templateRoot = `${import.meta.dirname}/../templates`;
|
|
34
|
+
const readTemplate = (name) => Effect.gen(function* () {
|
|
35
|
+
return yield* (yield* FileSystem.FileSystem).readFileString(`${templateRoot}/${name}`);
|
|
36
|
+
});
|
|
37
|
+
export { managedFiles, readTemplate };
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { CommandFailed } from "./errors.js";
|
|
2
|
+
import { Effect, FileSystem } from "effect";
|
|
3
|
+
export declare const verify: (cwd: string) => Effect.Effect<undefined, CommandFailed | import("effect/PlatformError").PlatformError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | FileSystem.FileSystem>;
|