@outcomeeng/spx 0.1.5 → 0.1.7
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 +33 -17
- package/dist/{chunk-5L7CHFBC.js → chunk-BQLLI7GS.js} +1 -1
- package/dist/chunk-BQLLI7GS.js.map +1 -0
- package/dist/cli.js +280 -264
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5L7CHFBC.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LEAF_KIND,
|
|
3
3
|
parseWorkItemName
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BQLLI7GS.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -17,19 +17,19 @@ async function initCommand(options = {}) {
|
|
|
17
17
|
["plugin", "marketplace", "list"],
|
|
18
18
|
{ cwd }
|
|
19
19
|
);
|
|
20
|
-
const exists = listOutput.includes("
|
|
20
|
+
const exists = listOutput.includes("outcomeeng");
|
|
21
21
|
if (!exists) {
|
|
22
22
|
await execa(
|
|
23
23
|
"claude",
|
|
24
|
-
["plugin", "marketplace", "add", "
|
|
24
|
+
["plugin", "marketplace", "add", "outcomeeng/claude"],
|
|
25
25
|
{ cwd }
|
|
26
26
|
);
|
|
27
|
-
return "\u2713
|
|
27
|
+
return "\u2713 outcomeeng marketplace installed successfully\n\nRun 'claude plugin marketplace list' to view all marketplaces.";
|
|
28
28
|
} else {
|
|
29
|
-
await execa("claude", ["plugin", "marketplace", "update", "
|
|
29
|
+
await execa("claude", ["plugin", "marketplace", "update", "outcomeeng"], {
|
|
30
30
|
cwd
|
|
31
31
|
});
|
|
32
|
-
return "\u2713
|
|
32
|
+
return "\u2713 outcomeeng marketplace updated successfully\n\nThe marketplace is now up to date.";
|
|
33
33
|
}
|
|
34
34
|
} catch (error) {
|
|
35
35
|
if (error instanceof Error) {
|
|
@@ -643,7 +643,7 @@ Searched for: **/.claude/settings.local.json`;
|
|
|
643
643
|
|
|
644
644
|
// src/domains/claude/index.ts
|
|
645
645
|
function registerClaudeCommands(claudeCmd) {
|
|
646
|
-
claudeCmd.command("init").description("Initialize or update
|
|
646
|
+
claudeCmd.command("init").description("Initialize or update outcomeeng marketplace plugin").action(async () => {
|
|
647
647
|
try {
|
|
648
648
|
const output = await initCommand({ cwd: process.cwd() });
|
|
649
649
|
console.log(output);
|
|
@@ -704,7 +704,118 @@ var claudeDomain = {
|
|
|
704
704
|
|
|
705
705
|
// src/commands/session/archive.ts
|
|
706
706
|
import { mkdir, rename, stat } from "fs/promises";
|
|
707
|
-
import { dirname, join as join2 } from "path";
|
|
707
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
708
|
+
|
|
709
|
+
// src/git/root.ts
|
|
710
|
+
import { execa as execa2 } from "execa";
|
|
711
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
712
|
+
|
|
713
|
+
// src/config/defaults.ts
|
|
714
|
+
var DEFAULT_CONFIG = {
|
|
715
|
+
specs: {
|
|
716
|
+
root: "specs",
|
|
717
|
+
work: {
|
|
718
|
+
dir: "work",
|
|
719
|
+
statusDirs: {
|
|
720
|
+
doing: "doing",
|
|
721
|
+
backlog: "backlog",
|
|
722
|
+
done: "archive"
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
decisions: "decisions",
|
|
726
|
+
templates: "templates"
|
|
727
|
+
},
|
|
728
|
+
sessions: {
|
|
729
|
+
dir: ".spx/sessions",
|
|
730
|
+
statusDirs: {
|
|
731
|
+
todo: "todo",
|
|
732
|
+
doing: "doing",
|
|
733
|
+
archive: "archive"
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
|
|
738
|
+
// src/git/root.ts
|
|
739
|
+
var defaultDeps = {
|
|
740
|
+
execa: async (command, args, options) => {
|
|
741
|
+
const result = await execa2(command, args, options);
|
|
742
|
+
return {
|
|
743
|
+
exitCode: result.exitCode ?? 0,
|
|
744
|
+
stdout: typeof result.stdout === "string" ? result.stdout : String(result.stdout),
|
|
745
|
+
stderr: typeof result.stderr === "string" ? result.stderr : String(result.stderr)
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
var NOT_GIT_REPO_WARNING = "Warning: Not in a git repository. Sessions will be created relative to current directory.";
|
|
750
|
+
function extractStdout(stdout) {
|
|
751
|
+
if (!stdout) return "";
|
|
752
|
+
const str = typeof stdout === "string" ? stdout : String(stdout);
|
|
753
|
+
return str.trim().replace(/\/+$/, "");
|
|
754
|
+
}
|
|
755
|
+
async function detectMainRepoRoot(cwd = process.cwd(), deps = defaultDeps) {
|
|
756
|
+
try {
|
|
757
|
+
const toplevelResult = await deps.execa(
|
|
758
|
+
"git",
|
|
759
|
+
["rev-parse", "--show-toplevel"],
|
|
760
|
+
{ cwd, reject: false }
|
|
761
|
+
);
|
|
762
|
+
if (toplevelResult.exitCode !== 0 || !toplevelResult.stdout) {
|
|
763
|
+
return {
|
|
764
|
+
root: cwd,
|
|
765
|
+
isGitRepo: false,
|
|
766
|
+
warning: NOT_GIT_REPO_WARNING
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
const toplevel = extractStdout(toplevelResult.stdout);
|
|
770
|
+
const commonDirResult = await deps.execa(
|
|
771
|
+
"git",
|
|
772
|
+
["rev-parse", "--git-common-dir"],
|
|
773
|
+
{ cwd, reject: false }
|
|
774
|
+
);
|
|
775
|
+
if (commonDirResult.exitCode !== 0 || !commonDirResult.stdout) {
|
|
776
|
+
return {
|
|
777
|
+
root: toplevel,
|
|
778
|
+
isGitRepo: true
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
const commonDir = extractStdout(commonDirResult.stdout);
|
|
782
|
+
const absoluteCommonDir = isAbsolute(commonDir) ? commonDir : resolve(toplevel, commonDir);
|
|
783
|
+
const mainRepoRoot = dirname(absoluteCommonDir);
|
|
784
|
+
return {
|
|
785
|
+
root: mainRepoRoot,
|
|
786
|
+
isGitRepo: true
|
|
787
|
+
};
|
|
788
|
+
} catch {
|
|
789
|
+
return {
|
|
790
|
+
root: cwd,
|
|
791
|
+
isGitRepo: false,
|
|
792
|
+
warning: NOT_GIT_REPO_WARNING
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
async function resolveSessionConfig(options = {}) {
|
|
797
|
+
const { sessionsDir, cwd, deps } = options;
|
|
798
|
+
const { statusDirs: statusDirs2 } = DEFAULT_CONFIG.sessions;
|
|
799
|
+
if (sessionsDir) {
|
|
800
|
+
return {
|
|
801
|
+
config: {
|
|
802
|
+
todoDir: join(sessionsDir, statusDirs2.todo),
|
|
803
|
+
doingDir: join(sessionsDir, statusDirs2.doing),
|
|
804
|
+
archiveDir: join(sessionsDir, statusDirs2.archive)
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
const gitResult = await detectMainRepoRoot(cwd, deps);
|
|
809
|
+
const baseDir = join(gitResult.root, DEFAULT_CONFIG.sessions.dir);
|
|
810
|
+
return {
|
|
811
|
+
config: {
|
|
812
|
+
todoDir: join(baseDir, statusDirs2.todo),
|
|
813
|
+
doingDir: join(baseDir, statusDirs2.doing),
|
|
814
|
+
archiveDir: join(baseDir, statusDirs2.archive)
|
|
815
|
+
},
|
|
816
|
+
warning: gitResult.warning
|
|
817
|
+
};
|
|
818
|
+
}
|
|
708
819
|
|
|
709
820
|
// src/session/errors.ts
|
|
710
821
|
var SessionError = class extends Error {
|
|
@@ -753,33 +864,73 @@ var NoSessionsAvailableError = class extends SessionError {
|
|
|
753
864
|
}
|
|
754
865
|
};
|
|
755
866
|
|
|
756
|
-
// src/session/
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
dir: "work",
|
|
765
|
-
statusDirs: {
|
|
766
|
-
doing: "doing",
|
|
767
|
-
backlog: "backlog",
|
|
768
|
-
done: "archive"
|
|
769
|
-
}
|
|
770
|
-
},
|
|
771
|
-
decisions: "decisions",
|
|
772
|
-
templates: "templates"
|
|
773
|
-
},
|
|
774
|
-
sessions: {
|
|
775
|
-
dir: ".spx/sessions",
|
|
776
|
-
statusDirs: {
|
|
777
|
-
todo: "todo",
|
|
778
|
-
doing: "doing",
|
|
779
|
-
archive: "archive"
|
|
780
|
-
}
|
|
867
|
+
// src/commands/session/archive.ts
|
|
868
|
+
var SessionAlreadyArchivedError = class extends Error {
|
|
869
|
+
/** The session ID that is already archived */
|
|
870
|
+
sessionId;
|
|
871
|
+
constructor(sessionId) {
|
|
872
|
+
super(`Session already archived: ${sessionId}.`);
|
|
873
|
+
this.name = "SessionAlreadyArchivedError";
|
|
874
|
+
this.sessionId = sessionId;
|
|
781
875
|
}
|
|
782
876
|
};
|
|
877
|
+
async function resolveArchivePaths(sessionId, config) {
|
|
878
|
+
const filename = `${sessionId}.md`;
|
|
879
|
+
const todoPath = join2(config.todoDir, filename);
|
|
880
|
+
const doingPath = join2(config.doingDir, filename);
|
|
881
|
+
const archivePath = join2(config.archiveDir, filename);
|
|
882
|
+
try {
|
|
883
|
+
const archiveStats = await stat(archivePath);
|
|
884
|
+
if (archiveStats.isFile()) {
|
|
885
|
+
throw new SessionAlreadyArchivedError(sessionId);
|
|
886
|
+
}
|
|
887
|
+
} catch (error) {
|
|
888
|
+
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
889
|
+
throw error;
|
|
890
|
+
}
|
|
891
|
+
if (error instanceof SessionAlreadyArchivedError) {
|
|
892
|
+
throw error;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
try {
|
|
896
|
+
const todoStats = await stat(todoPath);
|
|
897
|
+
if (todoStats.isFile()) {
|
|
898
|
+
return { source: todoPath, target: archivePath };
|
|
899
|
+
}
|
|
900
|
+
} catch {
|
|
901
|
+
}
|
|
902
|
+
try {
|
|
903
|
+
const doingStats = await stat(doingPath);
|
|
904
|
+
if (doingStats.isFile()) {
|
|
905
|
+
return { source: doingPath, target: archivePath };
|
|
906
|
+
}
|
|
907
|
+
} catch {
|
|
908
|
+
}
|
|
909
|
+
throw new SessionNotFoundError(sessionId);
|
|
910
|
+
}
|
|
911
|
+
async function archiveCommand(options) {
|
|
912
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
913
|
+
const { source, target } = await resolveArchivePaths(options.sessionId, config);
|
|
914
|
+
await mkdir(dirname2(target), { recursive: true });
|
|
915
|
+
await rename(source, target);
|
|
916
|
+
return `Archived session: ${options.sessionId}
|
|
917
|
+
Archive location: ${target}`;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// src/commands/session/delete.ts
|
|
921
|
+
import { stat as stat2, unlink } from "fs/promises";
|
|
922
|
+
|
|
923
|
+
// src/session/delete.ts
|
|
924
|
+
function resolveDeletePath(sessionId, existingPaths) {
|
|
925
|
+
const matchingPath = existingPaths.find((path8) => path8.includes(sessionId));
|
|
926
|
+
if (!matchingPath) {
|
|
927
|
+
throw new SessionNotFoundError(sessionId);
|
|
928
|
+
}
|
|
929
|
+
return matchingPath;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// src/session/show.ts
|
|
933
|
+
import { join as join3 } from "path";
|
|
783
934
|
|
|
784
935
|
// src/session/list.ts
|
|
785
936
|
import { parse as parseYaml } from "yaml";
|
|
@@ -819,6 +970,8 @@ function parseSessionId(id) {
|
|
|
819
970
|
}
|
|
820
971
|
|
|
821
972
|
// src/session/types.ts
|
|
973
|
+
var SESSION_STATUSES = ["todo", "doing", "archive"];
|
|
974
|
+
var DEFAULT_LIST_STATUSES = ["doing", "todo"];
|
|
822
975
|
var PRIORITY_ORDER = {
|
|
823
976
|
high: 0,
|
|
824
977
|
medium: 1,
|
|
@@ -905,11 +1058,11 @@ function sortSessions(sessions) {
|
|
|
905
1058
|
// src/session/show.ts
|
|
906
1059
|
var { dir: sessionsBaseDir, statusDirs } = DEFAULT_CONFIG.sessions;
|
|
907
1060
|
var DEFAULT_SESSION_CONFIG = {
|
|
908
|
-
todoDir:
|
|
909
|
-
doingDir:
|
|
910
|
-
archiveDir:
|
|
1061
|
+
todoDir: join3(sessionsBaseDir, statusDirs.todo),
|
|
1062
|
+
doingDir: join3(sessionsBaseDir, statusDirs.doing),
|
|
1063
|
+
archiveDir: join3(sessionsBaseDir, statusDirs.archive)
|
|
911
1064
|
};
|
|
912
|
-
var SEARCH_ORDER = [
|
|
1065
|
+
var SEARCH_ORDER = [...SESSION_STATUSES];
|
|
913
1066
|
function resolveSessionPaths(id, config = DEFAULT_SESSION_CONFIG) {
|
|
914
1067
|
const filename = `${id}.md`;
|
|
915
1068
|
return [
|
|
@@ -941,76 +1094,6 @@ function formatShowOutput(content, options) {
|
|
|
941
1094
|
return header + separator + content;
|
|
942
1095
|
}
|
|
943
1096
|
|
|
944
|
-
// src/commands/session/archive.ts
|
|
945
|
-
var SessionAlreadyArchivedError = class extends Error {
|
|
946
|
-
/** The session ID that is already archived */
|
|
947
|
-
sessionId;
|
|
948
|
-
constructor(sessionId) {
|
|
949
|
-
super(`Session already archived: ${sessionId}.`);
|
|
950
|
-
this.name = "SessionAlreadyArchivedError";
|
|
951
|
-
this.sessionId = sessionId;
|
|
952
|
-
}
|
|
953
|
-
};
|
|
954
|
-
async function resolveArchivePaths(sessionId, config) {
|
|
955
|
-
const filename = `${sessionId}.md`;
|
|
956
|
-
const todoPath = join2(config.todoDir, filename);
|
|
957
|
-
const doingPath = join2(config.doingDir, filename);
|
|
958
|
-
const archivePath = join2(config.archiveDir, filename);
|
|
959
|
-
try {
|
|
960
|
-
const archiveStats = await stat(archivePath);
|
|
961
|
-
if (archiveStats.isFile()) {
|
|
962
|
-
throw new SessionAlreadyArchivedError(sessionId);
|
|
963
|
-
}
|
|
964
|
-
} catch (error) {
|
|
965
|
-
if (error instanceof Error && "code" in error && error.code !== "ENOENT") {
|
|
966
|
-
throw error;
|
|
967
|
-
}
|
|
968
|
-
if (error instanceof SessionAlreadyArchivedError) {
|
|
969
|
-
throw error;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
try {
|
|
973
|
-
const todoStats = await stat(todoPath);
|
|
974
|
-
if (todoStats.isFile()) {
|
|
975
|
-
return { source: todoPath, target: archivePath };
|
|
976
|
-
}
|
|
977
|
-
} catch {
|
|
978
|
-
}
|
|
979
|
-
try {
|
|
980
|
-
const doingStats = await stat(doingPath);
|
|
981
|
-
if (doingStats.isFile()) {
|
|
982
|
-
return { source: doingPath, target: archivePath };
|
|
983
|
-
}
|
|
984
|
-
} catch {
|
|
985
|
-
}
|
|
986
|
-
throw new SessionNotFoundError(sessionId);
|
|
987
|
-
}
|
|
988
|
-
async function archiveCommand(options) {
|
|
989
|
-
const config = options.sessionsDir ? {
|
|
990
|
-
todoDir: join2(options.sessionsDir, "todo"),
|
|
991
|
-
doingDir: join2(options.sessionsDir, "doing"),
|
|
992
|
-
archiveDir: join2(options.sessionsDir, "archive")
|
|
993
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
994
|
-
const { source, target } = await resolveArchivePaths(options.sessionId, config);
|
|
995
|
-
await mkdir(dirname(target), { recursive: true });
|
|
996
|
-
await rename(source, target);
|
|
997
|
-
return `Archived session: ${options.sessionId}
|
|
998
|
-
Archive location: ${target}`;
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// src/commands/session/delete.ts
|
|
1002
|
-
import { stat as stat2, unlink } from "fs/promises";
|
|
1003
|
-
import { join as join3 } from "path";
|
|
1004
|
-
|
|
1005
|
-
// src/session/delete.ts
|
|
1006
|
-
function resolveDeletePath(sessionId, existingPaths) {
|
|
1007
|
-
const matchingPath = existingPaths.find((path8) => path8.includes(sessionId));
|
|
1008
|
-
if (!matchingPath) {
|
|
1009
|
-
throw new SessionNotFoundError(sessionId);
|
|
1010
|
-
}
|
|
1011
|
-
return matchingPath;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
1097
|
// src/commands/session/delete.ts
|
|
1015
1098
|
async function findExistingPaths(paths) {
|
|
1016
1099
|
const existing = [];
|
|
@@ -1026,11 +1109,7 @@ async function findExistingPaths(paths) {
|
|
|
1026
1109
|
return existing;
|
|
1027
1110
|
}
|
|
1028
1111
|
async function deleteCommand(options) {
|
|
1029
|
-
const config = options.sessionsDir
|
|
1030
|
-
todoDir: join3(options.sessionsDir, "todo"),
|
|
1031
|
-
doingDir: join3(options.sessionsDir, "doing"),
|
|
1032
|
-
archiveDir: join3(options.sessionsDir, "archive")
|
|
1033
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1112
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1034
1113
|
const paths = resolveSessionPaths(options.sessionId, config);
|
|
1035
1114
|
const existingPaths = await findExistingPaths(paths);
|
|
1036
1115
|
const pathToDelete = resolveDeletePath(options.sessionId, existingPaths);
|
|
@@ -1040,47 +1119,7 @@ async function deleteCommand(options) {
|
|
|
1040
1119
|
|
|
1041
1120
|
// src/commands/session/handoff.ts
|
|
1042
1121
|
import { mkdir as mkdir2, writeFile } from "fs/promises";
|
|
1043
|
-
import { join as
|
|
1044
|
-
|
|
1045
|
-
// src/git/root.ts
|
|
1046
|
-
import { execa as execa2 } from "execa";
|
|
1047
|
-
import { join as join4 } from "path";
|
|
1048
|
-
var defaultDeps = {
|
|
1049
|
-
execa: (command, args, options) => execa2(command, args, options)
|
|
1050
|
-
};
|
|
1051
|
-
var NOT_GIT_REPO_WARNING = "Warning: Not in a git repository. Sessions will be created relative to current directory.";
|
|
1052
|
-
async function detectGitRoot(cwd = process.cwd(), deps = defaultDeps) {
|
|
1053
|
-
try {
|
|
1054
|
-
const result = await deps.execa(
|
|
1055
|
-
"git",
|
|
1056
|
-
["rev-parse", "--show-toplevel"],
|
|
1057
|
-
{ cwd, reject: false }
|
|
1058
|
-
);
|
|
1059
|
-
if (result.exitCode === 0 && result.stdout) {
|
|
1060
|
-
const stdout = typeof result.stdout === "string" ? result.stdout : result.stdout.toString();
|
|
1061
|
-
const gitRoot = stdout.trim().replace(/\/+$/, "");
|
|
1062
|
-
return {
|
|
1063
|
-
root: gitRoot,
|
|
1064
|
-
isGitRepo: true
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
return {
|
|
1068
|
-
root: cwd,
|
|
1069
|
-
isGitRepo: false,
|
|
1070
|
-
warning: NOT_GIT_REPO_WARNING
|
|
1071
|
-
};
|
|
1072
|
-
} catch {
|
|
1073
|
-
return {
|
|
1074
|
-
root: cwd,
|
|
1075
|
-
isGitRepo: false,
|
|
1076
|
-
warning: NOT_GIT_REPO_WARNING
|
|
1077
|
-
};
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
function buildSessionPathFromRoot(gitRoot, sessionId, config) {
|
|
1081
|
-
const filename = `${sessionId}.md`;
|
|
1082
|
-
return join4(gitRoot, config.todoDir, filename);
|
|
1083
|
-
}
|
|
1122
|
+
import { join as join4, resolve as resolve2 } from "path";
|
|
1084
1123
|
|
|
1085
1124
|
// src/session/create.ts
|
|
1086
1125
|
var MIN_CONTENT_LENGTH = 1;
|
|
@@ -1101,7 +1140,6 @@ function validateSessionContent(content) {
|
|
|
1101
1140
|
}
|
|
1102
1141
|
|
|
1103
1142
|
// src/commands/session/handoff.ts
|
|
1104
|
-
var { statusDirs: statusDirs2 } = DEFAULT_CONFIG.sessions;
|
|
1105
1143
|
var FRONT_MATTER_START = /^---\r?\n/;
|
|
1106
1144
|
function hasFrontmatter(content) {
|
|
1107
1145
|
return FRONT_MATTER_START.test(content);
|
|
@@ -1126,20 +1164,7 @@ priority: medium
|
|
|
1126
1164
|
${content}`;
|
|
1127
1165
|
}
|
|
1128
1166
|
async function handoffCommand(options) {
|
|
1129
|
-
const config = options.sessionsDir
|
|
1130
|
-
todoDir: join5(options.sessionsDir, statusDirs2.todo),
|
|
1131
|
-
doingDir: join5(options.sessionsDir, statusDirs2.doing),
|
|
1132
|
-
archiveDir: join5(options.sessionsDir, statusDirs2.archive)
|
|
1133
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1134
|
-
let baseDir;
|
|
1135
|
-
let warningMessage;
|
|
1136
|
-
if (options.sessionsDir) {
|
|
1137
|
-
baseDir = options.sessionsDir;
|
|
1138
|
-
} else {
|
|
1139
|
-
const gitResult = await detectGitRoot();
|
|
1140
|
-
baseDir = gitResult.root;
|
|
1141
|
-
warningMessage = gitResult.warning;
|
|
1142
|
-
}
|
|
1167
|
+
const { config, warning } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1143
1168
|
const sessionId = generateSessionId();
|
|
1144
1169
|
const fullContent = buildSessionContent(options.content);
|
|
1145
1170
|
const validation = validateSessionContent(fullContent);
|
|
@@ -1147,23 +1172,21 @@ async function handoffCommand(options) {
|
|
|
1147
1172
|
throw new SessionInvalidContentError(validation.error ?? "Unknown validation error");
|
|
1148
1173
|
}
|
|
1149
1174
|
const filename = `${sessionId}.md`;
|
|
1150
|
-
const sessionPath =
|
|
1151
|
-
const absolutePath =
|
|
1152
|
-
|
|
1153
|
-
await mkdir2(todoDir, { recursive: true });
|
|
1175
|
+
const sessionPath = join4(config.todoDir, filename);
|
|
1176
|
+
const absolutePath = resolve2(sessionPath);
|
|
1177
|
+
await mkdir2(config.todoDir, { recursive: true });
|
|
1154
1178
|
await writeFile(sessionPath, fullContent, "utf-8");
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (warningMessage) {
|
|
1158
|
-
process.stderr.write(`${warningMessage}
|
|
1179
|
+
if (warning) {
|
|
1180
|
+
process.stderr.write(`${warning}
|
|
1159
1181
|
`);
|
|
1160
1182
|
}
|
|
1161
|
-
return
|
|
1183
|
+
return `Created handoff session <HANDOFF_ID>${sessionId}</HANDOFF_ID>
|
|
1184
|
+
<SESSION_FILE>${absolutePath}</SESSION_FILE>`;
|
|
1162
1185
|
}
|
|
1163
1186
|
|
|
1164
1187
|
// src/commands/session/list.ts
|
|
1165
1188
|
import { readdir, readFile } from "fs/promises";
|
|
1166
|
-
import { join as
|
|
1189
|
+
import { join as join5 } from "path";
|
|
1167
1190
|
async function loadSessionsFromDir(dir, status) {
|
|
1168
1191
|
try {
|
|
1169
1192
|
const files = await readdir(dir);
|
|
@@ -1171,7 +1194,7 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
1171
1194
|
for (const file of files) {
|
|
1172
1195
|
if (!file.endsWith(".md")) continue;
|
|
1173
1196
|
const id = file.replace(".md", "");
|
|
1174
|
-
const filePath =
|
|
1197
|
+
const filePath = join5(dir, file);
|
|
1175
1198
|
const content = await readFile(filePath, "utf-8");
|
|
1176
1199
|
const metadata = parseSessionMetadata(content);
|
|
1177
1200
|
sessions.push({
|
|
@@ -1189,7 +1212,12 @@ async function loadSessionsFromDir(dir, status) {
|
|
|
1189
1212
|
throw error;
|
|
1190
1213
|
}
|
|
1191
1214
|
}
|
|
1192
|
-
|
|
1215
|
+
var STATUS_DIR_KEY = {
|
|
1216
|
+
todo: "todoDir",
|
|
1217
|
+
doing: "doingDir",
|
|
1218
|
+
archive: "archiveDir"
|
|
1219
|
+
};
|
|
1220
|
+
function formatTextOutput(sessions) {
|
|
1193
1221
|
if (sessions.length === 0) {
|
|
1194
1222
|
return ` (no sessions)`;
|
|
1195
1223
|
}
|
|
@@ -1199,47 +1227,38 @@ function formatTextOutput(sessions, _status) {
|
|
|
1199
1227
|
return ` ${s.id}${priority}${tags}`;
|
|
1200
1228
|
}).join("\n");
|
|
1201
1229
|
}
|
|
1230
|
+
function validateStatus(input) {
|
|
1231
|
+
if (SESSION_STATUSES.includes(input)) {
|
|
1232
|
+
return input;
|
|
1233
|
+
}
|
|
1234
|
+
throw new Error(
|
|
1235
|
+
`Invalid status: "${input}". Valid values: ${SESSION_STATUSES.join(", ")}`
|
|
1236
|
+
);
|
|
1237
|
+
}
|
|
1202
1238
|
async function listCommand(options) {
|
|
1203
|
-
const config = options.sessionsDir
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
archiveDir: join6(options.sessionsDir, "archive")
|
|
1207
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1208
|
-
const statuses = options.status ? [options.status] : ["todo", "doing", "archive"];
|
|
1209
|
-
const allSessions = {
|
|
1210
|
-
todo: [],
|
|
1211
|
-
doing: [],
|
|
1212
|
-
archive: []
|
|
1213
|
-
};
|
|
1239
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1240
|
+
const statuses = options.status !== void 0 ? [validateStatus(options.status)] : DEFAULT_LIST_STATUSES;
|
|
1241
|
+
const sessionsByStatus = {};
|
|
1214
1242
|
for (const status of statuses) {
|
|
1215
|
-
const
|
|
1216
|
-
const sessions = await loadSessionsFromDir(
|
|
1217
|
-
|
|
1243
|
+
const dirKey = STATUS_DIR_KEY[status];
|
|
1244
|
+
const sessions = await loadSessionsFromDir(config[dirKey], status);
|
|
1245
|
+
sessionsByStatus[status] = sortSessions(sessions);
|
|
1218
1246
|
}
|
|
1219
1247
|
if (options.format === "json") {
|
|
1220
|
-
return JSON.stringify(
|
|
1248
|
+
return JSON.stringify(sessionsByStatus, null, 2);
|
|
1221
1249
|
}
|
|
1222
1250
|
const lines = [];
|
|
1223
|
-
|
|
1224
|
-
lines.push(
|
|
1225
|
-
lines.push(formatTextOutput(
|
|
1226
|
-
lines.push("");
|
|
1227
|
-
}
|
|
1228
|
-
if (statuses.includes("todo")) {
|
|
1229
|
-
lines.push("TODO:");
|
|
1230
|
-
lines.push(formatTextOutput(allSessions.todo, "todo"));
|
|
1251
|
+
for (const status of statuses) {
|
|
1252
|
+
lines.push(`${status.toUpperCase()}:`);
|
|
1253
|
+
lines.push(formatTextOutput(sessionsByStatus[status] ?? []));
|
|
1231
1254
|
lines.push("");
|
|
1232
1255
|
}
|
|
1233
|
-
if (statuses.includes("archive")) {
|
|
1234
|
-
lines.push("ARCHIVE:");
|
|
1235
|
-
lines.push(formatTextOutput(allSessions.archive, "archive"));
|
|
1236
|
-
}
|
|
1237
1256
|
return lines.join("\n").trim();
|
|
1238
1257
|
}
|
|
1239
1258
|
|
|
1240
1259
|
// src/commands/session/pickup.ts
|
|
1241
1260
|
import { mkdir as mkdir3, readdir as readdir2, readFile as readFile2, rename as rename2 } from "fs/promises";
|
|
1242
|
-
import { join as
|
|
1261
|
+
import { join as join6 } from "path";
|
|
1243
1262
|
|
|
1244
1263
|
// src/session/pickup.ts
|
|
1245
1264
|
function buildClaimPaths(sessionId, config) {
|
|
@@ -1275,6 +1294,8 @@ function selectBestSession(sessions) {
|
|
|
1275
1294
|
}
|
|
1276
1295
|
|
|
1277
1296
|
// src/commands/session/pickup.ts
|
|
1297
|
+
var PICKUP_SOURCE_STATUS = SESSION_STATUSES[0];
|
|
1298
|
+
var PICKUP_TARGET_STATUS = SESSION_STATUSES[1];
|
|
1278
1299
|
async function loadTodoSessions(config) {
|
|
1279
1300
|
try {
|
|
1280
1301
|
const files = await readdir2(config.todoDir);
|
|
@@ -1282,12 +1303,12 @@ async function loadTodoSessions(config) {
|
|
|
1282
1303
|
for (const file of files) {
|
|
1283
1304
|
if (!file.endsWith(".md")) continue;
|
|
1284
1305
|
const id = file.replace(".md", "");
|
|
1285
|
-
const filePath =
|
|
1306
|
+
const filePath = join6(config.todoDir, file);
|
|
1286
1307
|
const content = await readFile2(filePath, "utf-8");
|
|
1287
1308
|
const metadata = parseSessionMetadata(content);
|
|
1288
1309
|
sessions.push({
|
|
1289
1310
|
id,
|
|
1290
|
-
status:
|
|
1311
|
+
status: PICKUP_SOURCE_STATUS,
|
|
1291
1312
|
path: filePath,
|
|
1292
1313
|
metadata
|
|
1293
1314
|
});
|
|
@@ -1301,11 +1322,7 @@ async function loadTodoSessions(config) {
|
|
|
1301
1322
|
}
|
|
1302
1323
|
}
|
|
1303
1324
|
async function pickupCommand(options) {
|
|
1304
|
-
const config = options.sessionsDir
|
|
1305
|
-
todoDir: join7(options.sessionsDir, "todo"),
|
|
1306
|
-
doingDir: join7(options.sessionsDir, "doing"),
|
|
1307
|
-
archiveDir: join7(options.sessionsDir, "archive")
|
|
1308
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1325
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1309
1326
|
let sessionId;
|
|
1310
1327
|
if (options.auto) {
|
|
1311
1328
|
const sessions = await loadTodoSessions(config);
|
|
@@ -1327,7 +1344,7 @@ async function pickupCommand(options) {
|
|
|
1327
1344
|
throw classifyClaimError(error, sessionId);
|
|
1328
1345
|
}
|
|
1329
1346
|
const content = await readFile2(paths.target, "utf-8");
|
|
1330
|
-
const output = formatShowOutput(content, { status:
|
|
1347
|
+
const output = formatShowOutput(content, { status: PICKUP_TARGET_STATUS });
|
|
1331
1348
|
return `Claimed session <PICKUP_ID>${sessionId}</PICKUP_ID>
|
|
1332
1349
|
|
|
1333
1350
|
${output}`;
|
|
@@ -1335,7 +1352,8 @@ ${output}`;
|
|
|
1335
1352
|
|
|
1336
1353
|
// src/commands/session/prune.ts
|
|
1337
1354
|
import { readdir as readdir3, readFile as readFile3, unlink as unlink2 } from "fs/promises";
|
|
1338
|
-
import { join as
|
|
1355
|
+
import { join as join7 } from "path";
|
|
1356
|
+
var PRUNE_STATUS = SESSION_STATUSES[2];
|
|
1339
1357
|
var DEFAULT_KEEP_COUNT = 5;
|
|
1340
1358
|
var PruneValidationError = class extends Error {
|
|
1341
1359
|
constructor(message) {
|
|
@@ -1359,12 +1377,12 @@ async function loadArchiveSessions(config) {
|
|
|
1359
1377
|
for (const file of files) {
|
|
1360
1378
|
if (!file.endsWith(".md")) continue;
|
|
1361
1379
|
const id = file.replace(".md", "");
|
|
1362
|
-
const filePath =
|
|
1380
|
+
const filePath = join7(config.archiveDir, file);
|
|
1363
1381
|
const content = await readFile3(filePath, "utf-8");
|
|
1364
1382
|
const metadata = parseSessionMetadata(content);
|
|
1365
1383
|
sessions.push({
|
|
1366
1384
|
id,
|
|
1367
|
-
status:
|
|
1385
|
+
status: PRUNE_STATUS,
|
|
1368
1386
|
path: filePath,
|
|
1369
1387
|
metadata
|
|
1370
1388
|
});
|
|
@@ -1388,11 +1406,7 @@ async function pruneCommand(options) {
|
|
|
1388
1406
|
validatePruneOptions(options);
|
|
1389
1407
|
const keep = options.keep ?? DEFAULT_KEEP_COUNT;
|
|
1390
1408
|
const dryRun = options.dryRun ?? false;
|
|
1391
|
-
const config = options.sessionsDir
|
|
1392
|
-
todoDir: join8(options.sessionsDir, "todo"),
|
|
1393
|
-
doingDir: join8(options.sessionsDir, "doing"),
|
|
1394
|
-
archiveDir: join8(options.sessionsDir, "archive")
|
|
1395
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1409
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1396
1410
|
const sessions = await loadArchiveSessions(config);
|
|
1397
1411
|
const toPrune = selectSessionsToPrune(sessions, keep);
|
|
1398
1412
|
if (toPrune.length === 0) {
|
|
@@ -1421,7 +1435,6 @@ async function pruneCommand(options) {
|
|
|
1421
1435
|
|
|
1422
1436
|
// src/commands/session/release.ts
|
|
1423
1437
|
import { readdir as readdir4, rename as rename3 } from "fs/promises";
|
|
1424
|
-
import { join as join9 } from "path";
|
|
1425
1438
|
|
|
1426
1439
|
// src/session/release.ts
|
|
1427
1440
|
function buildReleasePaths(sessionId, config) {
|
|
@@ -1458,11 +1471,7 @@ async function loadDoingSessions(config) {
|
|
|
1458
1471
|
}
|
|
1459
1472
|
}
|
|
1460
1473
|
async function releaseCommand(options) {
|
|
1461
|
-
const config = options.sessionsDir
|
|
1462
|
-
todoDir: join9(options.sessionsDir, "todo"),
|
|
1463
|
-
doingDir: join9(options.sessionsDir, "doing"),
|
|
1464
|
-
archiveDir: join9(options.sessionsDir, "archive")
|
|
1465
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1474
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1466
1475
|
let sessionId;
|
|
1467
1476
|
if (options.sessionId) {
|
|
1468
1477
|
sessionId = options.sessionId;
|
|
@@ -1489,7 +1498,6 @@ Session returned to todo directory.`;
|
|
|
1489
1498
|
|
|
1490
1499
|
// src/commands/session/show.ts
|
|
1491
1500
|
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
1492
|
-
import { join as join10 } from "path";
|
|
1493
1501
|
async function findExistingPath(paths, _config) {
|
|
1494
1502
|
for (let i = 0; i < paths.length; i++) {
|
|
1495
1503
|
const filePath = paths[i];
|
|
@@ -1504,11 +1512,7 @@ async function findExistingPath(paths, _config) {
|
|
|
1504
1512
|
return null;
|
|
1505
1513
|
}
|
|
1506
1514
|
async function showCommand(options) {
|
|
1507
|
-
const config = options.sessionsDir
|
|
1508
|
-
todoDir: join10(options.sessionsDir, "todo"),
|
|
1509
|
-
doingDir: join10(options.sessionsDir, "doing"),
|
|
1510
|
-
archiveDir: join10(options.sessionsDir, "archive")
|
|
1511
|
-
} : DEFAULT_SESSION_CONFIG;
|
|
1515
|
+
const { config } = await resolveSessionConfig({ sessionsDir: options.sessionsDir });
|
|
1512
1516
|
const paths = resolveSessionPaths(options.sessionId, config);
|
|
1513
1517
|
const found = await findExistingPath(paths, config);
|
|
1514
1518
|
if (!found) {
|
|
@@ -1580,17 +1584,17 @@ async function readStdin() {
|
|
|
1580
1584
|
if (process.stdin.isTTY) {
|
|
1581
1585
|
return void 0;
|
|
1582
1586
|
}
|
|
1583
|
-
return new Promise((
|
|
1587
|
+
return new Promise((resolve3) => {
|
|
1584
1588
|
let data = "";
|
|
1585
1589
|
process.stdin.setEncoding("utf-8");
|
|
1586
1590
|
process.stdin.on("data", (chunk) => {
|
|
1587
1591
|
data += chunk;
|
|
1588
1592
|
});
|
|
1589
1593
|
process.stdin.on("end", () => {
|
|
1590
|
-
|
|
1594
|
+
resolve3(data.trim() || void 0);
|
|
1591
1595
|
});
|
|
1592
1596
|
process.stdin.on("error", () => {
|
|
1593
|
-
|
|
1597
|
+
resolve3(void 0);
|
|
1594
1598
|
});
|
|
1595
1599
|
});
|
|
1596
1600
|
}
|
|
@@ -1599,7 +1603,7 @@ function handleError(error) {
|
|
|
1599
1603
|
process.exit(1);
|
|
1600
1604
|
}
|
|
1601
1605
|
function registerSessionCommands(sessionCmd) {
|
|
1602
|
-
sessionCmd.command("list").description("List
|
|
1606
|
+
sessionCmd.command("list").description("List active sessions (doing + todo by default)").option("--status <status>", "Filter by status (todo|doing|archive); defaults to doing + todo").option("--json", "Output as JSON").option("--sessions-dir <path>", "Custom sessions directory").action(async (options) => {
|
|
1603
1607
|
try {
|
|
1604
1608
|
const output = await listCommand({
|
|
1605
1609
|
status: options.status,
|
|
@@ -1611,6 +1615,18 @@ function registerSessionCommands(sessionCmd) {
|
|
|
1611
1615
|
handleError(error);
|
|
1612
1616
|
}
|
|
1613
1617
|
});
|
|
1618
|
+
sessionCmd.command("todo").description("List todo sessions").option("--json", "Output as JSON").option("--sessions-dir <path>", "Custom sessions directory").action(async (options) => {
|
|
1619
|
+
try {
|
|
1620
|
+
const output = await listCommand({
|
|
1621
|
+
status: SESSION_STATUSES[0],
|
|
1622
|
+
format: options.json ? "json" : "text",
|
|
1623
|
+
sessionsDir: options.sessionsDir
|
|
1624
|
+
});
|
|
1625
|
+
console.log(output);
|
|
1626
|
+
} catch (error) {
|
|
1627
|
+
handleError(error);
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1614
1630
|
sessionCmd.command("show <id>").description("Show session content").option("--sessions-dir <path>", "Custom sessions directory").action(async (id, options) => {
|
|
1615
1631
|
try {
|
|
1616
1632
|
const output = await showCommand({
|
|
@@ -3182,7 +3198,7 @@ var ParseErrorCode;
|
|
|
3182
3198
|
|
|
3183
3199
|
// src/validation/config/scope.ts
|
|
3184
3200
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
3185
|
-
import { join as
|
|
3201
|
+
import { join as join8 } from "path";
|
|
3186
3202
|
var TSCONFIG_FILES = {
|
|
3187
3203
|
full: "tsconfig.json",
|
|
3188
3204
|
production: "tsconfig.production.json"
|
|
@@ -3229,7 +3245,7 @@ function hasTypeScriptFilesRecursive(dirPath, maxDepth = 2, deps = defaultScopeD
|
|
|
3229
3245
|
if (hasDirectTsFiles) return true;
|
|
3230
3246
|
const subdirs = items.filter((item) => item.isDirectory() && !item.name.startsWith("."));
|
|
3231
3247
|
for (const subdir of subdirs.slice(0, 5)) {
|
|
3232
|
-
if (hasTypeScriptFilesRecursive(
|
|
3248
|
+
if (hasTypeScriptFilesRecursive(join8(dirPath, subdir.name), maxDepth - 1, deps)) {
|
|
3233
3249
|
return true;
|
|
3234
3250
|
}
|
|
3235
3251
|
}
|
|
@@ -3572,7 +3588,7 @@ function buildEslintArgs(context) {
|
|
|
3572
3588
|
}
|
|
3573
3589
|
async function validateESLint(context, runner = defaultEslintProcessRunner) {
|
|
3574
3590
|
const { scope, validatedFiles, mode } = context;
|
|
3575
|
-
return new Promise((
|
|
3591
|
+
return new Promise((resolve3) => {
|
|
3576
3592
|
if (!validatedFiles || validatedFiles.length === 0) {
|
|
3577
3593
|
if (scope === VALIDATION_SCOPES.PRODUCTION) {
|
|
3578
3594
|
process.env.ESLINT_PRODUCTION_ONLY = "1";
|
|
@@ -3591,13 +3607,13 @@ async function validateESLint(context, runner = defaultEslintProcessRunner) {
|
|
|
3591
3607
|
});
|
|
3592
3608
|
eslintProcess.on("close", (code) => {
|
|
3593
3609
|
if (code === 0) {
|
|
3594
|
-
|
|
3610
|
+
resolve3({ success: true });
|
|
3595
3611
|
} else {
|
|
3596
|
-
|
|
3612
|
+
resolve3({ success: false, error: `ESLint exited with code ${code}` });
|
|
3597
3613
|
}
|
|
3598
3614
|
});
|
|
3599
3615
|
eslintProcess.on("error", (error) => {
|
|
3600
|
-
|
|
3616
|
+
resolve3({ success: false, error: error.message });
|
|
3601
3617
|
});
|
|
3602
3618
|
});
|
|
3603
3619
|
}
|
|
@@ -3644,7 +3660,7 @@ async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner)
|
|
|
3644
3660
|
if (analyzeDirectories.length === 0) {
|
|
3645
3661
|
return { success: true };
|
|
3646
3662
|
}
|
|
3647
|
-
return new Promise((
|
|
3663
|
+
return new Promise((resolve3) => {
|
|
3648
3664
|
const knipProcess = runner.spawn("npx", ["knip"], {
|
|
3649
3665
|
cwd: process.cwd(),
|
|
3650
3666
|
stdio: "pipe"
|
|
@@ -3659,17 +3675,17 @@ async function validateKnip(typescriptScope, runner = defaultKnipProcessRunner)
|
|
|
3659
3675
|
});
|
|
3660
3676
|
knipProcess.on("close", (code) => {
|
|
3661
3677
|
if (code === 0) {
|
|
3662
|
-
|
|
3678
|
+
resolve3({ success: true });
|
|
3663
3679
|
} else {
|
|
3664
3680
|
const errorOutput = knipOutput || knipError || "Unused code detected";
|
|
3665
|
-
|
|
3681
|
+
resolve3({
|
|
3666
3682
|
success: false,
|
|
3667
3683
|
error: errorOutput
|
|
3668
3684
|
});
|
|
3669
3685
|
}
|
|
3670
3686
|
});
|
|
3671
3687
|
knipProcess.on("error", (error) => {
|
|
3672
|
-
|
|
3688
|
+
resolve3({ success: false, error: error.message });
|
|
3673
3689
|
});
|
|
3674
3690
|
});
|
|
3675
3691
|
} catch (error) {
|
|
@@ -3761,7 +3777,7 @@ import { spawn as spawn3 } from "child_process";
|
|
|
3761
3777
|
import { existsSync as existsSync2, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
3762
3778
|
import { mkdtemp } from "fs/promises";
|
|
3763
3779
|
import { tmpdir } from "os";
|
|
3764
|
-
import { isAbsolute, join as
|
|
3780
|
+
import { isAbsolute as isAbsolute2, join as join9 } from "path";
|
|
3765
3781
|
var defaultTypeScriptProcessRunner = { spawn: spawn3 };
|
|
3766
3782
|
var defaultTypeScriptDeps = {
|
|
3767
3783
|
mkdtemp,
|
|
@@ -3775,19 +3791,19 @@ function buildTypeScriptArgs(context) {
|
|
|
3775
3791
|
return scope === VALIDATION_SCOPES.FULL ? ["tsc", "--noEmit"] : ["tsc", "--project", configFile];
|
|
3776
3792
|
}
|
|
3777
3793
|
async function createFileSpecificTsconfig(scope, files, deps = defaultTypeScriptDeps) {
|
|
3778
|
-
const tempDir = await deps.mkdtemp(
|
|
3779
|
-
const configPath =
|
|
3794
|
+
const tempDir = await deps.mkdtemp(join9(tmpdir(), "validate-ts-"));
|
|
3795
|
+
const configPath = join9(tempDir, "tsconfig.json");
|
|
3780
3796
|
const baseConfigFile = TSCONFIG_FILES[scope];
|
|
3781
3797
|
const projectRoot = process.cwd();
|
|
3782
|
-
const absoluteFiles = files.map((file) =>
|
|
3798
|
+
const absoluteFiles = files.map((file) => isAbsolute2(file) ? file : join9(projectRoot, file));
|
|
3783
3799
|
const tempConfig = {
|
|
3784
|
-
extends:
|
|
3800
|
+
extends: join9(projectRoot, baseConfigFile),
|
|
3785
3801
|
files: absoluteFiles,
|
|
3786
3802
|
include: [],
|
|
3787
3803
|
exclude: [],
|
|
3788
3804
|
compilerOptions: {
|
|
3789
3805
|
noEmit: true,
|
|
3790
|
-
typeRoots: [
|
|
3806
|
+
typeRoots: [join9(projectRoot, "node_modules", "@types")],
|
|
3791
3807
|
types: ["node"]
|
|
3792
3808
|
}
|
|
3793
3809
|
};
|
|
@@ -3807,7 +3823,7 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3807
3823
|
if (files && files.length > 0) {
|
|
3808
3824
|
const { configPath, cleanup } = await createFileSpecificTsconfig(scope, files, deps);
|
|
3809
3825
|
try {
|
|
3810
|
-
return await new Promise((
|
|
3826
|
+
return await new Promise((resolve3) => {
|
|
3811
3827
|
const tscProcess = runner.spawn("npx", ["tsc", "--project", configPath], {
|
|
3812
3828
|
cwd: process.cwd(),
|
|
3813
3829
|
stdio: "inherit"
|
|
@@ -3815,14 +3831,14 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3815
3831
|
tscProcess.on("close", (code) => {
|
|
3816
3832
|
cleanup();
|
|
3817
3833
|
if (code === 0) {
|
|
3818
|
-
|
|
3834
|
+
resolve3({ success: true, skipped: false });
|
|
3819
3835
|
} else {
|
|
3820
|
-
|
|
3836
|
+
resolve3({ success: false, error: `TypeScript exited with code ${code}` });
|
|
3821
3837
|
}
|
|
3822
3838
|
});
|
|
3823
3839
|
tscProcess.on("error", (error) => {
|
|
3824
3840
|
cleanup();
|
|
3825
|
-
|
|
3841
|
+
resolve3({ success: false, error: error.message });
|
|
3826
3842
|
});
|
|
3827
3843
|
});
|
|
3828
3844
|
} catch (error) {
|
|
@@ -3834,20 +3850,20 @@ async function validateTypeScript(scope, typescriptScope, files, runner = defaul
|
|
|
3834
3850
|
tool = "npx";
|
|
3835
3851
|
tscArgs = buildTypeScriptArgs({ scope, configFile });
|
|
3836
3852
|
}
|
|
3837
|
-
return new Promise((
|
|
3853
|
+
return new Promise((resolve3) => {
|
|
3838
3854
|
const tscProcess = runner.spawn(tool, tscArgs, {
|
|
3839
3855
|
cwd: process.cwd(),
|
|
3840
3856
|
stdio: "inherit"
|
|
3841
3857
|
});
|
|
3842
3858
|
tscProcess.on("close", (code) => {
|
|
3843
3859
|
if (code === 0) {
|
|
3844
|
-
|
|
3860
|
+
resolve3({ success: true, skipped: false });
|
|
3845
3861
|
} else {
|
|
3846
|
-
|
|
3862
|
+
resolve3({ success: false, error: `TypeScript exited with code ${code}` });
|
|
3847
3863
|
}
|
|
3848
3864
|
});
|
|
3849
3865
|
tscProcess.on("error", (error) => {
|
|
3850
|
-
|
|
3866
|
+
resolve3({ success: false, error: error.message });
|
|
3851
3867
|
});
|
|
3852
3868
|
});
|
|
3853
3869
|
}
|