@elmundi/ship-cli 0.8.1 → 0.11.2
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 +415 -22
- package/bin/shipctl.mjs +165 -0
- package/lib/adapters/_fs.mjs +165 -0
- package/lib/adapters/agents/index.mjs +26 -0
- package/lib/adapters/ci/azure-pipelines.mjs +23 -0
- package/lib/adapters/ci/buildkite.mjs +24 -0
- package/lib/adapters/ci/circleci.mjs +23 -0
- package/lib/adapters/ci/gh-actions.mjs +29 -0
- package/lib/adapters/ci/gitlab-ci.mjs +23 -0
- package/lib/adapters/ci/jenkins.mjs +23 -0
- package/lib/adapters/ci/manual.mjs +18 -0
- package/lib/adapters/index.mjs +122 -0
- package/lib/adapters/language/dart.mjs +23 -0
- package/lib/adapters/language/go.mjs +23 -0
- package/lib/adapters/language/java.mjs +27 -0
- package/lib/adapters/language/js.mjs +32 -0
- package/lib/adapters/language/kotlin.mjs +48 -0
- package/lib/adapters/language/py.mjs +34 -0
- package/lib/adapters/language/rust.mjs +23 -0
- package/lib/adapters/language/swift.mjs +37 -0
- package/lib/adapters/language/ts.mjs +35 -0
- package/lib/adapters/trackers/azure-boards.mjs +49 -0
- package/lib/adapters/trackers/clickup.mjs +43 -0
- package/lib/adapters/trackers/github-issues.mjs +52 -0
- package/lib/adapters/trackers/jira.mjs +72 -0
- package/lib/adapters/trackers/linear.mjs +62 -0
- package/lib/adapters/trackers/none.mjs +18 -0
- package/lib/adapters/trackers/spreadsheet.mjs +28 -0
- package/lib/artifacts/fs-index.mjs +230 -0
- package/lib/bootstrap/render.mjs +373 -0
- package/lib/cache/store.mjs +422 -0
- package/lib/commands/bootstrap.mjs +4 -0
- package/lib/commands/callback.mjs +302 -0
- package/lib/commands/config.mjs +257 -0
- package/lib/commands/docs.mjs +1 -1
- package/lib/commands/doctor.mjs +583 -0
- package/lib/commands/feedback.mjs +355 -0
- package/lib/commands/help.mjs +96 -21
- package/lib/commands/init.mjs +830 -158
- package/lib/commands/kickoff.mjs +192 -0
- package/lib/commands/knowledge.mjs +368 -0
- package/lib/commands/lanes.mjs +502 -0
- package/lib/commands/manifest-catalog.mjs +102 -38
- package/lib/commands/migrate.mjs +204 -0
- package/lib/commands/new.mjs +452 -0
- package/lib/commands/patterns.mjs +9 -43
- package/lib/commands/run.mjs +617 -0
- package/lib/commands/sync.mjs +749 -0
- package/lib/commands/telemetry.mjs +390 -0
- package/lib/commands/verify.mjs +187 -0
- package/lib/config/io.mjs +232 -0
- package/lib/config/migrate.mjs +215 -0
- package/lib/config/schema.mjs +650 -0
- package/lib/detect.mjs +162 -19
- package/lib/feedback/drafts.mjs +129 -0
- package/lib/find-ship-root.mjs +16 -10
- package/lib/http.mjs +237 -11
- package/lib/state/idempotency.mjs +183 -0
- package/lib/state/lockfile.mjs +180 -0
- package/lib/telemetry/outbox.mjs +224 -0
- package/lib/templates.mjs +53 -65
- package/lib/verify/checks/agents-on-disk.mjs +58 -0
- package/lib/verify/checks/api-reachable.mjs +39 -0
- package/lib/verify/checks/artifacts-up-to-date.mjs +78 -0
- package/lib/verify/checks/bootstrap-files.mjs +67 -0
- package/lib/verify/checks/cache-integrity.mjs +51 -0
- package/lib/verify/checks/ci-secrets.mjs +86 -0
- package/lib/verify/checks/config-present.mjs +39 -0
- package/lib/verify/checks/gitignore-cache.mjs +51 -0
- package/lib/verify/checks/rules-markers.mjs +135 -0
- package/lib/verify/checks/stack-enums.mjs +33 -0
- package/lib/verify/checks/tracker-labels.mjs +91 -0
- package/lib/verify/registry.mjs +120 -0
- package/lib/version.mjs +34 -0
- package/package.json +10 -3
- package/bin/ship.mjs +0 -68
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Shared filesystem helpers for adapter `detect()` hooks.
|
|
6
|
+
*
|
|
7
|
+
* All helpers are synchronous (detect() is still exposed as async so future
|
|
8
|
+
* adapters that need I/O concurrency can use it freely) and **never throw** on
|
|
9
|
+
* missing files — adapters must return `present:false` instead of propagating
|
|
10
|
+
* ENOENT up to the doctor command.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function exists(cwd, ...rel) {
|
|
14
|
+
try {
|
|
15
|
+
return fs.existsSync(path.join(cwd, ...rel));
|
|
16
|
+
} catch {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isFile(cwd, ...rel) {
|
|
22
|
+
try {
|
|
23
|
+
const s = fs.statSync(path.join(cwd, ...rel));
|
|
24
|
+
return s.isFile();
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function isDir(cwd, ...rel) {
|
|
31
|
+
try {
|
|
32
|
+
const s = fs.statSync(path.join(cwd, ...rel));
|
|
33
|
+
return s.isDirectory();
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function readText(cwd, rel) {
|
|
40
|
+
try {
|
|
41
|
+
return fs.readFileSync(path.join(cwd, rel), "utf8");
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function readJson(cwd, rel) {
|
|
48
|
+
const txt = readText(cwd, rel);
|
|
49
|
+
if (txt == null) return null;
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(txt);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* List direct children of `cwd/rel`. Returns [] if missing / not a directory.
|
|
59
|
+
*/
|
|
60
|
+
export function listDir(cwd, rel) {
|
|
61
|
+
try {
|
|
62
|
+
return fs.readdirSync(path.join(cwd, rel));
|
|
63
|
+
} catch {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Shallow recursive walk capped at depth and file count to avoid hammering
|
|
70
|
+
* huge monorepos during `shipctl doctor`.
|
|
71
|
+
*/
|
|
72
|
+
export function walk(cwd, { maxDepth = 3, maxFiles = 500, ignore = DEFAULT_IGNORE } = {}) {
|
|
73
|
+
const out = [];
|
|
74
|
+
const base = path.resolve(cwd);
|
|
75
|
+
function visit(dir, depth) {
|
|
76
|
+
if (out.length >= maxFiles) return;
|
|
77
|
+
let entries;
|
|
78
|
+
try {
|
|
79
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
80
|
+
} catch {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
for (const e of entries) {
|
|
84
|
+
if (out.length >= maxFiles) return;
|
|
85
|
+
if (ignore.has(e.name)) continue;
|
|
86
|
+
const full = path.join(dir, e.name);
|
|
87
|
+
const rel = path.relative(base, full);
|
|
88
|
+
if (e.isDirectory()) {
|
|
89
|
+
if (depth < maxDepth) visit(full, depth + 1);
|
|
90
|
+
} else if (e.isFile()) {
|
|
91
|
+
out.push(rel);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
visit(base, 0);
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const DEFAULT_IGNORE = new Set([
|
|
100
|
+
"node_modules",
|
|
101
|
+
".git",
|
|
102
|
+
"dist",
|
|
103
|
+
"build",
|
|
104
|
+
".next",
|
|
105
|
+
"out",
|
|
106
|
+
"coverage",
|
|
107
|
+
".turbo",
|
|
108
|
+
"target",
|
|
109
|
+
"venv",
|
|
110
|
+
".venv",
|
|
111
|
+
"__pycache__",
|
|
112
|
+
".pytest_cache",
|
|
113
|
+
".mypy_cache",
|
|
114
|
+
".gradle",
|
|
115
|
+
"Pods",
|
|
116
|
+
"DerivedData",
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Read every .env*-style file at the repo root. Returns an array of
|
|
121
|
+
* `{file, content}`. Hidden/ignored by .gitignore is not a concern — we are
|
|
122
|
+
* only scanning for variable *names*, never values.
|
|
123
|
+
*/
|
|
124
|
+
export function readEnvFiles(cwd) {
|
|
125
|
+
const hits = [];
|
|
126
|
+
const entries = listDir(cwd, ".");
|
|
127
|
+
for (const name of entries) {
|
|
128
|
+
if (!/^\.env($|[.\-])/.test(name)) continue;
|
|
129
|
+
if (!isFile(cwd, name)) continue;
|
|
130
|
+
const content = readText(cwd, name);
|
|
131
|
+
if (content != null) hits.push({ file: name, content });
|
|
132
|
+
}
|
|
133
|
+
return hits;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Read every YAML workflow under `.github/workflows/`. Returns
|
|
138
|
+
* `[{file, content}]`.
|
|
139
|
+
*/
|
|
140
|
+
export function readGithubWorkflows(cwd) {
|
|
141
|
+
const dir = path.join(".github", "workflows");
|
|
142
|
+
if (!isDir(cwd, dir)) return [];
|
|
143
|
+
const hits = [];
|
|
144
|
+
for (const name of listDir(cwd, dir)) {
|
|
145
|
+
if (!/\.ya?ml$/i.test(name)) continue;
|
|
146
|
+
const rel = path.join(dir, name);
|
|
147
|
+
const content = readText(cwd, rel);
|
|
148
|
+
if (content != null) hits.push({ file: rel, content });
|
|
149
|
+
}
|
|
150
|
+
return hits;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Flatten a package.json's dependency sections into a single `{name → range}`
|
|
155
|
+
* map for easier inspection.
|
|
156
|
+
*/
|
|
157
|
+
export function pkgDeps(pkg) {
|
|
158
|
+
if (!pkg || typeof pkg !== "object") return {};
|
|
159
|
+
const all = {};
|
|
160
|
+
for (const key of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
161
|
+
const obj = pkg[key];
|
|
162
|
+
if (obj && typeof obj === "object") Object.assign(all, obj);
|
|
163
|
+
}
|
|
164
|
+
return all;
|
|
165
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent detection today lives in `cli/lib/detect.mjs` (synchronous, returns
|
|
3
|
+
* the `AgentTarget[]` shape with `{id, label, paths, confidence}`).
|
|
4
|
+
*
|
|
5
|
+
* This module re-exports it and provides a thin adapter-shaped wrapper
|
|
6
|
+
* (`detectAll`) that yields `{id, present, confidence, evidence}` entries
|
|
7
|
+
* consistent with tracker/ci/language adapters. Full per-agent adapter
|
|
8
|
+
* wrappers (bootstrap/verify) land in a later task.
|
|
9
|
+
*/
|
|
10
|
+
import { detectAgentTargets, KNOWN_AGENTS } from "../../detect.mjs";
|
|
11
|
+
|
|
12
|
+
export { detectAgentTargets, KNOWN_AGENTS };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} cwd
|
|
16
|
+
* @returns {Promise<Array<{id:string, present:boolean, confidence:number, evidence:Array}>>}
|
|
17
|
+
*/
|
|
18
|
+
export async function detectAllAgents(cwd) {
|
|
19
|
+
const targets = detectAgentTargets(cwd);
|
|
20
|
+
return targets.map((t) => ({
|
|
21
|
+
id: t.id,
|
|
22
|
+
present: true,
|
|
23
|
+
confidence: t.confidence,
|
|
24
|
+
evidence: [{ type: "file", where: t.paths[0] || "-", match: t.label }],
|
|
25
|
+
}));
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "azure-pipelines";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, "azure-pipelines.yml") || isFile(cwd, "azure-pipelines.yaml")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: "azure-pipelines.yml", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isDir, listDir } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "buildkite";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (!isDir(cwd, ".buildkite")) {
|
|
8
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
9
|
+
}
|
|
10
|
+
const files = listDir(cwd, ".buildkite");
|
|
11
|
+
return {
|
|
12
|
+
present: true,
|
|
13
|
+
confidence: 1,
|
|
14
|
+
evidence: [{ type: "dir", where: ".buildkite/", match: `${files.length} entry(ies)` }],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function bootstrap() {
|
|
19
|
+
return { todo: true };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function verify() {
|
|
23
|
+
return { todo: true };
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "circleci";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, ".circleci", "config.yml") || isFile(cwd, ".circleci", "config.yaml")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: ".circleci/config.yml", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isDir, listDir } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "gh-actions";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
const evidence = [];
|
|
8
|
+
if (!isDir(cwd, ".github", "workflows")) {
|
|
9
|
+
return { present: false, confidence: 0, evidence };
|
|
10
|
+
}
|
|
11
|
+
const files = listDir(cwd, ".github/workflows").filter((f) => /\.ya?ml$/i.test(f));
|
|
12
|
+
if (files.length === 0) {
|
|
13
|
+
return { present: false, confidence: 0, evidence };
|
|
14
|
+
}
|
|
15
|
+
evidence.push({
|
|
16
|
+
type: "dir",
|
|
17
|
+
where: ".github/workflows/",
|
|
18
|
+
match: `${files.length} workflow(s)`,
|
|
19
|
+
});
|
|
20
|
+
return { present: true, confidence: 1, evidence };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function bootstrap() {
|
|
24
|
+
return { todo: true };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function verify() {
|
|
28
|
+
return { todo: true };
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "gitlab-ci";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, ".gitlab-ci.yml")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: ".gitlab-ci.yml", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "jenkins";
|
|
4
|
+
export const kind = "ci";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, "Jenkinsfile")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: "Jenkinsfile", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const id = "manual";
|
|
2
|
+
export const kind = "ci";
|
|
3
|
+
|
|
4
|
+
export async function detect() {
|
|
5
|
+
return {
|
|
6
|
+
present: true,
|
|
7
|
+
confidence: 0.05,
|
|
8
|
+
evidence: [{ type: "fallback", where: "-", match: "no CI system detected" }],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function bootstrap() {
|
|
13
|
+
return { todo: true };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function verify() {
|
|
17
|
+
return { todo: true };
|
|
18
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import * as linear from "./trackers/linear.mjs";
|
|
2
|
+
import * as jira from "./trackers/jira.mjs";
|
|
3
|
+
import * as githubIssues from "./trackers/github-issues.mjs";
|
|
4
|
+
import * as azureBoards from "./trackers/azure-boards.mjs";
|
|
5
|
+
import * as clickup from "./trackers/clickup.mjs";
|
|
6
|
+
import * as spreadsheet from "./trackers/spreadsheet.mjs";
|
|
7
|
+
import * as trackerNone from "./trackers/none.mjs";
|
|
8
|
+
|
|
9
|
+
import * as ghActions from "./ci/gh-actions.mjs";
|
|
10
|
+
import * as gitlabCi from "./ci/gitlab-ci.mjs";
|
|
11
|
+
import * as buildkite from "./ci/buildkite.mjs";
|
|
12
|
+
import * as circleci from "./ci/circleci.mjs";
|
|
13
|
+
import * as azurePipelines from "./ci/azure-pipelines.mjs";
|
|
14
|
+
import * as jenkins from "./ci/jenkins.mjs";
|
|
15
|
+
import * as ciManual from "./ci/manual.mjs";
|
|
16
|
+
|
|
17
|
+
import * as ts from "./language/ts.mjs";
|
|
18
|
+
import * as js from "./language/js.mjs";
|
|
19
|
+
import * as py from "./language/py.mjs";
|
|
20
|
+
import * as go from "./language/go.mjs";
|
|
21
|
+
import * as rust from "./language/rust.mjs";
|
|
22
|
+
import * as java from "./language/java.mjs";
|
|
23
|
+
import * as kotlin from "./language/kotlin.mjs";
|
|
24
|
+
import * as swift from "./language/swift.mjs";
|
|
25
|
+
import * as dart from "./language/dart.mjs";
|
|
26
|
+
|
|
27
|
+
import { detectAllAgents } from "./agents/index.mjs";
|
|
28
|
+
|
|
29
|
+
/** @typedef {{id:string, kind:string, detect:(cwd:string)=>Promise<{present:boolean,confidence:number,evidence:Array}>}} Adapter */
|
|
30
|
+
|
|
31
|
+
export const trackers = Object.freeze({
|
|
32
|
+
linear,
|
|
33
|
+
jira,
|
|
34
|
+
"github-issues": githubIssues,
|
|
35
|
+
"azure-boards": azureBoards,
|
|
36
|
+
clickup,
|
|
37
|
+
spreadsheet,
|
|
38
|
+
none: trackerNone,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const ci = Object.freeze({
|
|
42
|
+
"gh-actions": ghActions,
|
|
43
|
+
"gitlab-ci": gitlabCi,
|
|
44
|
+
buildkite,
|
|
45
|
+
circleci,
|
|
46
|
+
"azure-pipelines": azurePipelines,
|
|
47
|
+
jenkins,
|
|
48
|
+
manual: ciManual,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
export const language = Object.freeze({
|
|
52
|
+
ts,
|
|
53
|
+
js,
|
|
54
|
+
py,
|
|
55
|
+
go,
|
|
56
|
+
rust,
|
|
57
|
+
java,
|
|
58
|
+
kotlin,
|
|
59
|
+
swift,
|
|
60
|
+
dart,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export { detectAllAgents };
|
|
64
|
+
|
|
65
|
+
function sortByConfidenceDesc(entries) {
|
|
66
|
+
return [...entries].sort((a, b) => b.confidence - a.confidence || a.id.localeCompare(b.id));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Run every adapter in every category against `cwd`, concurrently per
|
|
71
|
+
* category, and return findings sorted by confidence descending.
|
|
72
|
+
*
|
|
73
|
+
* @param {string} cwd
|
|
74
|
+
* @returns {Promise<{trackers:Array, ci:Array, language:Array, agents:Array}>}
|
|
75
|
+
*/
|
|
76
|
+
export async function detectAll(cwd) {
|
|
77
|
+
const runOne = async (registry) => {
|
|
78
|
+
const ids = Object.keys(registry);
|
|
79
|
+
const entries = await Promise.all(
|
|
80
|
+
ids.map(async (id) => {
|
|
81
|
+
const adapter = registry[id];
|
|
82
|
+
try {
|
|
83
|
+
const r = await adapter.detect(cwd);
|
|
84
|
+
return {
|
|
85
|
+
id,
|
|
86
|
+
present: !!r.present,
|
|
87
|
+
confidence: typeof r.confidence === "number" ? r.confidence : 0,
|
|
88
|
+
evidence: Array.isArray(r.evidence) ? r.evidence : [],
|
|
89
|
+
};
|
|
90
|
+
} catch (e) {
|
|
91
|
+
return {
|
|
92
|
+
id,
|
|
93
|
+
present: false,
|
|
94
|
+
confidence: 0,
|
|
95
|
+
evidence: [
|
|
96
|
+
{
|
|
97
|
+
type: "error",
|
|
98
|
+
where: "-",
|
|
99
|
+
match: String(e && e.message ? e.message : e),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
return sortByConfidenceDesc(entries);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const [trackerResults, ciResults, languageResults, agentResults] = await Promise.all([
|
|
110
|
+
runOne(trackers),
|
|
111
|
+
runOne(ci),
|
|
112
|
+
runOne(language),
|
|
113
|
+
detectAllAgents(cwd),
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
trackers: trackerResults,
|
|
118
|
+
ci: ciResults,
|
|
119
|
+
language: languageResults,
|
|
120
|
+
agents: sortByConfidenceDesc(agentResults),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "dart";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, "pubspec.yaml")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: "pubspec.yaml", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "go";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (isFile(cwd, "go.mod")) {
|
|
8
|
+
return {
|
|
9
|
+
present: true,
|
|
10
|
+
confidence: 1,
|
|
11
|
+
evidence: [{ type: "file", where: "go.mod", match: "present" }],
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function bootstrap() {
|
|
18
|
+
return { todo: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function verify() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { isFile } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "java";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
const evidence = [];
|
|
8
|
+
if (isFile(cwd, "pom.xml")) {
|
|
9
|
+
evidence.push({ type: "file", where: "pom.xml", match: "present" });
|
|
10
|
+
}
|
|
11
|
+
if (isFile(cwd, "build.gradle")) {
|
|
12
|
+
evidence.push({ type: "file", where: "build.gradle", match: "present" });
|
|
13
|
+
}
|
|
14
|
+
if (isFile(cwd, "build.gradle.kts")) {
|
|
15
|
+
evidence.push({ type: "file", where: "build.gradle.kts", match: "present" });
|
|
16
|
+
}
|
|
17
|
+
if (!evidence.length) return { present: false, confidence: 0, evidence };
|
|
18
|
+
return { present: true, confidence: 1, evidence };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function bootstrap() {
|
|
22
|
+
return { todo: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function verify() {
|
|
26
|
+
return { todo: true };
|
|
27
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isFile, pkgDeps, readJson } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "js";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
if (!isFile(cwd, "package.json")) {
|
|
8
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
9
|
+
}
|
|
10
|
+
const pkg = readJson(cwd, "package.json");
|
|
11
|
+
const deps = pkgDeps(pkg);
|
|
12
|
+
if (deps.typescript || isFile(cwd, "tsconfig.json")) {
|
|
13
|
+
return {
|
|
14
|
+
present: false,
|
|
15
|
+
confidence: 0,
|
|
16
|
+
evidence: [{ type: "file", where: "package.json", match: "typescript present → js skipped" }],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
present: true,
|
|
21
|
+
confidence: 0.9,
|
|
22
|
+
evidence: [{ type: "file", where: "package.json", match: "present (no typescript)" }],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function bootstrap() {
|
|
27
|
+
return { todo: true };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function verify() {
|
|
31
|
+
return { todo: true };
|
|
32
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isFile, readText, walk } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "kotlin";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
const evidence = [];
|
|
8
|
+
let strong = false;
|
|
9
|
+
|
|
10
|
+
if (isFile(cwd, "build.gradle.kts")) {
|
|
11
|
+
const body = readText(cwd, "build.gradle.kts") || "";
|
|
12
|
+
evidence.push({ type: "file", where: "build.gradle.kts", match: "present" });
|
|
13
|
+
if (/\bkotlin\b/i.test(body)) {
|
|
14
|
+
strong = true;
|
|
15
|
+
evidence.push({
|
|
16
|
+
type: "file",
|
|
17
|
+
where: "build.gradle.kts",
|
|
18
|
+
match: "kotlin plugin referenced",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const hasGradle =
|
|
24
|
+
isFile(cwd, "build.gradle") ||
|
|
25
|
+
isFile(cwd, "build.gradle.kts") ||
|
|
26
|
+
isFile(cwd, "settings.gradle") ||
|
|
27
|
+
isFile(cwd, "settings.gradle.kts");
|
|
28
|
+
if (hasGradle) {
|
|
29
|
+
const files = walk(cwd, { maxDepth: 4, maxFiles: 400 });
|
|
30
|
+
const kt = files.find((f) => f.endsWith(".kt"));
|
|
31
|
+
if (kt) evidence.push({ type: "file", where: kt, match: "*.kt present" });
|
|
32
|
+
if (kt && !strong) {
|
|
33
|
+
return { present: true, confidence: 0.7, evidence };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (strong) return { present: true, confidence: 1, evidence };
|
|
38
|
+
if (evidence.length) return { present: true, confidence: 0.6, evidence };
|
|
39
|
+
return { present: false, confidence: 0, evidence: [] };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function bootstrap() {
|
|
43
|
+
return { todo: true };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function verify() {
|
|
47
|
+
return { todo: true };
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isFile, listDir } from "../_fs.mjs";
|
|
2
|
+
|
|
3
|
+
export const id = "py";
|
|
4
|
+
export const kind = "language";
|
|
5
|
+
|
|
6
|
+
export async function detect(cwd) {
|
|
7
|
+
const evidence = [];
|
|
8
|
+
let hit = false;
|
|
9
|
+
|
|
10
|
+
if (isFile(cwd, "pyproject.toml")) {
|
|
11
|
+
hit = true;
|
|
12
|
+
evidence.push({ type: "file", where: "pyproject.toml", match: "present" });
|
|
13
|
+
}
|
|
14
|
+
if (isFile(cwd, "setup.py")) {
|
|
15
|
+
hit = true;
|
|
16
|
+
evidence.push({ type: "file", where: "setup.py", match: "present" });
|
|
17
|
+
}
|
|
18
|
+
for (const name of listDir(cwd, ".")) {
|
|
19
|
+
if (/^requirements.*\.txt$/.test(name) && isFile(cwd, name)) {
|
|
20
|
+
hit = true;
|
|
21
|
+
evidence.push({ type: "file", where: name, match: "present" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return { present: hit, confidence: hit ? 1 : 0, evidence };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function bootstrap() {
|
|
29
|
+
return { todo: true };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function verify() {
|
|
33
|
+
return { todo: true };
|
|
34
|
+
}
|