@elmundi/ship-cli 0.14.2 → 0.15.4
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 +17 -16
- package/bin/shipctl.mjs +4 -80
- package/lib/commands/feedback.mjs +1 -1
- package/lib/commands/help.mjs +47 -131
- package/lib/commands/init.mjs +17 -250
- package/lib/commands/knowledge.mjs +25 -328
- package/lib/commands/preflight.mjs +213 -0
- package/lib/commands/run.mjs +298 -119
- package/lib/commands/trigger.mjs +95 -10
- package/lib/config/schema.mjs +73 -11
- package/lib/http.mjs +0 -2
- package/lib/runtime/routines.mjs +39 -0
- package/lib/templates.mjs +2 -2
- package/lib/verify/checks/agents-on-disk.mjs +5 -28
- package/lib/verify/registry.mjs +7 -8
- package/package.json +1 -1
- package/lib/artifacts/fs-index.mjs +0 -230
- package/lib/cache/store.mjs +0 -422
- package/lib/commands/bootstrap.mjs +0 -4
- package/lib/commands/callback.mjs +0 -742
- package/lib/commands/docs.mjs +0 -90
- package/lib/commands/kickoff.mjs +0 -192
- package/lib/commands/lanes.mjs +0 -566
- package/lib/commands/manifest-catalog.mjs +0 -251
- package/lib/commands/migrate.mjs +0 -204
- package/lib/commands/new.mjs +0 -452
- package/lib/commands/patterns.mjs +0 -160
- package/lib/commands/process.mjs +0 -388
- package/lib/commands/search.mjs +0 -43
- package/lib/commands/sync.mjs +0 -824
- package/lib/config/migrate.mjs +0 -223
- package/lib/find-ship-root.mjs +0 -75
- package/lib/process/specialist-prompt-contract.mjs +0 -171
- package/lib/state/lockfile.mjs +0 -180
- package/lib/vendor/run-agent.workflow.yml +0 -254
- package/lib/verify/checks/artifacts-up-to-date.mjs +0 -78
- package/lib/verify/checks/cache-integrity.mjs +0 -51
- package/lib/verify/checks/gitignore-cache.mjs +0 -51
- package/lib/verify/checks/rules-markers.mjs +0 -135
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { apiGet, apiPost, fetchArtifact } from "../http.mjs";
|
|
3
|
-
import { resolveShipRepoRootForCatalog } from "../find-ship-root.mjs";
|
|
4
|
-
import { findShipRoot } from "../config/io.mjs";
|
|
5
|
-
import { writeCached, cachePath } from "../cache/store.mjs";
|
|
6
|
-
import { searchCommand } from "./search.mjs";
|
|
7
|
-
import { scanArtifacts, readArtifactFile, pluralFor } from "../artifacts/fs-index.mjs";
|
|
8
|
-
|
|
9
|
-
/** @type {Record<string, { arrayKey: string; name: string; apiPath: string; fetchKind: "tool"|"collection" }>} */
|
|
10
|
-
const RESOURCES = {
|
|
11
|
-
tool: {
|
|
12
|
-
arrayKey: "tools",
|
|
13
|
-
name: "Tools",
|
|
14
|
-
apiPath: "tools",
|
|
15
|
-
fetchKind: "tool",
|
|
16
|
-
},
|
|
17
|
-
collection: {
|
|
18
|
-
arrayKey: "collections",
|
|
19
|
-
name: "Collections",
|
|
20
|
-
apiPath: "collections",
|
|
21
|
-
fetchKind: "collection",
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @param {"tool"|"collection"} resource
|
|
27
|
-
* @param {{ baseUrl: string; json: boolean }} ctx
|
|
28
|
-
* @param {string[]} args
|
|
29
|
-
*/
|
|
30
|
-
export async function resourceManifestCommand(resource, ctx, args) {
|
|
31
|
-
const spec = RESOURCES[resource];
|
|
32
|
-
if (!spec) throw new Error(`Unknown resource: ${resource}`);
|
|
33
|
-
|
|
34
|
-
const [sub, ...rest] = args;
|
|
35
|
-
if (!sub || sub === "help") {
|
|
36
|
-
const plural = pluralFor(spec.fetchKind);
|
|
37
|
-
console.log(`Usage:
|
|
38
|
-
shipctl ${resource} list
|
|
39
|
-
shipctl ${resource} show <id>
|
|
40
|
-
shipctl ${resource} fetch <id> [--version V] [--print]
|
|
41
|
-
shipctl ${resource} search <query> [--top-k N]
|
|
42
|
-
|
|
43
|
-
With a local Ship tree (cwd or SHIP_REPO): scans artifacts/${plural}/<id>/ARTIFACT.md on disk.
|
|
44
|
-
Otherwise: methodology API (GET /${spec.apiPath}, POST /fetch for fetch, POST /search for search).
|
|
45
|
-
|
|
46
|
-
In a Ship workspace (.ship/config.yml), 'fetch' writes the artifact to
|
|
47
|
-
.ship/cache/<kind>/<id>@<version>/ARTIFACT.md and prints a 'cached:' line. Pass
|
|
48
|
-
--print to also echo the body on stdout.
|
|
49
|
-
|
|
50
|
-
Plural alias: shipctl ${spec.apiPath} …
|
|
51
|
-
|
|
52
|
-
Global flags: --base-url URL --json`);
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (sub === "search") {
|
|
57
|
-
await searchCommand(ctx, rest);
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const root = resolveShipRepoRootForCatalog();
|
|
62
|
-
if (root) {
|
|
63
|
-
await manifestFromDisk(resource, root, spec, ctx, sub, rest);
|
|
64
|
-
} else {
|
|
65
|
-
await manifestFromHosted(resource, spec, ctx, sub, rest);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Parse `fetch`-specific flags so the hosted catalog path can honour
|
|
71
|
-
* `--print`, `--version` and `--cwd` without polluting the global flag
|
|
72
|
-
* extractor. Unknown flags are silently preserved as positionals (no error),
|
|
73
|
-
* matching the rest of the manifest-catalog command.
|
|
74
|
-
* @param {string[]} rest
|
|
75
|
-
*/
|
|
76
|
-
function parseFetchFlags(rest) {
|
|
77
|
-
const out = { positional: /** @type {string[]} */ ([]), print: false, version: null, cwd: null };
|
|
78
|
-
const copy = [...rest];
|
|
79
|
-
while (copy.length) {
|
|
80
|
-
const a = copy.shift();
|
|
81
|
-
if (a === "--print") { out.print = true; continue; }
|
|
82
|
-
if (a === "--version" && copy.length) { out.version = copy.shift(); continue; }
|
|
83
|
-
if (a && a.startsWith("--version=")) { out.version = a.slice("--version=".length); continue; }
|
|
84
|
-
if (a === "--cwd" && copy.length) { out.cwd = copy.shift(); continue; }
|
|
85
|
-
if (a && a.startsWith("--cwd=")) { out.cwd = a.slice("--cwd=".length); continue; }
|
|
86
|
-
out.positional.push(a);
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* @param {"tool"|"collection"} resource
|
|
93
|
-
*/
|
|
94
|
-
async function manifestFromHosted(resource, spec, ctx, sub, rest) {
|
|
95
|
-
const base = ctx.baseUrl;
|
|
96
|
-
if (sub === "list") {
|
|
97
|
-
const data = await apiGet(base, `/${spec.apiPath}`);
|
|
98
|
-
if (ctx.json) {
|
|
99
|
-
console.log(JSON.stringify(data, null, 2));
|
|
100
|
-
} else {
|
|
101
|
-
const entries = /** @type {Array<{ id: string; title: string; path: string; tags?: string[] }>} */ (
|
|
102
|
-
data[spec.arrayKey] || []
|
|
103
|
-
);
|
|
104
|
-
console.log(`${data.description || spec.name}\n`);
|
|
105
|
-
for (const p of entries) {
|
|
106
|
-
console.log(`- ${p.id}`);
|
|
107
|
-
console.log(` ${p.title}`);
|
|
108
|
-
console.log(` path: ${p.path} tags: ${(p.tags || []).join(", ")}\n`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (sub === "show") {
|
|
114
|
-
const id = rest[0];
|
|
115
|
-
if (!id) {
|
|
116
|
-
console.error("show: id required.");
|
|
117
|
-
process.exit(1);
|
|
118
|
-
}
|
|
119
|
-
const data = await apiGet(base, `/${spec.apiPath}/${encodeURIComponent(id)}`);
|
|
120
|
-
if (ctx.json) {
|
|
121
|
-
console.log(JSON.stringify(data, null, 2));
|
|
122
|
-
} else {
|
|
123
|
-
console.log(`# ${data.title} (${data.id})\n`);
|
|
124
|
-
console.log(data.content);
|
|
125
|
-
}
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
if (sub === "fetch") {
|
|
129
|
-
const flags = parseFetchFlags(rest);
|
|
130
|
-
const id = flags.positional[0];
|
|
131
|
-
if (!id) {
|
|
132
|
-
console.error("fetch: id required.");
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
const shipRoot = findShipRoot(flags.cwd || process.cwd());
|
|
137
|
-
const wantCache = !!shipRoot;
|
|
138
|
-
const wantStdoutBody = flags.print || !wantCache || ctx.json;
|
|
139
|
-
|
|
140
|
-
if (wantCache) {
|
|
141
|
-
const { content, meta } = await fetchArtifact(
|
|
142
|
-
base,
|
|
143
|
-
spec.fetchKind,
|
|
144
|
-
id,
|
|
145
|
-
flags.version || undefined,
|
|
146
|
-
);
|
|
147
|
-
const version = meta.version || flags.version || "0.0.0";
|
|
148
|
-
const writeRes = writeCached(shipRoot, spec.fetchKind, id, version, content, {
|
|
149
|
-
content_sha256: meta.content_sha256,
|
|
150
|
-
updated_at: meta.updated_at,
|
|
151
|
-
channel: meta.channel,
|
|
152
|
-
version,
|
|
153
|
-
source_url: meta.source_url,
|
|
154
|
-
});
|
|
155
|
-
const rel = path.relative(shipRoot, writeRes.bodyPath) || cachePath(shipRoot, spec.fetchKind, id, version);
|
|
156
|
-
const relDisplay = path.isAbsolute(rel) ? writeRes.bodyPath : rel;
|
|
157
|
-
if (ctx.json) {
|
|
158
|
-
console.log(
|
|
159
|
-
JSON.stringify(
|
|
160
|
-
{
|
|
161
|
-
kind: spec.fetchKind,
|
|
162
|
-
id,
|
|
163
|
-
version,
|
|
164
|
-
content_sha256: meta.content_sha256,
|
|
165
|
-
cached_path: relDisplay,
|
|
166
|
-
content: wantStdoutBody ? content : undefined,
|
|
167
|
-
},
|
|
168
|
-
null,
|
|
169
|
-
2,
|
|
170
|
-
),
|
|
171
|
-
);
|
|
172
|
-
} else {
|
|
173
|
-
console.log(`cached: ${spec.fetchKind}/${id}@${version} \u2192 ${relDisplay}`);
|
|
174
|
-
if (wantStdoutBody) {
|
|
175
|
-
console.log(`# ${id}@${version}\n`);
|
|
176
|
-
console.log(content);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const data = await apiPost(base, "/fetch", { kind: spec.fetchKind, id, ...(flags.version ? { version: flags.version } : {}) });
|
|
183
|
-
if (ctx.json) {
|
|
184
|
-
console.log(JSON.stringify(data, null, 2));
|
|
185
|
-
} else {
|
|
186
|
-
console.error(
|
|
187
|
-
`note: not in a Ship workspace (no .ship/config.yml found); printing body only. Run 'shipctl config init' to enable caching.`,
|
|
188
|
-
);
|
|
189
|
-
console.log(`# ${data.title || data.id} (${data.id})\n`);
|
|
190
|
-
console.log(data.content);
|
|
191
|
-
}
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
console.error(`Unknown ${resource} subcommand: ${sub}`);
|
|
195
|
-
process.exit(1);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* @param {"tool"|"collection"} resource
|
|
200
|
-
*/
|
|
201
|
-
async function manifestFromDisk(resource, root, spec, ctx, sub, rest) {
|
|
202
|
-
const entries = scanArtifacts(root, spec.fetchKind);
|
|
203
|
-
|
|
204
|
-
if (sub === "list") {
|
|
205
|
-
if (ctx.json) {
|
|
206
|
-
const payload = {
|
|
207
|
-
description: spec.name,
|
|
208
|
-
version: 1,
|
|
209
|
-
[spec.arrayKey]: entries,
|
|
210
|
-
};
|
|
211
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
212
|
-
} else {
|
|
213
|
-
console.log(`${spec.name}\n`);
|
|
214
|
-
for (const p of entries) {
|
|
215
|
-
console.log(`- ${p.id}`);
|
|
216
|
-
console.log(` ${p.title}`);
|
|
217
|
-
console.log(` path: ${p.path} tags: ${(p.tags || []).join(", ")}\n`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (sub === "show" || sub === "fetch") {
|
|
224
|
-
const id = rest[0];
|
|
225
|
-
if (!id) {
|
|
226
|
-
console.error(`${sub}: id required.`);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
}
|
|
229
|
-
const entry = entries.find((e) => e.id === id);
|
|
230
|
-
if (!entry) {
|
|
231
|
-
console.error(`Unknown id: ${id}`);
|
|
232
|
-
process.exit(1);
|
|
233
|
-
}
|
|
234
|
-
const file = readArtifactFile(root, spec.fetchKind, id);
|
|
235
|
-
if (!file) {
|
|
236
|
-
console.error(`Missing file: ${entry.path}`);
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
const content = file.content;
|
|
240
|
-
if (ctx.json) {
|
|
241
|
-
console.log(JSON.stringify({ ...entry, content }, null, 2));
|
|
242
|
-
} else {
|
|
243
|
-
console.log(`# ${entry.title} (${entry.id})\n`);
|
|
244
|
-
console.log(content);
|
|
245
|
-
}
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.error(`Unknown ${resource} subcommand: ${sub}`);
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
package/lib/commands/migrate.mjs
DELETED
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `shipctl migrate` — upgrade `.ship/config.yml` from v1 to v2 (RFC-0007).
|
|
3
|
-
*
|
|
4
|
-
* The command is conservative:
|
|
5
|
-
* - Writes `.ship/config.yml.bak` before touching anything.
|
|
6
|
-
* - Refuses to overwrite without `--yes` unless `--dry-run` is set.
|
|
7
|
-
* - Emits a machine-readable summary under `--json` so adopters can
|
|
8
|
-
* wire it into their own migration runbooks.
|
|
9
|
-
*
|
|
10
|
-
* The actual migration rules live in `cli/lib/config/migrate.mjs`; this
|
|
11
|
-
* command is the I/O and UX layer.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import fs from "node:fs";
|
|
15
|
-
import path from "node:path";
|
|
16
|
-
|
|
17
|
-
import { readConfig, writeConfig } from "../config/io.mjs";
|
|
18
|
-
import { validateConfig } from "../config/schema.mjs";
|
|
19
|
-
import { migrateV1ToV2 } from "../config/migrate.mjs";
|
|
20
|
-
|
|
21
|
-
const EXIT_OK = 0;
|
|
22
|
-
const EXIT_USAGE = 2;
|
|
23
|
-
const EXIT_NOOP = 0;
|
|
24
|
-
const EXIT_VALIDATION = 1;
|
|
25
|
-
|
|
26
|
-
function printHelp() {
|
|
27
|
-
console.log(`shipctl migrate — upgrade .ship/config.yml to the current schema.
|
|
28
|
-
|
|
29
|
-
USAGE
|
|
30
|
-
shipctl migrate [--dry-run] [--yes] [--json] [--cwd <dir>]
|
|
31
|
-
|
|
32
|
-
FLAGS
|
|
33
|
-
--dry-run Print the proposed new config without writing to disk.
|
|
34
|
-
--yes Skip the interactive confirmation and overwrite in place
|
|
35
|
-
(a backup is always written to .ship/config.yml.bak first).
|
|
36
|
-
--json Emit a structured summary (path, backup, warnings, stubs).
|
|
37
|
-
--cwd <dir> Repo root (default: search upward for .ship/config.yml).
|
|
38
|
-
--help Show this help.
|
|
39
|
-
|
|
40
|
-
EXIT
|
|
41
|
-
0 migration applied (or already at the latest schema)
|
|
42
|
-
1 resulting config failed validation
|
|
43
|
-
2 argument / IO error
|
|
44
|
-
`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @param {{json?: boolean, yes?: boolean, dryRun?: boolean}} ctx
|
|
49
|
-
* @param {string[]} rest
|
|
50
|
-
*/
|
|
51
|
-
export async function migrateCommand(ctx, rest) {
|
|
52
|
-
const args = parseArgs(rest);
|
|
53
|
-
if (args.help) {
|
|
54
|
-
printHelp();
|
|
55
|
-
process.exit(EXIT_OK);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const cwd = args.cwd || process.cwd();
|
|
59
|
-
let read;
|
|
60
|
-
try {
|
|
61
|
-
read = readConfig(cwd);
|
|
62
|
-
} catch (err) {
|
|
63
|
-
die(EXIT_USAGE, err instanceof Error ? err.message : String(err));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { config: src, filePath } = read;
|
|
67
|
-
|
|
68
|
-
let result;
|
|
69
|
-
try {
|
|
70
|
-
result = migrateV1ToV2(src);
|
|
71
|
-
} catch (err) {
|
|
72
|
-
die(EXIT_USAGE, `migrate failed: ${err instanceof Error ? err.message : err}`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!result.migrated) {
|
|
76
|
-
const payload = {
|
|
77
|
-
path: filePath,
|
|
78
|
-
migrated: false,
|
|
79
|
-
warnings: result.warnings,
|
|
80
|
-
stub_lanes: [],
|
|
81
|
-
};
|
|
82
|
-
if (ctx.json || args.json) {
|
|
83
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
84
|
-
} else {
|
|
85
|
-
console.log(`${filePath}: already at the latest schema (no changes).`);
|
|
86
|
-
}
|
|
87
|
-
process.exit(EXIT_NOOP);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const validation = validateConfig(result.config);
|
|
91
|
-
if (!validation.ok) {
|
|
92
|
-
const msg = [
|
|
93
|
-
"migrate produced an invalid v2 config:",
|
|
94
|
-
...validation.errors.map((e) => ` - ${e}`),
|
|
95
|
-
...result.warnings.map((w) => ` (warn) ${w}`),
|
|
96
|
-
].join("\n");
|
|
97
|
-
die(EXIT_VALIDATION, msg);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const yes = ctx.yes || args.yes;
|
|
101
|
-
const dryRun = ctx.dryRun || args.dryRun;
|
|
102
|
-
const backupPath = `${filePath}.bak`;
|
|
103
|
-
|
|
104
|
-
if (dryRun || !yes) {
|
|
105
|
-
const summary = {
|
|
106
|
-
path: filePath,
|
|
107
|
-
migrated: true,
|
|
108
|
-
backup: backupPath,
|
|
109
|
-
dry_run: Boolean(dryRun),
|
|
110
|
-
warnings: result.warnings,
|
|
111
|
-
stub_lanes: result.stub_lanes,
|
|
112
|
-
};
|
|
113
|
-
if (ctx.json || args.json) {
|
|
114
|
-
console.log(
|
|
115
|
-
JSON.stringify(
|
|
116
|
-
{ ...summary, proposed_config: result.config },
|
|
117
|
-
null,
|
|
118
|
-
2,
|
|
119
|
-
),
|
|
120
|
-
);
|
|
121
|
-
} else {
|
|
122
|
-
console.log(`Proposed migration for ${filePath}:`);
|
|
123
|
-
for (const w of result.warnings) console.log(` - ${w}`);
|
|
124
|
-
if (result.stub_lanes.length) {
|
|
125
|
-
console.log(` - stub lanes (fill before shipping): ${result.stub_lanes.join(", ")}`);
|
|
126
|
-
}
|
|
127
|
-
console.log("");
|
|
128
|
-
console.log(serialiseForDisplay(result.config));
|
|
129
|
-
console.log("");
|
|
130
|
-
if (dryRun) {
|
|
131
|
-
console.log("--dry-run: not writing to disk.");
|
|
132
|
-
} else {
|
|
133
|
-
console.log("Re-run with --yes to apply the migration (writes .bak first).");
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
process.exit(EXIT_OK);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
fs.copyFileSync(filePath, backupPath);
|
|
141
|
-
writeConfig(filePath, result.config);
|
|
142
|
-
} catch (err) {
|
|
143
|
-
die(EXIT_USAGE, `migrate write failed: ${err instanceof Error ? err.message : err}`);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (ctx.json || args.json) {
|
|
147
|
-
console.log(
|
|
148
|
-
JSON.stringify(
|
|
149
|
-
{
|
|
150
|
-
path: filePath,
|
|
151
|
-
migrated: true,
|
|
152
|
-
backup: backupPath,
|
|
153
|
-
warnings: result.warnings,
|
|
154
|
-
stub_lanes: result.stub_lanes,
|
|
155
|
-
},
|
|
156
|
-
null,
|
|
157
|
-
2,
|
|
158
|
-
),
|
|
159
|
-
);
|
|
160
|
-
} else {
|
|
161
|
-
console.log(`Wrote ${filePath} (backup at ${backupPath}).`);
|
|
162
|
-
for (const w of result.warnings) console.log(` - ${w}`);
|
|
163
|
-
if (result.stub_lanes.length) {
|
|
164
|
-
console.log(
|
|
165
|
-
` - stub lanes to finish: ${result.stub_lanes.join(", ")} — edit the file or rerun 'shipctl init'.`,
|
|
166
|
-
);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
process.exit(EXIT_OK);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function parseArgs(rest) {
|
|
173
|
-
const out = { dryRun: false, yes: false, json: false, help: false, cwd: null };
|
|
174
|
-
const copy = [...rest];
|
|
175
|
-
while (copy.length) {
|
|
176
|
-
const a = copy.shift();
|
|
177
|
-
if (a === "--help" || a === "-h") out.help = true;
|
|
178
|
-
else if (a === "--dry-run") out.dryRun = true;
|
|
179
|
-
else if (a === "--yes") out.yes = true;
|
|
180
|
-
else if (a === "--json") out.json = true;
|
|
181
|
-
else if (a === "--cwd" && copy[0] !== undefined) out.cwd = path.resolve(String(copy.shift()));
|
|
182
|
-
else if (a && a.startsWith("--cwd=")) out.cwd = path.resolve(a.slice("--cwd=".length));
|
|
183
|
-
else {
|
|
184
|
-
console.error(`unknown argument: ${a}\nRun: shipctl migrate --help`);
|
|
185
|
-
process.exit(EXIT_USAGE);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return out;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Render the config as YAML-ish for display. We deliberately don't
|
|
193
|
-
* import the YAML module here — the real write path already
|
|
194
|
-
* normalises, and `--dry-run` is human-scan territory. JSON is good
|
|
195
|
-
* enough and shows every field unambiguously.
|
|
196
|
-
*/
|
|
197
|
-
function serialiseForDisplay(config) {
|
|
198
|
-
return JSON.stringify(config, null, 2);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function die(code, msg) {
|
|
202
|
-
console.error(msg);
|
|
203
|
-
process.exit(code);
|
|
204
|
-
}
|