@elmundi/ship-cli 0.8.1 → 0.12.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/README.md +651 -25
- package/bin/shipctl.mjs +168 -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 +422 -0
- package/lib/cache/store.mjs +422 -0
- package/lib/commands/bootstrap.mjs +4 -0
- package/lib/commands/callback.mjs +742 -0
- package/lib/commands/config.mjs +257 -0
- package/lib/commands/docs.mjs +4 -4
- package/lib/commands/doctor.mjs +583 -0
- package/lib/commands/feedback.mjs +355 -0
- package/lib/commands/help.mjs +159 -24
- package/lib/commands/init.mjs +830 -158
- package/lib/commands/kickoff.mjs +192 -0
- package/lib/commands/knowledge.mjs +562 -0
- package/lib/commands/lanes.mjs +527 -0
- package/lib/commands/manifest-catalog.mjs +106 -42
- package/lib/commands/migrate.mjs +204 -0
- package/lib/commands/new.mjs +452 -0
- package/lib/commands/patterns.mjs +14 -48
- package/lib/commands/run.mjs +857 -0
- package/lib/commands/search.mjs +2 -2
- package/lib/commands/sync.mjs +824 -0
- package/lib/commands/telemetry.mjs +390 -0
- package/lib/commands/trigger.mjs +196 -0
- package/lib/commands/verify.mjs +187 -0
- package/lib/config/io.mjs +232 -0
- package/lib/config/migrate.mjs +223 -0
- package/lib/config/schema.mjs +901 -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,257 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_CONFIG,
|
|
6
|
+
ensureAnonymousId,
|
|
7
|
+
findShipRoot,
|
|
8
|
+
readConfig,
|
|
9
|
+
writeConfig,
|
|
10
|
+
writeState,
|
|
11
|
+
defaultState,
|
|
12
|
+
SHIP_DIR,
|
|
13
|
+
CONFIG_REL,
|
|
14
|
+
STATE_REL,
|
|
15
|
+
} from "../config/io.mjs";
|
|
16
|
+
import { validateConfig } from "../config/schema.mjs";
|
|
17
|
+
|
|
18
|
+
function parseConfigArgs(rest) {
|
|
19
|
+
const out = { cwd: null, positional: [] };
|
|
20
|
+
const copy = [...rest];
|
|
21
|
+
while (copy.length) {
|
|
22
|
+
const a = copy[0];
|
|
23
|
+
if (a === "--cwd" && copy[1]) {
|
|
24
|
+
copy.shift();
|
|
25
|
+
out.cwd = String(copy.shift());
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (a.startsWith("--cwd=")) {
|
|
29
|
+
out.cwd = a.slice("--cwd=".length);
|
|
30
|
+
copy.shift();
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
out.positional.push(copy.shift());
|
|
34
|
+
}
|
|
35
|
+
out.cwd = out.cwd || process.cwd();
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureGitignoreEntry(shipRoot) {
|
|
40
|
+
const giPath = path.join(shipRoot, ".gitignore");
|
|
41
|
+
const entries = [
|
|
42
|
+
"# Ship",
|
|
43
|
+
".ship/cache/",
|
|
44
|
+
".ship/telemetry-outbox.jsonl",
|
|
45
|
+
".ship/feedback-drafts/",
|
|
46
|
+
".ship/state.json",
|
|
47
|
+
];
|
|
48
|
+
let current = "";
|
|
49
|
+
if (fs.existsSync(giPath)) current = fs.readFileSync(giPath, "utf8");
|
|
50
|
+
const existingLines = new Set(current.split(/\r?\n/).map((l) => l.trim()));
|
|
51
|
+
const toAppend = entries.filter((e) => !existingLines.has(e.trim()));
|
|
52
|
+
if (toAppend.length === 0) return { giPath, changed: false };
|
|
53
|
+
const prefix = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
54
|
+
const tail = current.length === 0 ? `${toAppend.join("\n")}\n` : `${prefix}${toAppend.join("\n")}\n`;
|
|
55
|
+
fs.writeFileSync(giPath, current + tail, "utf8");
|
|
56
|
+
return { giPath, changed: true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function initCmd(rest) {
|
|
60
|
+
const { cwd } = parseConfigArgs(rest);
|
|
61
|
+
const root = path.resolve(cwd);
|
|
62
|
+
const shipDir = path.join(root, SHIP_DIR);
|
|
63
|
+
const filePath = path.join(root, CONFIG_REL);
|
|
64
|
+
|
|
65
|
+
if (fs.existsSync(filePath)) {
|
|
66
|
+
console.error(`exists: ${filePath}`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fs.mkdirSync(shipDir, { recursive: true });
|
|
71
|
+
const config = DEFAULT_CONFIG();
|
|
72
|
+
ensureAnonymousId(config);
|
|
73
|
+
|
|
74
|
+
writeConfig(filePath, config);
|
|
75
|
+
writeState(root, defaultState());
|
|
76
|
+
|
|
77
|
+
const cacheDir = path.join(shipDir, "cache");
|
|
78
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
79
|
+
const keep = path.join(cacheDir, ".gitkeep");
|
|
80
|
+
if (!fs.existsSync(keep)) fs.writeFileSync(keep, "", "utf8");
|
|
81
|
+
|
|
82
|
+
const { giPath, changed } = ensureGitignoreEntry(root);
|
|
83
|
+
|
|
84
|
+
console.log(`created: ${filePath}`);
|
|
85
|
+
console.log(`created: ${path.join(root, STATE_REL)}`);
|
|
86
|
+
console.log(`created: ${cacheDir}/`);
|
|
87
|
+
console.log(`${changed ? "updated" : "ok "}: ${giPath}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getAtPath(obj, dottedKey) {
|
|
91
|
+
const parts = parsePath(dottedKey);
|
|
92
|
+
let cur = obj;
|
|
93
|
+
for (const p of parts) {
|
|
94
|
+
if (cur == null) return undefined;
|
|
95
|
+
if (typeof cur !== "object") return undefined;
|
|
96
|
+
cur = cur[p];
|
|
97
|
+
}
|
|
98
|
+
return cur;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Split a dotted key, preserving `<kind>/<id>` segments under artifacts.pins.
|
|
103
|
+
* Example: artifacts.pins.pattern/role-developer → ["artifacts","pins","pattern/role-developer"]
|
|
104
|
+
*/
|
|
105
|
+
function parsePath(dottedKey) {
|
|
106
|
+
const raw = dottedKey.split(".");
|
|
107
|
+
const out = [];
|
|
108
|
+
for (let i = 0; i < raw.length; i++) {
|
|
109
|
+
if (
|
|
110
|
+
out.length === 2 &&
|
|
111
|
+
out[0] === "artifacts" &&
|
|
112
|
+
out[1] === "pins"
|
|
113
|
+
) {
|
|
114
|
+
out.push(raw.slice(i).join("."));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
out.push(raw[i]);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function setAtPath(obj, dottedKey, value) {
|
|
123
|
+
const parts = parsePath(dottedKey);
|
|
124
|
+
let cur = obj;
|
|
125
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
126
|
+
const p = parts[i];
|
|
127
|
+
if (cur[p] == null || typeof cur[p] !== "object") cur[p] = {};
|
|
128
|
+
cur = cur[p];
|
|
129
|
+
}
|
|
130
|
+
cur[parts[parts.length - 1]] = value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function parseValue(raw) {
|
|
134
|
+
if (raw === "true") return true;
|
|
135
|
+
if (raw === "false") return false;
|
|
136
|
+
if (raw === "null") return null;
|
|
137
|
+
if (/^-?\d+$/.test(raw)) return Number(raw);
|
|
138
|
+
if (/^-?\d+\.\d+$/.test(raw)) return Number(raw);
|
|
139
|
+
const trimmed = raw.trim();
|
|
140
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
141
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
142
|
+
if (inner.length === 0) return [];
|
|
143
|
+
return inner.split(",").map((x) => parseValue(x.trim().replace(/^['"]|['"]$/g, "")));
|
|
144
|
+
}
|
|
145
|
+
if (
|
|
146
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
147
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
148
|
+
) {
|
|
149
|
+
return trimmed.slice(1, -1);
|
|
150
|
+
}
|
|
151
|
+
return raw;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function getCmd(rest) {
|
|
155
|
+
const { cwd, positional } = parseConfigArgs(rest);
|
|
156
|
+
const key = positional[0];
|
|
157
|
+
if (!key) {
|
|
158
|
+
console.error("usage: shipctl config get <dotted.key>");
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const { config } = readConfig(cwd);
|
|
162
|
+
const val = getAtPath(config, key);
|
|
163
|
+
if (val === undefined) {
|
|
164
|
+
console.error(`unknown key: ${key}`);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(val) || (val !== null && typeof val === "object")) {
|
|
168
|
+
console.log(JSON.stringify(val));
|
|
169
|
+
} else {
|
|
170
|
+
console.log(String(val));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function setCmd(rest) {
|
|
175
|
+
const { cwd, positional } = parseConfigArgs(rest);
|
|
176
|
+
const [key, ...valueParts] = positional;
|
|
177
|
+
if (!key || valueParts.length === 0) {
|
|
178
|
+
console.error("usage: shipctl config set <dotted.key> <value>");
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
const raw = valueParts.join(" ");
|
|
182
|
+
const value = parseValue(raw);
|
|
183
|
+
const { config, filePath } = readConfig(cwd);
|
|
184
|
+
setAtPath(config, key, value);
|
|
185
|
+
|
|
186
|
+
const res = validateConfig(config);
|
|
187
|
+
if (!res.ok) {
|
|
188
|
+
for (const e of res.errors) console.error(e);
|
|
189
|
+
process.exit(10);
|
|
190
|
+
}
|
|
191
|
+
for (const w of res.warnings) console.error(`warn: ${w}`);
|
|
192
|
+
|
|
193
|
+
writeConfig(filePath, config);
|
|
194
|
+
console.log(`${key} = ${JSON.stringify(value)}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function validateCmd(rest) {
|
|
198
|
+
const { cwd } = parseConfigArgs(rest);
|
|
199
|
+
const { config, filePath } = readConfig(cwd);
|
|
200
|
+
const res = validateConfig(config);
|
|
201
|
+
for (const w of res.warnings) console.error(`warn: ${w}`);
|
|
202
|
+
if (!res.ok) {
|
|
203
|
+
for (const e of res.errors) console.error(e);
|
|
204
|
+
process.exit(10);
|
|
205
|
+
}
|
|
206
|
+
console.log(`ok: ${filePath}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function showCmd(rest) {
|
|
210
|
+
const { cwd } = parseConfigArgs(rest);
|
|
211
|
+
const { config } = readConfig(cwd);
|
|
212
|
+
process.stdout.write(YAML.stringify(config, { lineWidth: 0, indent: 2 }));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function pathCmd(rest) {
|
|
216
|
+
const { cwd } = parseConfigArgs(rest);
|
|
217
|
+
const root = findShipRoot(cwd);
|
|
218
|
+
if (!root) {
|
|
219
|
+
console.log("not found");
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
console.log(path.join(root, CONFIG_REL));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function configCommand(_ctx, rest) {
|
|
226
|
+
const [sub, ...tail] = rest;
|
|
227
|
+
if (!sub || sub === "-h" || sub === "--help" || sub === "help") {
|
|
228
|
+
console.log(`shipctl config <subcommand>
|
|
229
|
+
|
|
230
|
+
Subcommands:
|
|
231
|
+
init [--cwd DIR] Create .ship/config.yml + state.json + cache/.
|
|
232
|
+
get <dotted.key> Print value.
|
|
233
|
+
set <dotted.key> <value> Update value (validates; atomic write).
|
|
234
|
+
validate Validate .ship/config.yml; exit 10 on errors.
|
|
235
|
+
show Pretty-print effective YAML.
|
|
236
|
+
path Print absolute path to config file.
|
|
237
|
+
`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
switch (sub) {
|
|
241
|
+
case "init":
|
|
242
|
+
return initCmd(tail);
|
|
243
|
+
case "get":
|
|
244
|
+
return getCmd(tail);
|
|
245
|
+
case "set":
|
|
246
|
+
return setCmd(tail);
|
|
247
|
+
case "validate":
|
|
248
|
+
return validateCmd(tail);
|
|
249
|
+
case "show":
|
|
250
|
+
return showCmd(tail);
|
|
251
|
+
case "path":
|
|
252
|
+
return pathCmd(tail);
|
|
253
|
+
default:
|
|
254
|
+
console.error(`unknown subcommand: config ${sub}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
}
|
package/lib/commands/docs.mjs
CHANGED
|
@@ -9,11 +9,11 @@ export async function docsCommand(ctx, args) {
|
|
|
9
9
|
const [sub, ...rest] = args;
|
|
10
10
|
if (!sub || sub === "help") {
|
|
11
11
|
console.log(`Usage:
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
shipctl docs fetch <repo-relative-path>
|
|
13
|
+
shipctl docs feedback --title "..." --summary "..." [--recommendation "line"]... [--source-context "..."]
|
|
14
14
|
|
|
15
|
-
Vector search:
|
|
16
|
-
Catalog bodies:
|
|
15
|
+
Vector search: shipctl search <query>
|
|
16
|
+
Catalog bodies: shipctl pattern|tool|collection fetch <id>
|
|
17
17
|
|
|
18
18
|
Global flags: --base-url URL --json`);
|
|
19
19
|
return;
|