@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,373 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Bootstrap renderer for `shipctl init --bootstrap`.
|
|
6
|
+
*
|
|
7
|
+
* This is intentionally a v1: full preset-body interpretation (parsing
|
|
8
|
+
* `## Bootstrap (files to write)` blocks from adapter artifacts) is TODO
|
|
9
|
+
* and tracked as a templating engine in RFC-0004. For now we:
|
|
10
|
+
*
|
|
11
|
+
* - Always emit a SHIP_BOOTSTRAP_PLAN.md summary so the user has a
|
|
12
|
+
* single actionable next-step document.
|
|
13
|
+
* - For the common `mobile-app + gh-actions + linear` triple we also
|
|
14
|
+
* write minimal CI workflow skeleton, label contract YAML, and
|
|
15
|
+
* `.env.example` placeholders. Other combos fall back to plan-only.
|
|
16
|
+
*
|
|
17
|
+
* @typedef {Object} PlanFile
|
|
18
|
+
* @property {string} path Relative to cwd.
|
|
19
|
+
* @property {string} content
|
|
20
|
+
* @property {"create"|"append"|"patch"} mode
|
|
21
|
+
*
|
|
22
|
+
* @typedef {Object} PlanSummary
|
|
23
|
+
* @property {string[]} notes
|
|
24
|
+
* @property {Array<{path:string, mode:string, detail?:string}>} files
|
|
25
|
+
*
|
|
26
|
+
* @typedef {Object} RenderedPlan
|
|
27
|
+
* @property {PlanFile[]} files
|
|
28
|
+
* @property {PlanSummary} summary
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const MOBILE_LABELS = [
|
|
32
|
+
"platform:ios",
|
|
33
|
+
"platform:android",
|
|
34
|
+
"store:review",
|
|
35
|
+
"flag:behind",
|
|
36
|
+
"flag:ahead",
|
|
37
|
+
"change-record",
|
|
38
|
+
"blocked",
|
|
39
|
+
"preview:ready",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const ENV_EXAMPLE_MARKER_START = "# --- ship-managed ---";
|
|
43
|
+
const ENV_EXAMPLE_MARKER_END = "# --- end ship-managed ---";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {object} cfg
|
|
47
|
+
* @returns {RenderedPlan}
|
|
48
|
+
*/
|
|
49
|
+
export function renderMobileAppGhActionsLinear(cfg) {
|
|
50
|
+
const preset = cfg.stack?.preset || "mobile-app";
|
|
51
|
+
const tracker = cfg.stack?.tracker || "linear";
|
|
52
|
+
const ci = cfg.stack?.ci || "gh-actions";
|
|
53
|
+
const agents = Array.isArray(cfg.stack?.agents) ? cfg.stack.agents : [];
|
|
54
|
+
|
|
55
|
+
const workflow = `# ship-managed: workflow
|
|
56
|
+
# Skeleton written by \`shipctl init --bootstrap\`.
|
|
57
|
+
# shipctl sync (Epic 7) will fill in job bodies from preset:preset-${preset}.
|
|
58
|
+
name: ship-pilot
|
|
59
|
+
on:
|
|
60
|
+
pull_request:
|
|
61
|
+
push:
|
|
62
|
+
branches: [main]
|
|
63
|
+
|
|
64
|
+
jobs:
|
|
65
|
+
# ship-managed: workflow
|
|
66
|
+
lint:
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
# TODO: language-specific lint wired by shipctl sync
|
|
71
|
+
- run: echo "lint: placeholder"
|
|
72
|
+
|
|
73
|
+
# ship-managed: workflow
|
|
74
|
+
build-ios:
|
|
75
|
+
runs-on: macos-latest
|
|
76
|
+
steps:
|
|
77
|
+
- uses: actions/checkout@v4
|
|
78
|
+
# TODO: EAS / Fastlane build steps wired by shipctl sync
|
|
79
|
+
- run: echo "build-ios: placeholder"
|
|
80
|
+
|
|
81
|
+
# ship-managed: workflow
|
|
82
|
+
build-android:
|
|
83
|
+
runs-on: ubuntu-latest
|
|
84
|
+
steps:
|
|
85
|
+
- uses: actions/checkout@v4
|
|
86
|
+
# TODO: Gradle / EAS build steps wired by shipctl sync
|
|
87
|
+
- run: echo "build-android: placeholder"
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const labelsYml = `# ship-managed: labels
|
|
91
|
+
# Synced to the tracker (${tracker}) by \`shipctl verify\`.
|
|
92
|
+
version: 1
|
|
93
|
+
preset: ${preset}
|
|
94
|
+
labels:
|
|
95
|
+
${MOBILE_LABELS.map((l) => ` - name: "${l}"`).join("\n")}
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
const envBlock = `${ENV_EXAMPLE_MARKER_START}
|
|
99
|
+
# Placeholders for ${preset} / ${tracker} / ${ci}.
|
|
100
|
+
# Fill these in .env.local (not committed) or your platform secret store.
|
|
101
|
+
LINEAR_API_KEY=
|
|
102
|
+
LINEAR_TEAM_ID=
|
|
103
|
+
GITHUB_TOKEN=
|
|
104
|
+
EXPO_TOKEN=
|
|
105
|
+
SENTRY_AUTH_TOKEN=
|
|
106
|
+
${ENV_EXAMPLE_MARKER_END}
|
|
107
|
+
`;
|
|
108
|
+
|
|
109
|
+
const plan = renderAdoptionMinimum(cfg, {
|
|
110
|
+
extraNotes: [
|
|
111
|
+
"Mobile-app pilot scaffolding was emitted (gh-actions + linear).",
|
|
112
|
+
"See `.github/workflows/ship-pilot.yml`, `.ship/labels.yml`, `.env.example`.",
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const files = [
|
|
117
|
+
...plan.files,
|
|
118
|
+
{
|
|
119
|
+
path: ".github/workflows/ship-pilot.yml",
|
|
120
|
+
content: workflow,
|
|
121
|
+
mode: /** @type {"create"} */ ("create"),
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
path: ".ship/labels.yml",
|
|
125
|
+
content: labelsYml,
|
|
126
|
+
mode: /** @type {"create"} */ ("create"),
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: ".env.example",
|
|
130
|
+
content: envBlock,
|
|
131
|
+
mode: /** @type {"append"} */ ("append"),
|
|
132
|
+
},
|
|
133
|
+
];
|
|
134
|
+
|
|
135
|
+
const summary = {
|
|
136
|
+
notes: [
|
|
137
|
+
...plan.summary.notes,
|
|
138
|
+
`bootstrap: mobile-app + ${ci} + ${tracker} triple rendered`,
|
|
139
|
+
`agents: ${agents.join(", ") || "(none)"}`,
|
|
140
|
+
],
|
|
141
|
+
files: files.map((f) => ({
|
|
142
|
+
path: f.path,
|
|
143
|
+
mode: f.mode,
|
|
144
|
+
detail:
|
|
145
|
+
f.path === ".ship/labels.yml"
|
|
146
|
+
? `${MOBILE_LABELS.length} labels`
|
|
147
|
+
: f.path === ".env.example"
|
|
148
|
+
? "5 placeholders"
|
|
149
|
+
: undefined,
|
|
150
|
+
})),
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return { files, summary };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Generate just the SHIP_BOOTSTRAP_PLAN.md summary. Used as the v1
|
|
158
|
+
* fallback for preset / CI / tracker combos that don't have a specific
|
|
159
|
+
* renderer yet.
|
|
160
|
+
*
|
|
161
|
+
* @param {object} cfg
|
|
162
|
+
* @param {{extraNotes?:string[]}} [opts]
|
|
163
|
+
* @returns {RenderedPlan}
|
|
164
|
+
*/
|
|
165
|
+
export function renderAdoptionMinimum(cfg, opts = {}) {
|
|
166
|
+
const stack = cfg.stack || {};
|
|
167
|
+
const preset = stack.preset || "adoption-minimum";
|
|
168
|
+
const tracker = stack.tracker || "none";
|
|
169
|
+
const ci = stack.ci || "manual";
|
|
170
|
+
const agents = Array.isArray(stack.agents) ? stack.agents : [];
|
|
171
|
+
const language = stack.language || "multi";
|
|
172
|
+
const channel = cfg.api?.channel || "stable";
|
|
173
|
+
const telemetry = cfg.telemetry?.share === true ? "on" : "off";
|
|
174
|
+
const extraNotes = opts.extraNotes || [];
|
|
175
|
+
|
|
176
|
+
const todos = buildTodoList({ preset, ci, tracker, agents });
|
|
177
|
+
const recommendedTools = buildRecommendedTools({ preset });
|
|
178
|
+
const recommendedSecrets = buildRecommendedSecrets({ tracker, ci });
|
|
179
|
+
|
|
180
|
+
const body = `# Ship bootstrap plan
|
|
181
|
+
|
|
182
|
+
_Generated by \`shipctl init --bootstrap\` on ${new Date().toISOString()}._
|
|
183
|
+
|
|
184
|
+
## Chosen stack
|
|
185
|
+
|
|
186
|
+
- **preset**: \`${preset}\`
|
|
187
|
+
- **tracker**: \`${tracker}\`
|
|
188
|
+
- **ci**: \`${ci}\`
|
|
189
|
+
- **language**: \`${language}\`
|
|
190
|
+
- **agents**: ${agents.length ? agents.map((a) => `\`${a}\``).join(", ") : "_(none)_"}
|
|
191
|
+
- **channel**: \`${channel}\`
|
|
192
|
+
- **telemetry**: \`${telemetry}\`
|
|
193
|
+
|
|
194
|
+
## Recommended tools
|
|
195
|
+
|
|
196
|
+
${recommendedTools.map((t) => `- ${t}`).join("\n") || "_(none for this preset yet — fill manually.)_"}
|
|
197
|
+
|
|
198
|
+
## Recommended secrets / env
|
|
199
|
+
|
|
200
|
+
${recommendedSecrets.map((s) => `- \`${s}\``).join("\n") || "_(none required.)_"}
|
|
201
|
+
|
|
202
|
+
## Files to create / review
|
|
203
|
+
|
|
204
|
+
${todos.map((t) => `- [ ] ${t}`).join("\n")}
|
|
205
|
+
|
|
206
|
+
## Next steps
|
|
207
|
+
|
|
208
|
+
1. \`shipctl sync\` to refresh \`.ship/cache/\` against the Ship API.
|
|
209
|
+
2. \`shipctl verify\` to confirm tracker labels / CI secrets / rules markers.
|
|
210
|
+
3. Open the preset artifact for full details:
|
|
211
|
+
\`shipctl collection show preset-${preset}\`.
|
|
212
|
+
|
|
213
|
+
${
|
|
214
|
+
extraNotes.length
|
|
215
|
+
? `## Notes\n\n${extraNotes.map((n) => `- ${n}`).join("\n")}\n`
|
|
216
|
+
: ""
|
|
217
|
+
}`;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
files: [
|
|
221
|
+
{
|
|
222
|
+
path: "SHIP_BOOTSTRAP_PLAN.md",
|
|
223
|
+
content: body,
|
|
224
|
+
mode: /** @type {"create"} */ ("create"),
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
summary: {
|
|
228
|
+
notes: ["bootstrap: plan-only fallback rendered (SHIP_BOOTSTRAP_PLAN.md)"],
|
|
229
|
+
files: [
|
|
230
|
+
{
|
|
231
|
+
path: "SHIP_BOOTSTRAP_PLAN.md",
|
|
232
|
+
mode: "create",
|
|
233
|
+
detail: `${todos.length} todo items`,
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function buildTodoList({ preset, ci, tracker, agents }) {
|
|
241
|
+
const todos = [];
|
|
242
|
+
if (ci === "gh-actions") {
|
|
243
|
+
todos.push("Confirm `.github/workflows/ship-pilot.yml` skeleton (shipctl sync will fill the job bodies).");
|
|
244
|
+
} else {
|
|
245
|
+
todos.push(`Author the CI workflow skeleton for \`${ci}\` manually (no renderer yet).`);
|
|
246
|
+
}
|
|
247
|
+
if (tracker !== "none") {
|
|
248
|
+
todos.push(`Create the label contract for \`${tracker}\` (see preset:preset-${preset} for the label set).`);
|
|
249
|
+
}
|
|
250
|
+
for (const a of agents) {
|
|
251
|
+
todos.push(`Agent rules for \`${a}\`: install via \`shipctl init --copy-rules --agents ${a}\`.`);
|
|
252
|
+
}
|
|
253
|
+
todos.push("Populate `.env.example` / secret store with the secrets listed above.");
|
|
254
|
+
todos.push("Run `shipctl verify` after the above to confirm the stack.");
|
|
255
|
+
return todos;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function buildRecommendedTools({ preset }) {
|
|
259
|
+
const common = ["`shipctl doctor` — inspect repo and reconcile stack"];
|
|
260
|
+
const byPreset = {
|
|
261
|
+
"mobile-app": [
|
|
262
|
+
"EAS Build / Fastlane for iOS + Android signed builds",
|
|
263
|
+
"Detox or Maestro for device-farm E2E",
|
|
264
|
+
"Expo Updates or CodePush for OTA patches",
|
|
265
|
+
],
|
|
266
|
+
"web-app": [
|
|
267
|
+
"Playwright (hosted) for PR preview E2E",
|
|
268
|
+
"Preview deployments (Vercel / Netlify / Fly) per PR",
|
|
269
|
+
],
|
|
270
|
+
"api-backend": [
|
|
271
|
+
"Contract tests (Pact / OpenAPI diff)",
|
|
272
|
+
"Migration discipline (Atlas / Liquibase)",
|
|
273
|
+
],
|
|
274
|
+
cli: ["Cross-platform release matrix (GoReleaser / pkg / esbuild)"],
|
|
275
|
+
monorepo: ["Turborepo / Nx / pnpm workspaces for per-package CI"],
|
|
276
|
+
"adoption-minimum": [],
|
|
277
|
+
};
|
|
278
|
+
return [...common, ...(byPreset[preset] || [])];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function buildRecommendedSecrets({ tracker, ci }) {
|
|
282
|
+
const secrets = new Set();
|
|
283
|
+
if (tracker === "linear") secrets.add("LINEAR_API_KEY").add("LINEAR_TEAM_ID");
|
|
284
|
+
if (tracker === "jira") secrets.add("JIRA_API_TOKEN").add("JIRA_EMAIL");
|
|
285
|
+
if (tracker === "github-issues") secrets.add("GITHUB_TOKEN");
|
|
286
|
+
if (ci === "gh-actions") secrets.add("GITHUB_TOKEN");
|
|
287
|
+
if (ci === "circleci") secrets.add("CIRCLE_TOKEN");
|
|
288
|
+
return [...secrets];
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Pick the right renderer for this stack. v1 only special-cases
|
|
293
|
+
* `mobile-app + gh-actions + linear`.
|
|
294
|
+
*
|
|
295
|
+
* @param {object} cfg
|
|
296
|
+
* @returns {RenderedPlan}
|
|
297
|
+
*/
|
|
298
|
+
export function renderPlan(cfg /*, presetArtifact */) {
|
|
299
|
+
const preset = cfg.stack?.preset;
|
|
300
|
+
const tracker = cfg.stack?.tracker;
|
|
301
|
+
const ci = cfg.stack?.ci;
|
|
302
|
+
|
|
303
|
+
if (preset === "mobile-app" && ci === "gh-actions" && tracker === "linear") {
|
|
304
|
+
return renderMobileAppGhActionsLinear(cfg);
|
|
305
|
+
}
|
|
306
|
+
return renderAdoptionMinimum(cfg);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Apply a plan to disk. Append-mode files use marker-guarded idempotency.
|
|
311
|
+
* Create-mode files are skipped when they already exist unless `force`
|
|
312
|
+
* is set (we never silently stomp a user's file).
|
|
313
|
+
*
|
|
314
|
+
* @param {string} cwd
|
|
315
|
+
* @param {RenderedPlan} plan
|
|
316
|
+
* @param {{dryRun?:boolean, force?:boolean}} [opts]
|
|
317
|
+
* @returns {Array<{path:string, action:"wrote"|"skipped"|"appended"|"would_write"|"would_skip"|"would_append"}>}
|
|
318
|
+
*/
|
|
319
|
+
export function applyPlan(cwd, plan, opts = {}) {
|
|
320
|
+
const { dryRun = false, force = false } = opts;
|
|
321
|
+
/** @type {Array<{path:string, action:string}>} */
|
|
322
|
+
const results = [];
|
|
323
|
+
|
|
324
|
+
for (const file of plan.files) {
|
|
325
|
+
const abs = path.join(cwd, file.path);
|
|
326
|
+
|
|
327
|
+
if (file.mode === "append") {
|
|
328
|
+
const current = fs.existsSync(abs) ? fs.readFileSync(abs, "utf8") : "";
|
|
329
|
+
if (current.includes(ENV_EXAMPLE_MARKER_START)) {
|
|
330
|
+
results.push({ path: file.path, action: dryRun ? "would_skip" : "skipped" });
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (dryRun) {
|
|
334
|
+
results.push({ path: file.path, action: "would_append" });
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
338
|
+
const prefix = current.length && !current.endsWith("\n") ? "\n" : "";
|
|
339
|
+
fs.writeFileSync(abs, current + prefix + file.content, "utf8");
|
|
340
|
+
results.push({ path: file.path, action: "appended" });
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (fs.existsSync(abs) && !force) {
|
|
345
|
+
results.push({ path: file.path, action: dryRun ? "would_skip" : "skipped" });
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
if (dryRun) {
|
|
349
|
+
results.push({ path: file.path, action: "would_write" });
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
353
|
+
fs.writeFileSync(abs, file.content, "utf8");
|
|
354
|
+
results.push({ path: file.path, action: "wrote" });
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
return results;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Top-level entry point used by `shipctl init --bootstrap`.
|
|
362
|
+
*
|
|
363
|
+
* @param {string} cwd
|
|
364
|
+
* @param {object} config
|
|
365
|
+
* @param {object|null} presetArtifact Reserved for v2 when we parse the preset body.
|
|
366
|
+
* @param {Array<object>} _adapters Reserved for v2.
|
|
367
|
+
* @param {{dryRun?:boolean, force?:boolean}} [opts]
|
|
368
|
+
*/
|
|
369
|
+
export function renderBootstrap(cwd, config, presetArtifact, _adapters, opts = {}) {
|
|
370
|
+
const plan = renderPlan(config, presetArtifact);
|
|
371
|
+
const results = applyPlan(cwd, plan, opts);
|
|
372
|
+
return { plan, results };
|
|
373
|
+
}
|