deepline 0.3.90 → 0.3.91
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/dist/bundling-sources/sdk/src/client.ts +101 -6
- package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +2 -556
- package/dist/bundling-sources/sdk/src/monitor-fleets.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +7 -12
- package/dist/bundling-sources/sdk/src/types.ts +6 -1
- package/dist/bundling-sources/shared_libs/integration-contracts/monitor-fleet-contract.ts +549 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/errors.ts +96 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/limits.ts +8 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/plan.ts +320 -0
- package/dist/bundling-sources/shared_libs/monitors/fleets/verdict.ts +106 -0
- package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +5 -1
- package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +2 -12
- package/dist/bundling-sources/shared_libs/monitors/package.json +107 -5
- package/dist/bundling-sources/shared_libs/monitors/provider-monitor-batch-operation.ts +12 -7
- package/dist/bundling-sources/shared_libs/observability/scheduled-jobs.json +36 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/db-session-lease-store.ts +578 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-execution-relay.ts +521 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/roll-receipt-gateway.ts +1378 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/commands/runtime-topology.ts +1024 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-gateway-route-probe.ts +193 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machine-lifecycle.ts +104 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-machines.ts +1844 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/fly-registry.ts +21 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/process-tree.ts +40 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-gateway-drain.ts +262 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-surface.ts +98 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-topology.ts +303 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-publication-guardian.ts +70 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/runtime-tuple-recovery.ts +519 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-fly-rollback-alignment.ts +280 -0
- package/dist/bundling-sources/shared_libs/play-runtime/deployment/vercel-production-gate.ts +159 -0
- package/dist/bundling-sources/shared_libs/play-runtime/integration-operation-reader-fence/memory-store.ts +114 -0
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +44 -5
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/child-play-resolver.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +3662 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/progress-sidecar.ts +47 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-deadline.ts +84 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-gateway-transport.ts +91 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-heartbeat.ts +424 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-progress-push.ts +321 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-sheet-persistence-retry.ts +110 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/runtime-terminal-push.ts +625 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +46 -3
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/receipt-gateway.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/webhook-receipt-retry.ts +50 -0
- package/dist/bundling-sources/shared_libs/plays/bundling/repository-adapter.ts +24 -0
- package/dist/bundling-sources/shared_libs/plays/child-run-projection.ts +174 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/api-client.ts +267 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/control-plane-types.ts +73 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/manifest.ts +162 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/project-serializer.ts +87 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/source-file-path.ts +26 -0
- package/dist/bundling-sources/shared_libs/plays/deploy/target-paths.ts +36 -0
- package/dist/bundling-sources/shared_libs/plays/launch/input-contract.ts +400 -0
- package/dist/bundling-sources/shared_libs/plays/launch/prepare.ts +96 -0
- package/dist/bundling-sources/shared_libs/plays/local-file-discovery.ts +385 -0
- package/dist/bundling-sources/shared_libs/plays/play-grep-search.ts +79 -6
- package/dist/bundling-sources/shared_libs/plays/play-reference.ts +117 -0
- package/dist/bundling-sources/shared_libs/plays/run-input-summary.ts +260 -0
- package/dist/bundling-sources/shared_libs/plays/run-summary-types.ts +49 -0
- package/dist/bundling-sources/shared_libs/plays/secrets/errors.ts +19 -0
- package/dist/bundling-sources/shared_libs/plays/sheet-filter.ts +347 -0
- package/dist/bundling-sources/shared_libs/plays/standard-webhooks.ts +117 -0
- package/dist/bundling-sources/shared_libs/plays/status-result.ts +388 -0
- package/dist/bundling-sources/shared_libs/plays/transform-factor-structure.ts +377 -0
- package/dist/bundling-sources/shared_libs/plays/webhook-payload.ts +113 -0
- package/dist/cli/index.js +369 -162
- package/dist/cli/index.mjs +369 -162
- package/dist/{compiler-manifest-BaJfE-5H.d.mts → compiler-manifest-x14KL83E.d.mts} +1 -1
- package/dist/{compiler-manifest-BaJfE-5H.d.ts → compiler-manifest-x14KL83E.d.ts} +1 -1
- package/dist/index.d.mts +45 -8
- package/dist/index.d.ts +45 -8
- package/dist/index.js +61 -14
- package/dist/index.mjs +61 -14
- package/dist/install-integrity.json +54 -2
- package/dist/monitor-fleet-contract.d.mts +3 -10
- package/dist/monitor-fleet-contract.d.ts +3 -10
- package/dist/monitor-fleet-contract.js +2 -0
- package/dist/monitor-fleet-contract.mjs +1 -1
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/release.d.mts +3 -3
- package/dist/release.d.ts +3 -3
- package/dist/release.js +7 -12
- package/dist/release.mjs +7 -12
- package/package.json +2 -2
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import picomatch from 'picomatch';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* deepline.json — the repo-owned half of deploy configuration. The repo
|
|
7
|
+
* describes what plays exist and where; Convex (playProjects) decides what
|
|
8
|
+
* happens when they change (production branch, PR checks, enable/disable).
|
|
9
|
+
* No field lives in both places.
|
|
10
|
+
*/
|
|
11
|
+
export type DeeplineRepoManifest = {
|
|
12
|
+
/** Globs relative to the project root directory. */
|
|
13
|
+
plays: string[];
|
|
14
|
+
/** Optional tenant guard: the workspace slug this repo expects. */
|
|
15
|
+
org?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const DEFAULT_PLAY_GLOBS = ['**/*.play.ts'];
|
|
19
|
+
const MAX_DISCOVERED_PLAY_FILES = 200;
|
|
20
|
+
const IGNORED_DIRECTORIES = new Set([
|
|
21
|
+
'node_modules',
|
|
22
|
+
'.git',
|
|
23
|
+
'dist',
|
|
24
|
+
'build',
|
|
25
|
+
'.next',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export class DeeplineManifestError extends Error {
|
|
29
|
+
constructor(message: string) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = 'DeeplineManifestError';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function parseDeeplineManifest(
|
|
36
|
+
raw: string | null,
|
|
37
|
+
): DeeplineRepoManifest {
|
|
38
|
+
if (raw === null) {
|
|
39
|
+
return { plays: DEFAULT_PLAY_GLOBS };
|
|
40
|
+
}
|
|
41
|
+
let parsed: unknown;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(raw);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new DeeplineManifestError(
|
|
46
|
+
`deepline.json is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
50
|
+
throw new DeeplineManifestError('deepline.json must be a JSON object.');
|
|
51
|
+
}
|
|
52
|
+
const record = parsed as { plays?: unknown; org?: unknown };
|
|
53
|
+
let plays = DEFAULT_PLAY_GLOBS;
|
|
54
|
+
if (record.plays !== undefined) {
|
|
55
|
+
if (
|
|
56
|
+
!Array.isArray(record.plays) ||
|
|
57
|
+
record.plays.length === 0 ||
|
|
58
|
+
!record.plays.every(
|
|
59
|
+
(glob) => typeof glob === 'string' && glob.trim().length > 0,
|
|
60
|
+
)
|
|
61
|
+
) {
|
|
62
|
+
throw new DeeplineManifestError(
|
|
63
|
+
'deepline.json "plays" must be a non-empty array of glob strings, e.g. ["plays/**/*.play.ts"].',
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
plays = record.plays.map((glob) => glob.trim().replace(/^\.\//, ''));
|
|
67
|
+
}
|
|
68
|
+
if (plays.some((glob) => glob.startsWith('/') || glob.includes('..'))) {
|
|
69
|
+
throw new DeeplineManifestError(
|
|
70
|
+
'deepline.json "plays" globs must be relative and must not contain "..".',
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const org =
|
|
74
|
+
typeof record.org === 'string' && record.org.trim()
|
|
75
|
+
? record.org.trim()
|
|
76
|
+
: undefined;
|
|
77
|
+
return { plays, ...(org ? { org } : {}) };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const DEFAULT_MANIFEST_FILE = 'deepline.json';
|
|
81
|
+
|
|
82
|
+
export async function readDeeplineManifest(
|
|
83
|
+
projectRoot: string,
|
|
84
|
+
manifestFile: string = DEFAULT_MANIFEST_FILE,
|
|
85
|
+
): Promise<DeeplineRepoManifest> {
|
|
86
|
+
let raw: string | null = null;
|
|
87
|
+
try {
|
|
88
|
+
raw = await readFile(join(projectRoot, manifestFile), 'utf8');
|
|
89
|
+
} catch (error) {
|
|
90
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
91
|
+
// Only "file does not exist" may fall through; a permissions or I/O
|
|
92
|
+
// failure must never silently select default globs.
|
|
93
|
+
if (code !== 'ENOENT' && code !== 'ENOTDIR') {
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
// An absent DEFAULT manifest means "use defaults". An absent EXPLICITLY
|
|
97
|
+
// configured manifest means the project points at a file the repo does
|
|
98
|
+
// not have — deploying **/*.play.ts instead would publish plays the
|
|
99
|
+
// manifest was supposed to scope out.
|
|
100
|
+
if (manifestFile !== DEFAULT_MANIFEST_FILE) {
|
|
101
|
+
throw new DeeplineManifestError(
|
|
102
|
+
`DEEPLINE_MANIFEST_MISSING: this project is configured to read ${manifestFile}, but the file does not exist at the project root.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
raw = null;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
return parseDeeplineManifest(raw);
|
|
109
|
+
} catch (error) {
|
|
110
|
+
if (error instanceof DeeplineManifestError) {
|
|
111
|
+
throw new DeeplineManifestError(
|
|
112
|
+
error.message.replace(/^deepline\.json/, manifestFile),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Explicit globs beat inference; a glob that accidentally matches
|
|
121
|
+
* node_modules or fixtures publishes garbage, so vendored/build directories
|
|
122
|
+
* are always skipped and discovery is capped loudly.
|
|
123
|
+
*/
|
|
124
|
+
export async function discoverPlayFiles(input: {
|
|
125
|
+
projectRoot: string;
|
|
126
|
+
globs: string[];
|
|
127
|
+
}): Promise<string[]> {
|
|
128
|
+
const matchers = input.globs.map((glob) => picomatch(glob));
|
|
129
|
+
const found: string[] = [];
|
|
130
|
+
|
|
131
|
+
async function walk(dir: string): Promise<void> {
|
|
132
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
133
|
+
for (const entry of entries) {
|
|
134
|
+
if (entry.name.startsWith('.') && entry.name !== '.') continue;
|
|
135
|
+
const absolute = join(dir, entry.name);
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
if (IGNORED_DIRECTORIES.has(entry.name)) continue;
|
|
138
|
+
await walk(absolute);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (!entry.isFile()) continue;
|
|
142
|
+
const relativePath = relative(input.projectRoot, absolute);
|
|
143
|
+
if (matchers.some((match) => match(relativePath))) {
|
|
144
|
+
found.push(relativePath);
|
|
145
|
+
if (found.length > MAX_DISCOVERED_PLAY_FILES) {
|
|
146
|
+
throw new DeeplineManifestError(
|
|
147
|
+
`deepline.json globs matched more than ${MAX_DISCOVERED_PLAY_FILES} play files. Tighten the globs.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rootStat = await stat(input.projectRoot).catch(() => null);
|
|
155
|
+
if (!rootStat?.isDirectory()) {
|
|
156
|
+
throw new DeeplineManifestError(
|
|
157
|
+
`Project root directory does not exist in the repo: ${input.projectRoot}`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
await walk(input.projectRoot);
|
|
161
|
+
return found.sort();
|
|
162
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { DEFAULT_MANIFEST_FILE } from './manifest';
|
|
2
|
+
import type { PlayDeploymentDoc, PlayProjectDoc } from './control-plane-types';
|
|
3
|
+
|
|
4
|
+
export { DEFAULT_MANIFEST_FILE } from './manifest';
|
|
5
|
+
|
|
6
|
+
export type SerializedPlayProject = {
|
|
7
|
+
id: string;
|
|
8
|
+
orgId: string;
|
|
9
|
+
gitHost: string;
|
|
10
|
+
installationId: string;
|
|
11
|
+
repoId: number;
|
|
12
|
+
repoFullName: string;
|
|
13
|
+
rootDirectory: string;
|
|
14
|
+
manifestPath: string;
|
|
15
|
+
productionBranch: string;
|
|
16
|
+
checkOnPullRequest: boolean;
|
|
17
|
+
disabled: boolean;
|
|
18
|
+
createdByUserId: string;
|
|
19
|
+
createdAt: number;
|
|
20
|
+
updatedAt: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function serializePlayProject(
|
|
24
|
+
project: PlayProjectDoc,
|
|
25
|
+
): SerializedPlayProject {
|
|
26
|
+
return {
|
|
27
|
+
id: String(project._id),
|
|
28
|
+
orgId: project.orgId,
|
|
29
|
+
gitHost: project.gitHost,
|
|
30
|
+
installationId: project.installationId,
|
|
31
|
+
repoId: project.repoId,
|
|
32
|
+
repoFullName: project.repoFullName,
|
|
33
|
+
rootDirectory: project.rootDirectory,
|
|
34
|
+
manifestPath: project.manifestPath?.trim() || DEFAULT_MANIFEST_FILE,
|
|
35
|
+
productionBranch: project.productionBranch,
|
|
36
|
+
checkOnPullRequest: project.checkOnPullRequest,
|
|
37
|
+
disabled: Boolean(project.disabledAt),
|
|
38
|
+
createdByUserId: project.createdByUserId,
|
|
39
|
+
createdAt: project.createdAt,
|
|
40
|
+
updatedAt: project.updatedAt,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type SerializedPlayDeployment = {
|
|
45
|
+
id: string;
|
|
46
|
+
orgId: string;
|
|
47
|
+
playProjectId: string;
|
|
48
|
+
commitSha: string;
|
|
49
|
+
ref: string;
|
|
50
|
+
isProduction: boolean;
|
|
51
|
+
trigger: string;
|
|
52
|
+
status: string;
|
|
53
|
+
pullRequestNumber: number | null;
|
|
54
|
+
/** Production push only: the PR whose merge produced this commit. */
|
|
55
|
+
mergedPullRequestNumber: number | null;
|
|
56
|
+
committerLogin: string | null;
|
|
57
|
+
results: unknown;
|
|
58
|
+
buildSeconds: number | null;
|
|
59
|
+
error: string | null;
|
|
60
|
+
createdAt: number;
|
|
61
|
+
startedAt: number | null;
|
|
62
|
+
finishedAt: number | null;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function serializePlayDeployment(
|
|
66
|
+
deployment: PlayDeploymentDoc,
|
|
67
|
+
): SerializedPlayDeployment {
|
|
68
|
+
return {
|
|
69
|
+
id: String(deployment._id),
|
|
70
|
+
orgId: deployment.orgId,
|
|
71
|
+
playProjectId: String(deployment.playProjectId),
|
|
72
|
+
commitSha: deployment.commitSha,
|
|
73
|
+
ref: deployment.ref,
|
|
74
|
+
isProduction: deployment.isProduction,
|
|
75
|
+
trigger: deployment.trigger,
|
|
76
|
+
status: deployment.status,
|
|
77
|
+
pullRequestNumber: deployment.pullRequestNumber ?? null,
|
|
78
|
+
mergedPullRequestNumber: deployment.mergedPullRequestNumber ?? null,
|
|
79
|
+
committerLogin: deployment.committerLogin ?? null,
|
|
80
|
+
results: deployment.results ?? null,
|
|
81
|
+
buildSeconds: deployment.buildSeconds ?? null,
|
|
82
|
+
error: deployment.error ?? null,
|
|
83
|
+
createdAt: deployment.createdAt,
|
|
84
|
+
startedAt: deployment.startedAt ?? null,
|
|
85
|
+
finishedAt: deployment.finishedAt ?? null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A revision's source path is repo-relative and is rendered as a location in
|
|
3
|
+
* the dashboard's play tree, so it is admitted once, here, rather than trusted
|
|
4
|
+
* from the request body.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const MAX_LENGTH = 1024;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns the path when it could have come from a repository walk, and null
|
|
11
|
+
* otherwise. Null is not an error: the field is optional everywhere downstream,
|
|
12
|
+
* and a play that did not come from a git deploy legitimately has no path.
|
|
13
|
+
*/
|
|
14
|
+
export function normalizeSourceFilePath(value: unknown): string | null {
|
|
15
|
+
if (typeof value !== 'string') return null;
|
|
16
|
+
const trimmed = value.trim();
|
|
17
|
+
if (!trimmed || trimmed.length > MAX_LENGTH) return null;
|
|
18
|
+
// Absolute paths and Windows separators never come from the repo walk, and
|
|
19
|
+
// `..` would let a path escape the project it claims to belong to.
|
|
20
|
+
if (trimmed.startsWith('/') || trimmed.includes('\\')) return null;
|
|
21
|
+
const segments = trimmed.split('/');
|
|
22
|
+
if (segments.some((segment) => segment === '..' || segment === '')) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return trimmed;
|
|
26
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deployment-target path validation shared by play-project create and
|
|
3
|
+
* update. The canonical forms feed the exact-duplicate target check in
|
|
4
|
+
* convex/playProjects.ts, so create and update MUST canonicalize
|
|
5
|
+
* identically — path aliases ('' vs '.', 'pkg//a' vs 'pkg/a') would
|
|
6
|
+
* otherwise slip past it.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function canonicalizeRootDirectory(
|
|
10
|
+
value: string,
|
|
11
|
+
): { ok: true; rootDirectory: string } | { ok: false; error: string } {
|
|
12
|
+
if (value.split('/').some((part) => part === '..')) {
|
|
13
|
+
return { ok: false, error: 'rootDirectory must not contain "..".' };
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
ok: true,
|
|
17
|
+
rootDirectory: value
|
|
18
|
+
.split('/')
|
|
19
|
+
.filter((part) => part && part !== '.')
|
|
20
|
+
.join('/'),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function validateManifestPath(
|
|
25
|
+
value: string,
|
|
26
|
+
): { ok: true; manifestPath: string } | { ok: false; error: string } {
|
|
27
|
+
const manifestPath = value.trim();
|
|
28
|
+
if (manifestPath.includes('/') || manifestPath.includes('..')) {
|
|
29
|
+
return {
|
|
30
|
+
ok: false,
|
|
31
|
+
error:
|
|
32
|
+
'manifestPath must be a bare file name under the base path (e.g. deepline.staging.json).',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return { ok: true, manifestPath };
|
|
36
|
+
}
|