@smoothbricks/cli 0.10.2 → 0.10.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 +31 -23
- package/dist/bun/preload.d.ts +13 -0
- package/dist/bun/preload.d.ts.map +1 -0
- package/dist/bun/preload.js +12 -0
- package/dist/cli.js +36 -4
- package/dist/generate/index.d.ts.map +1 -1
- package/dist/generate/index.js +0 -6
- package/dist/github-ci/index.d.ts +17 -2
- package/dist/github-ci/index.d.ts.map +1 -1
- package/dist/github-ci/index.js +192 -26
- package/dist/github-ci/outputs.d.ts +22 -0
- package/dist/github-ci/outputs.d.ts.map +1 -0
- package/dist/github-ci/outputs.js +648 -0
- package/dist/lib/cli-package.js +2 -2
- package/dist/lib/json.d.ts +118 -11
- package/dist/lib/json.d.ts.map +1 -1
- package/dist/lib/json.js +317 -29
- package/dist/lib/workspace.d.ts +7 -14
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +76 -82
- package/dist/monorepo/ci-workflow.d.ts +2 -0
- package/dist/monorepo/ci-workflow.d.ts.map +1 -1
- package/dist/monorepo/ci-workflow.js +28 -2
- package/dist/monorepo/git-config.js +2 -2
- package/dist/monorepo/index.d.ts +3 -1
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +6 -2
- package/dist/monorepo/lockfile.d.ts +13 -1
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +33 -21
- package/dist/monorepo/managed-files.d.ts +12 -0
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +86 -28
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +78 -45
- package/dist/monorepo/packed-manifest.d.ts +5 -2
- package/dist/monorepo/packed-manifest.d.ts.map +1 -1
- package/dist/monorepo/packed-manifest.js +16 -47
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +28 -16
- package/dist/monorepo/packs/index.d.ts +3 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +18 -6
- package/dist/monorepo/publish-workflow.d.ts +1 -0
- package/dist/monorepo/publish-workflow.d.ts.map +1 -1
- package/dist/monorepo/publish-workflow.js +242 -3
- package/dist/monorepo/runtime.d.ts +15 -0
- package/dist/monorepo/runtime.d.ts.map +1 -1
- package/dist/monorepo/runtime.js +66 -12
- package/dist/monorepo/tool-validation.d.ts.map +1 -1
- package/dist/monorepo/tool-validation.js +32 -23
- package/dist/monorepo/wrangler.d.ts.map +1 -1
- package/dist/monorepo/wrangler.js +13 -13
- package/dist/nx/index.d.ts +12 -4
- package/dist/nx/index.d.ts.map +1 -1
- package/dist/nx/index.js +86 -19
- package/dist/pr/index.d.ts.map +1 -1
- package/dist/pr/index.js +28 -32
- package/dist/release/candidates.d.ts +3 -2
- package/dist/release/candidates.d.ts.map +1 -1
- package/dist/release/candidates.js +4 -5
- package/dist/release/github-release.d.ts +3 -0
- package/dist/release/github-release.d.ts.map +1 -1
- package/dist/release/github-release.js +11 -0
- package/dist/release/index.d.ts +20 -1
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +221 -89
- package/dist/release/orchestration.d.ts +8 -1
- package/dist/release/orchestration.d.ts.map +1 -1
- package/dist/release/orchestration.js +38 -1
- package/dist/wrangler/prepare-env.d.ts +0 -1
- package/dist/wrangler/prepare-env.d.ts.map +1 -1
- package/dist/wrangler/prepare-env.js +100 -51
- package/dist/wrangler/scaffold.d.ts.map +1 -1
- package/dist/wrangler/scaffold.js +12 -12
- package/managed/raw/git-format-staged.yml +19 -1
- package/managed/raw/tooling/direnv/github-actions-bootstrap.sh +6 -0
- package/managed/raw/tooling/direnv/merge-newer-pins.sh +3 -3
- package/managed/raw/tooling/direnv/setup-environment.ts +187 -19
- package/managed/raw/tooling/git-hooks/pre-commit.sh +0 -6
- package/managed/templates/github/actions/cache-nix-devenv/action.yml +12 -10
- package/managed/templates/github/actions/cache-node-modules/action.yml +6 -2
- package/managed/templates/github/actions/cache-nx/action.yml +2 -2
- package/managed/templates/github/actions/cache-ttsc-plugins/action.yml +19 -0
- package/managed/templates/github/actions/save-nix-devenv/action.yml +11 -3
- package/managed/templates/github/actions/setup-devenv/action.yml +5 -0
- package/managed/templates/github/workflows/managed-files.yml +80 -0
- package/package.json +18 -4
- package/src/bun/preload.ts +12 -0
- package/src/cli.ts +49 -11
- package/src/generate/index.ts +0 -6
- package/src/github-ci/index.test.ts +486 -0
- package/src/github-ci/index.ts +220 -30
- package/src/github-ci/outputs.ts +505 -0
- package/src/lib/cli-package.ts +2 -2
- package/src/lib/json.ts +291 -32
- package/src/lib/workspace.ts +88 -100
- package/src/monorepo/__tests__/ci-workflow.test.ts +14 -3
- package/src/monorepo/__tests__/publish-workflow.test.ts +222 -1
- package/src/monorepo/ci-workflow.ts +28 -2
- package/src/monorepo/git-config.ts +2 -2
- package/src/monorepo/index.ts +6 -2
- package/src/monorepo/lockfile.test.ts +35 -36
- package/src/monorepo/lockfile.ts +48 -24
- package/src/monorepo/managed-files.test.ts +63 -0
- package/src/monorepo/managed-files.ts +94 -34
- package/src/monorepo/package-policy.test.ts +70 -51
- package/src/monorepo/package-policy.ts +104 -59
- package/src/monorepo/packed-manifest.ts +18 -52
- package/src/monorepo/packed-package.ts +43 -22
- package/src/monorepo/packs/index.test.ts +18 -1
- package/src/monorepo/packs/index.ts +20 -7
- package/src/monorepo/publish-workflow.ts +482 -3
- package/src/monorepo/runtime.test.ts +42 -3
- package/src/monorepo/runtime.ts +90 -12
- package/src/monorepo/tool-validation.ts +35 -25
- package/src/monorepo/wrangler.test.ts +11 -15
- package/src/monorepo/wrangler.ts +15 -15
- package/src/nx/index.test.ts +43 -0
- package/src/nx/index.ts +110 -23
- package/src/pr/index.ts +42 -36
- package/src/release/__tests__/candidates.test.ts +5 -4
- package/src/release/__tests__/fixture-repo.test.ts +18 -16
- package/src/release/__tests__/github-release.test.ts +11 -0
- package/src/release/__tests__/orchestration.test.ts +57 -2
- package/src/release/__tests__/trust-publisher.test.ts +98 -2
- package/src/release/candidates.ts +10 -17
- package/src/release/github-release.ts +12 -0
- package/src/release/index.ts +321 -112
- package/src/release/orchestration.ts +55 -3
- package/src/wrangler/prepare-env.ts +75 -54
- package/src/wrangler/scaffold.test.ts +9 -12
- package/src/wrangler/scaffold.ts +13 -13
package/src/github-ci/index.ts
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { appendFile, mkdtemp, realpath, rename, rm } from 'node:fs/promises';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { $ } from 'bun';
|
|
5
|
+
import typia from 'typia';
|
|
6
|
+
import { parseStringArrayText } from '../lib/json.js';
|
|
5
7
|
import { decode, run, runStatus } from '../lib/run.js';
|
|
8
|
+
import { type ProjectTargets, readProjectTargets } from '../nx/index.js';
|
|
9
|
+
import type { NxTargetRun } from './outputs.js';
|
|
10
|
+
|
|
11
|
+
interface GithubActionsEventPayload {
|
|
12
|
+
repository?: {
|
|
13
|
+
default_branch?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface NxProjectDeployConfigurations {
|
|
18
|
+
targets?: {
|
|
19
|
+
deploy?: {
|
|
20
|
+
configurations?: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const parseGithubActionsEvent = typia.json.createIsParse<GithubActionsEventPayload>();
|
|
26
|
+
const parseNxProjectDeployConfigurations = typia.json.createIsParse<NxProjectDeployConfigurations>();
|
|
6
27
|
|
|
7
28
|
type NxSmartMode = 'auto' | 'affected' | 'run-many';
|
|
8
29
|
|
|
@@ -113,6 +134,15 @@ async function addReferencesFrom(roots: Set<string>, path: string, cwd: string):
|
|
|
113
134
|
}
|
|
114
135
|
}
|
|
115
136
|
|
|
137
|
+
export function nxSmartArgs(target: string, mode: 'affected' | 'run-many', configuration?: string): string[] {
|
|
138
|
+
const args = [mode, '-t', target];
|
|
139
|
+
if (configuration) {
|
|
140
|
+
args.push(`--configuration=${configuration}`);
|
|
141
|
+
}
|
|
142
|
+
args.push(`--exclude=tag:ci:skip:${target}`, '--parallel=5');
|
|
143
|
+
return args;
|
|
144
|
+
}
|
|
145
|
+
|
|
116
146
|
export async function githubCiNxSmart(
|
|
117
147
|
root: string,
|
|
118
148
|
options: { target: string; name?: string; step?: string; mode?: NxSmartMode; configuration?: string },
|
|
@@ -121,11 +151,7 @@ export async function githubCiNxSmart(
|
|
|
121
151
|
const step = options.step ?? '';
|
|
122
152
|
await createGithubStatus(name, step);
|
|
123
153
|
const mode = resolveNxSmartMode(options.mode ?? 'auto');
|
|
124
|
-
const nxArgs =
|
|
125
|
-
if (options.configuration) {
|
|
126
|
-
nxArgs.push(`--configuration=${options.configuration}`);
|
|
127
|
-
}
|
|
128
|
-
nxArgs.push('--parallel=5');
|
|
154
|
+
const nxArgs = nxSmartArgs(options.target, mode, options.configuration);
|
|
129
155
|
const status = await runStatus('nx', nxArgs, root);
|
|
130
156
|
await updateGithubStatus(name, status === 0 ? 'success' : 'failure', step);
|
|
131
157
|
if (status !== 0) {
|
|
@@ -133,18 +159,170 @@ export async function githubCiNxSmart(
|
|
|
133
159
|
}
|
|
134
160
|
}
|
|
135
161
|
|
|
136
|
-
export
|
|
162
|
+
export interface NxRunManyOptions {
|
|
163
|
+
targets: string;
|
|
164
|
+
projects?: string;
|
|
165
|
+
configuration?: string;
|
|
166
|
+
collectOutputs?: string;
|
|
167
|
+
allowEmptyProjects?: boolean;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface ExpandedNxTargetRuns {
|
|
171
|
+
runs: NxTargetRun[];
|
|
172
|
+
unmatchedGlobs: string[];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function expandNxTargetRuns(projects: ProjectTargets[], options: NxRunManyOptions): ExpandedNxTargetRuns {
|
|
176
|
+
const selectedProjects = selectProjects(projects, options.projects);
|
|
177
|
+
if (options.projects !== undefined && selectedProjects.length === 0 && options.allowEmptyProjects !== true) {
|
|
178
|
+
throw new Error(`No Nx projects matched --projects ${options.projects}.`);
|
|
179
|
+
}
|
|
180
|
+
const selectedTargetNames = [...new Set(selectedProjects.flatMap((project) => project.targets))].sort((a, b) =>
|
|
181
|
+
a.localeCompare(b),
|
|
182
|
+
);
|
|
183
|
+
const runs: NxTargetRun[] = [];
|
|
184
|
+
const unmatchedGlobs: string[] = [];
|
|
185
|
+
const addedTargets = new Set<string>();
|
|
186
|
+
for (const targetPattern of commaSeparatedValues(options.targets)) {
|
|
187
|
+
const isGlob = isGlobPattern(targetPattern);
|
|
188
|
+
const targets = isGlob
|
|
189
|
+
? selectedTargetNames.filter((target) => new Bun.Glob(targetPattern).match(target))
|
|
190
|
+
: [targetPattern];
|
|
191
|
+
if (isGlob && targets.length === 0) {
|
|
192
|
+
unmatchedGlobs.push(targetPattern);
|
|
193
|
+
}
|
|
194
|
+
for (const target of targets) {
|
|
195
|
+
if (addedTargets.has(target)) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const owners = selectedProjects.filter((project) => project.targets.includes(target));
|
|
199
|
+
const runProjects = owners.length > 0 ? owners : selectedProjects;
|
|
200
|
+
if (runProjects.length === 0) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
addedTargets.add(target);
|
|
204
|
+
runs.push({ target, projects: runProjects });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return { runs, unmatchedGlobs };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function expandNxTargetDependencyRuns(runs: NxTargetRun[]): NxTargetRun[] {
|
|
211
|
+
const expanded: NxTargetRun[] = [];
|
|
212
|
+
const added = new Set<string>();
|
|
213
|
+
const visiting = new Set<string>();
|
|
214
|
+
|
|
215
|
+
const visit = (project: ProjectTargets, target: string): void => {
|
|
216
|
+
const key = `${project.project}:${target}`;
|
|
217
|
+
if (added.has(key)) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (visiting.has(key)) {
|
|
221
|
+
throw new Error(`Nx target dependency cycle detected at ${key}.`);
|
|
222
|
+
}
|
|
223
|
+
visiting.add(key);
|
|
224
|
+
for (const dependency of project.targetDependencies?.get(target) ?? []) {
|
|
225
|
+
if (dependency.startsWith('^')) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const dependencyTargets = isGlobPattern(dependency)
|
|
229
|
+
? project.targets.filter((candidate) => new Bun.Glob(dependency).match(candidate))
|
|
230
|
+
: project.targets.includes(dependency)
|
|
231
|
+
? [dependency]
|
|
232
|
+
: [];
|
|
233
|
+
for (const dependencyTarget of dependencyTargets.sort((left, right) => left.localeCompare(right))) {
|
|
234
|
+
visit(project, dependencyTarget);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
visiting.delete(key);
|
|
238
|
+
added.add(key);
|
|
239
|
+
if ((project.targetOutputs?.get(target)?.length ?? 0) > 0) {
|
|
240
|
+
expanded.push({ target, projects: [project] });
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
for (const run of runs) {
|
|
245
|
+
for (const project of run.projects) {
|
|
246
|
+
visit(project, run.target);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return expanded;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export function nxRunManyArgs(run: NxTargetRun, configuration?: string): string[] {
|
|
253
|
+
if (run.projects.length === 0) {
|
|
254
|
+
throw new Error(`Nx target ${run.target} has no selected projects.`);
|
|
255
|
+
}
|
|
256
|
+
const nxArgs = [
|
|
257
|
+
'run-many',
|
|
258
|
+
'-t',
|
|
259
|
+
run.target,
|
|
260
|
+
`--projects=${run.projects.map((project) => project.project).join(',')}`,
|
|
261
|
+
];
|
|
262
|
+
if (configuration) {
|
|
263
|
+
nxArgs.push(`--configuration=${configuration}`);
|
|
264
|
+
}
|
|
265
|
+
nxArgs.push('--parallel=5');
|
|
266
|
+
return nxArgs;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export async function readGitHeadSha(root: string): Promise<string> {
|
|
270
|
+
// invariant throw: GitHub CI commands require a valid repository root.
|
|
271
|
+
return decode((await $`git rev-parse HEAD`.cwd(root).quiet()).stdout).trim();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export async function githubCiNxRunMany(root: string, options: NxRunManyOptions): Promise<void> {
|
|
275
|
+
const expanded = expandNxTargetRuns(await readProjectTargets(root), options);
|
|
276
|
+
if (expanded.unmatchedGlobs.length > 0) {
|
|
277
|
+
console.log(`No Nx targets matched target glob(s): ${expanded.unmatchedGlobs.join(', ')}; skipping.`);
|
|
278
|
+
}
|
|
279
|
+
for (const targetRun of expanded.runs) {
|
|
280
|
+
await run('nx', nxRunManyArgs(targetRun, options.configuration), root);
|
|
281
|
+
}
|
|
282
|
+
if (options.collectOutputs) {
|
|
283
|
+
const { collectNxOutputs } = await loadOutputBoundary();
|
|
284
|
+
const sourceSha = await readGitHeadSha(root);
|
|
285
|
+
await collectNxOutputs(root, options.collectOutputs, expandNxTargetDependencyRuns(expanded.runs), sourceSha);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export async function githubCiApplyOutputs(
|
|
137
290
|
root: string,
|
|
138
|
-
|
|
291
|
+
directories: string[],
|
|
292
|
+
expectedSourceSha: string,
|
|
139
293
|
): Promise<void> {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
294
|
+
const { applyCollectedOutputs } = await loadOutputBoundary();
|
|
295
|
+
await applyCollectedOutputs(root, directories, expectedSourceSha);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function loadOutputBoundary() {
|
|
299
|
+
if (import.meta.url.endsWith('/src/github-ci/index.ts')) {
|
|
300
|
+
// The source self-hosting shim has no Typia transform; register it before loading manifest validators.
|
|
301
|
+
await import('@smoothbricks/cli/bun/preload');
|
|
143
302
|
}
|
|
144
|
-
|
|
145
|
-
|
|
303
|
+
// This boundary must stay lazy because the transformed dist and source self-hosting paths initialize Typia differently.
|
|
304
|
+
return import('./outputs.js');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function isGlobPattern(value: string): boolean {
|
|
308
|
+
return /[*?{[]/.test(value);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function selectProjects(projects: ProjectTargets[], selectors: string | undefined): ProjectTargets[] {
|
|
312
|
+
if (selectors === undefined) {
|
|
313
|
+
return projects.slice().sort((left, right) => left.project.localeCompare(right.project));
|
|
146
314
|
}
|
|
147
|
-
|
|
315
|
+
const patterns = commaSeparatedValues(selectors);
|
|
316
|
+
return projects
|
|
317
|
+
.filter((project) => patterns.some((pattern) => new Bun.Glob(pattern).match(project.project)))
|
|
318
|
+
.sort((left, right) => left.project.localeCompare(right.project));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function commaSeparatedValues(value: string): string[] {
|
|
322
|
+
return value
|
|
323
|
+
.split(',')
|
|
324
|
+
.map((entry) => entry.trim())
|
|
325
|
+
.filter(Boolean);
|
|
148
326
|
}
|
|
149
327
|
|
|
150
328
|
export async function githubCiNxDeploy(
|
|
@@ -182,7 +360,31 @@ function resolveNxSmartMode(mode: NxSmartMode): 'affected' | 'run-many' {
|
|
|
182
360
|
if (mode === 'affected' || mode === 'run-many') {
|
|
183
361
|
return mode;
|
|
184
362
|
}
|
|
185
|
-
|
|
363
|
+
const defaultBranch = eventDefaultBranch() ?? 'main';
|
|
364
|
+
if (process.env.GITHUB_EVENT_NAME === 'push') {
|
|
365
|
+
return process.env.GITHUB_REF_NAME === defaultBranch ? 'run-many' : 'affected';
|
|
366
|
+
}
|
|
367
|
+
// A PR into a NON-default branch is an integration surface (e.g. a mirror-sync
|
|
368
|
+
// review branch): its base moves outside the default-branch workflow that
|
|
369
|
+
// affected scoping is calibrated against, so under-selection can pass PR CI
|
|
370
|
+
// and only fail after merge. Validate those PRs in full.
|
|
371
|
+
if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
|
|
372
|
+
const base = process.env.GITHUB_BASE_REF;
|
|
373
|
+
return base && base !== defaultBranch ? 'run-many' : 'affected';
|
|
374
|
+
}
|
|
375
|
+
return 'affected';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** The repository default branch from the Actions event payload. */
|
|
379
|
+
function eventDefaultBranch(): string | undefined {
|
|
380
|
+
const eventPath = process.env.GITHUB_EVENT_PATH;
|
|
381
|
+
if (!eventPath) return undefined;
|
|
382
|
+
try {
|
|
383
|
+
const payload = parseGithubActionsEvent(readFileSync(eventPath, 'utf8'));
|
|
384
|
+
return payload?.repository?.default_branch || undefined;
|
|
385
|
+
} catch {
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
186
388
|
}
|
|
187
389
|
|
|
188
390
|
async function deployProjectsWithConfiguration(
|
|
@@ -207,24 +409,12 @@ async function deployProjectsWithConfiguration(
|
|
|
207
409
|
|
|
208
410
|
async function deployTargetHasConfiguration(root: string, project: string, configuration: string): Promise<boolean> {
|
|
209
411
|
const result = await $`nx show project ${project} --json`.cwd(root).quiet();
|
|
210
|
-
const parsed
|
|
211
|
-
|
|
212
|
-
const deploy = recordValue(targets)?.deploy;
|
|
213
|
-
const configurations = recordValue(deploy)?.configurations;
|
|
214
|
-
return recordValue(configurations)?.[configuration] !== undefined;
|
|
412
|
+
const parsed = parseNxProjectDeployConfigurations(decode(result.stdout));
|
|
413
|
+
return parsed?.targets?.deploy?.configurations?.[configuration] !== undefined;
|
|
215
414
|
}
|
|
216
415
|
|
|
217
416
|
function nxProjectList(output: string): string[] {
|
|
218
|
-
|
|
219
|
-
return Array.isArray(parsed) ? parsed.filter((project): project is string => typeof project === 'string') : [];
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
function recordValue(value: unknown): Record<string, unknown> | undefined {
|
|
223
|
-
return isRecord(value) ? value : undefined;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
227
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
417
|
+
return parseStringArrayText(output) ?? [];
|
|
228
418
|
}
|
|
229
419
|
|
|
230
420
|
async function createGithubStatus(name: string, step: string): Promise<void> {
|