@frontera-sdk/cli 1.52.38 → 1.52.40
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 +9 -0
- package/package.json +6 -4
- package/src/adopt.ts +6 -2
- package/src/api/apps-api.ts +86 -4
- package/src/api/blueprint-authoring-api.ts +35 -11
- package/src/api/dataset-api.ts +29 -0
- package/src/api/governed-action-api.ts +90 -2
- package/src/api/platform-api.ts +87 -2
- package/src/api/workflow-api.ts +33 -2
- package/src/app-contract.ts +156 -0
- package/src/app-lock.ts +125 -0
- package/src/args.ts +14 -3
- package/src/auth-verify.ts +10 -0
- package/src/automation-template.ts +12 -1
- package/src/blueprint/action-rules.ts +516 -0
- package/src/blueprint/action-semantic-fit.ts +205 -0
- package/src/blueprint/compile.ts +51 -2
- package/src/blueprint/diff.ts +34 -2
- package/src/blueprint/metric-query.ts +215 -0
- package/src/blueprint/metric-rules.ts +122 -0
- package/src/blueprint/object-sets.ts +116 -0
- package/src/blueprint/ontology-edit-plan.ts +48 -4
- package/src/blueprint/projection.ts +24 -1
- package/src/blueprint/scaffold.ts +84 -42
- package/src/commands/action/available.ts +1 -1
- package/src/commands/action/criteria-refusal.ts +84 -0
- package/src/commands/action/decide.ts +5 -2
- package/src/commands/action/deploy.ts +13 -4
- package/src/commands/action/list.ts +134 -19
- package/src/commands/action/requests.ts +34 -6
- package/src/commands/action/submit.ts +28 -13
- package/src/commands/agent/index-commands.ts +69 -7
- package/src/commands/agent/resolve.ts +2 -0
- package/src/commands/app/checks.ts +44 -0
- package/src/commands/app/deploy.ts +243 -60
- package/src/commands/app/deployments.ts +97 -0
- package/src/commands/app/dev.ts +103 -11
- package/src/commands/app/init.ts +36 -3
- package/src/commands/app/promote.ts +45 -19
- package/src/commands/app/pull.ts +3 -1
- package/src/commands/app/rollback.ts +46 -0
- package/src/commands/app/sdk.ts +54 -8
- package/src/commands/app/unpin.ts +27 -0
- package/src/commands/app/versions.ts +1 -1
- package/src/commands/automation/bindings.ts +4 -2
- package/src/commands/automation/build-entry.ts +4 -1
- package/src/commands/automation/index-commands.ts +3 -1
- package/src/commands/automation/pull.ts +6 -1
- package/src/commands/automation/run.ts +34 -9
- package/src/commands/blueprint/bind.ts +2 -1
- package/src/commands/blueprint/declarative.ts +331 -31
- package/src/commands/blueprint/get.ts +6 -2
- package/src/commands/blueprint/grants.ts +64 -10
- package/src/commands/blueprint/metric.ts +108 -0
- package/src/commands/blueprint/object-sets.ts +71 -0
- package/src/commands/blueprint/query.ts +155 -31
- package/src/commands/dataset/index-commands.ts +156 -7
- package/src/commands/knowledge/index-commands.ts +122 -20
- package/src/commands/local/check.ts +657 -44
- package/src/commands/local/data.ts +33 -12
- package/src/commands/local/dev.ts +875 -88
- package/src/commands/local/docs-mcp.ts +10 -18
- package/src/commands/local/index-commands.ts +300 -63
- package/src/commands/local/init.ts +18 -5
- package/src/commands/local/key.ts +0 -3
- package/src/commands/local/providers.ts +0 -7
- package/src/commands/local/remove.ts +116 -0
- package/src/commands/local/schema.ts +3 -4
- package/src/commands/local/sdk-refresh.ts +59 -0
- package/src/commands/local/snapshot.ts +100 -0
- package/src/commands/local/sync-request.ts +320 -13
- package/src/commands/local/sync.ts +102 -5
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +52 -8
- package/src/commands/types.ts +29 -0
- package/src/commands/workflow/archive.ts +46 -0
- package/src/commands/workflow/get.ts +2 -1
- package/src/commands/workflow/index-commands.ts +5 -0
- package/src/commands/workflow/push.ts +49 -2
- package/src/commands/workflow/run.ts +65 -7
- package/src/commands/workflow/runs.ts +9 -6
- package/src/commands/workflow/webhook-test.ts +166 -0
- package/src/config.ts +43 -6
- package/src/dev-broker.ts +52 -0
- package/src/exit.ts +103 -4
- package/src/flag-help.ts +77 -17
- package/src/forge/touches.ts +15 -12
- package/src/function-outside-imports.ts +89 -0
- package/src/harness.ts +42 -24
- package/src/heal.ts +112 -1
- package/src/local/fix-words.ts +12 -0
- package/src/local/function-bundle-recovery.ts +775 -0
- package/src/local/local-actions.ts +103 -10
- package/src/local/local-agent-action-grants.ts +210 -0
- package/src/local/local-agent-checks.ts +232 -0
- package/src/local/local-agent-grants.ts +29 -7
- package/src/local/local-agent-lean-plugins.ts +68 -0
- package/src/local/local-agent-missing-tools.ts +68 -0
- package/src/local/local-agent-name.ts +27 -0
- package/src/local/local-app-code.ts +191 -0
- package/src/local/local-app-dev-files.ts +186 -0
- package/src/local/local-app-dev-session.ts +88 -0
- package/src/local/local-app-lock-install.ts +114 -0
- package/src/local/local-app-pulled.ts +167 -0
- package/src/local/local-app-restore.ts +111 -0
- package/src/local/local-app-runtime.ts +68 -0
- package/src/local/local-app-sdk-paths.ts +149 -0
- package/src/local/local-app-sdk.ts +509 -0
- package/src/local/local-applied.ts +211 -21
- package/src/local/local-apply-history.ts +84 -0
- package/src/local/local-apply-summary.ts +86 -0
- package/src/local/local-apply-watchdog.ts +95 -0
- package/src/local/local-apps.ts +180 -7
- package/src/local/local-blueprint-errors.ts +198 -0
- package/src/local/local-blueprint-outline.ts +31 -12
- package/src/local/local-blueprint-schemas.ts +582 -0
- package/src/local/local-build-inputs.ts +48 -0
- package/src/local/local-build-summary.ts +495 -28
- package/src/local/local-case-evidence.ts +120 -0
- package/src/local/local-case-not-run.ts +153 -0
- package/src/local/local-chat-apps.ts +93 -16
- package/src/local/local-check-inherited.ts +83 -0
- package/src/local/local-cities.ts +89 -0
- package/src/local/local-component-health.ts +119 -0
- package/src/local/local-control.ts +34 -7
- package/src/local/local-db-snapshot.ts +348 -0
- package/src/local/local-dev-references.ts +346 -35
- package/src/local/local-dev-sync.ts +136 -7
- package/src/local/local-docker.ts +35 -1
- package/src/local/local-docs-search.ts +1 -1
- package/src/local/local-docs.ts +281 -2
- package/src/local/local-empty-workflows.ts +92 -0
- package/src/local/local-env.ts +16 -1
- package/src/local/local-file-checks.ts +104 -0
- package/src/local/local-function-ids.ts +141 -0
- package/src/local/local-function-recovery.ts +141 -0
- package/src/local/local-function-state.ts +87 -0
- package/src/local/local-keys.ts +7 -5
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-media-placeholders.ts +182 -0
- package/src/local/local-media-sets.ts +90 -10
- package/src/local/local-model-parameters.ts +89 -0
- package/src/local/local-object-sets.ts +18 -6
- package/src/local/local-operating.ts +35 -1
- package/src/local/local-org-settings.ts +205 -7
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +141 -16
- package/src/local/local-ports.ts +99 -7
- package/src/local/local-project-notices.ts +234 -0
- package/src/local/local-project-secrets.ts +133 -0
- package/src/local/local-project.ts +12 -0
- package/src/local/local-provider-refusal.ts +66 -0
- package/src/local/local-request-evidence.ts +112 -0
- package/src/local/local-runtime-health.ts +123 -0
- package/src/local/local-runtime-history.ts +50 -0
- package/src/local/local-runtime-target.ts +42 -0
- package/src/local/local-sample-data.ts +141 -0
- package/src/local/local-sample-remake.ts +138 -0
- package/src/local/local-scaffold.ts +336 -53
- package/src/local/local-schema-check.ts +148 -0
- package/src/local/local-schemas.ts +742 -25
- package/src/local/local-secret-carry.ts +107 -0
- package/src/local/local-secrets.ts +38 -8
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +1 -1
- package/src/local/local-state.ts +43 -2
- package/src/local/local-studio-server.ts +170 -7
- package/src/local/local-supervisor.ts +44 -6
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +11 -0
- package/src/local/local-sync-push.ts +10 -7
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +343 -32
- package/src/local/local-sync-snapshot.ts +15 -10
- package/src/local/local-synthetic-data.ts +2116 -99
- package/src/local/local-test-baseline.ts +113 -0
- package/src/local/local-test-cases.ts +76 -1
- package/src/local/local-test-runner.ts +582 -91
- package/src/local/local-test-runs.ts +13 -5
- package/src/local/local-vendored-sdk.ts +183 -0
- package/src/local/local-workflow-state.ts +16 -3
- package/src/local/local-workflow-tests.ts +482 -0
- package/src/local/local-workspace-grants.ts +81 -0
- package/src/main.ts +50 -9
- package/src/mcp/a11y-scan.ts +55 -0
- package/src/mcp/browser/ax-snapshot.ts +181 -0
- package/src/mcp/browser/credentials.ts +78 -0
- package/src/mcp/browser/frames.ts +336 -0
- package/src/mcp/browser/headless-session.ts +413 -0
- package/src/mcp/browser/headless-tab.ts +575 -0
- package/src/mcp/browser/keys.ts +70 -0
- package/src/mcp/browser/page-scripts.ts +129 -0
- package/src/mcp/browser/wait.ts +73 -0
- package/src/mcp/forge-link.ts +126 -0
- package/src/mcp/headless.ts +199 -0
- package/src/mcp/mcp-apps.ts +435 -0
- package/src/mcp/mcp-chat.ts +226 -0
- package/src/mcp/mcp-cli.ts +53 -0
- package/src/mcp/mcp-protocol.ts +122 -0
- package/src/mcp/mcp-runtime.ts +113 -0
- package/src/mcp/mcp-server.ts +108 -0
- package/src/mcp/mcp-traces.ts +409 -0
- package/src/mcp/page-observation.ts +193 -0
- package/src/mcp/tools/agent-tools.ts +700 -0
- package/src/mcp/tools/app-tools.ts +241 -0
- package/src/mcp/tools/browser-tools.ts +310 -0
- package/src/mcp/tools/docs-tools.ts +32 -0
- package/src/mcp/tools/project-tools.ts +205 -0
- package/src/mcp/tools/test-tools.ts +339 -0
- package/src/mcp/tools/workflow-tools.ts +611 -0
- package/src/output.ts +1 -1
- package/src/pack.ts +8 -3
- package/src/packaging.ts +1 -1
- package/src/project-bootstrap.ts +107 -4
- package/src/project-context.ts +18 -1
- package/src/project.ts +27 -2
- package/src/provenance.ts +25 -0
- package/src/render-evidence.ts +65 -7
- package/src/template.ts +14 -8
- package/src/templates/next-app-files.ts +35 -6
- package/src/templates/next-skills.ts +61 -27
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +24 -21
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { LocalProject } from './local-project'
|
|
2
2
|
import { readState } from './local-state'
|
|
3
|
-
import {
|
|
3
|
+
import { recordRun } from './local-test-baseline'
|
|
4
|
+
import { caseCount, runCases, selectCaseFiles, type CaseFile, type CaseResult } from './local-test-runner'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Test runs started from Studio: the same runner as `frontera local test`,
|
|
7
8
|
* run in the supervisor so the page can follow progress. Kept in memory,
|
|
8
|
-
* newest first;
|
|
9
|
+
* newest first; each case's outcome is also kept as its last run, as
|
|
10
|
+
* `frontera local test` keeps it.
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
export interface TestRun {
|
|
@@ -35,13 +37,13 @@ export function createTestRuns(project: LocalProject, serviceUrl: string) {
|
|
|
35
37
|
if (runs.some((r) => r.status === 'running')) return { error: 'a test run is already running', status: 409 }
|
|
36
38
|
const creds = readState(project)?.credentials
|
|
37
39
|
if (!creds?.session) return { error: 'the runtime is not seeded yet', status: 409 }
|
|
38
|
-
let files:
|
|
40
|
+
let files: CaseFile[]
|
|
39
41
|
try {
|
|
40
|
-
files =
|
|
42
|
+
files = selectCaseFiles(project.root, { agent: args.agent })
|
|
41
43
|
} catch (err) {
|
|
42
44
|
return { error: `a case file could not be read: ${(err as Error).message}`, status: 422 }
|
|
43
45
|
}
|
|
44
|
-
const total = files
|
|
46
|
+
const total = caseCount(files)
|
|
45
47
|
if (total === 0) return { error: 'no test cases to run', status: 400 }
|
|
46
48
|
|
|
47
49
|
const run: TestRun = {
|
|
@@ -61,6 +63,12 @@ export function createTestRuns(project: LocalProject, serviceUrl: string) {
|
|
|
61
63
|
void runCases({ serviceUrl, creds, files, live: run.live, onResult: (r) => run.results.push(r) })
|
|
62
64
|
.then((results) => {
|
|
63
65
|
run.status = results.every((r) => r.passed) ? 'passed' : 'failed'
|
|
66
|
+
// Each case's last run, as `frontera local test` keeps it: Forge's home marks the evaluations from it.
|
|
67
|
+
try {
|
|
68
|
+
recordRun(project, results)
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`[local-test-runs] could not keep the results: ${(err as Error).message}`)
|
|
71
|
+
}
|
|
64
72
|
})
|
|
65
73
|
.catch((err: Error) => {
|
|
66
74
|
run.status = 'error'
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { dirname, join } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { AUTOMATION_SDK_FILES } from '../automation-template'
|
|
6
|
+
import vendored from '../vendor/sdk-sources.json'
|
|
7
|
+
import { compareVersions } from './local-app-sdk'
|
|
8
|
+
import { readSnapshot } from './local-sync-snapshot'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The Functions SDK a project keeps in `vendor/frontera/` (what its Function
|
|
12
|
+
* tests and typecheck resolve `@frontera-sdk/functions` to), against this
|
|
13
|
+
* CLI's copy.
|
|
14
|
+
*
|
|
15
|
+
* The CLI writes those files (`local init`, a pull), so they are the CLI's to
|
|
16
|
+
* keep current — but a person may have edited one, and an edit is never
|
|
17
|
+
* overwritten unasked. So the CLI records what it wrote: the manifest
|
|
18
|
+
* `vendor/frontera/.frontera-sdk.json` holds each file's hash as written.
|
|
19
|
+
* A file still at that hash is the CLI's copy, unmodified, and `local dev`
|
|
20
|
+
* replaces it with this CLI's when they differ. A file that differs from the
|
|
21
|
+
* hash was edited and is kept; `frontera local sdk-refresh --force` replaces
|
|
22
|
+
* it. A project from a CLI that wrote no manifest cannot tell the two apart,
|
|
23
|
+
* so it is only told; `frontera local sdk-refresh` replaces the files that
|
|
24
|
+
* differ and keeps a copy of each one it replaces.
|
|
25
|
+
*
|
|
26
|
+
* Which way a difference points is said only when something records it: the
|
|
27
|
+
* manifest's version (a newer CLI wrote the copy), or, with no manifest, the
|
|
28
|
+
* platform release the project was pulled from (`.frontera/sync/remote.json`
|
|
29
|
+
* `platformVersion`) when it is newer than this CLI: its Functions were
|
|
30
|
+
* written against a newer SDK, so this CLI's copy would be a step back.
|
|
31
|
+
* Neither replaces a newer copy without `--force`; the advice is to update
|
|
32
|
+
* the CLI. With nothing to go by, the copy only "differs".
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export const SDK_MANIFEST = 'vendor/frontera/.frontera-sdk.json'
|
|
36
|
+
|
|
37
|
+
interface SdkManifest {
|
|
38
|
+
version: string
|
|
39
|
+
files: Record<string, string>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sha = (text: string) => createHash('sha256').update(text).digest('hex')
|
|
43
|
+
|
|
44
|
+
function cliFiles(): Record<string, string> {
|
|
45
|
+
const files = vendored.files as Record<string, string>
|
|
46
|
+
return Object.fromEntries(AUTOMATION_SDK_FILES.flatMap((rel) => (files[rel] === undefined ? [] : [[`vendor/${rel}`, files[rel]!]])))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readManifest(root: string): SdkManifest | null {
|
|
50
|
+
try {
|
|
51
|
+
const doc = JSON.parse(readFileSync(join(root, SDK_MANIFEST), 'utf8')) as Partial<SdkManifest>
|
|
52
|
+
return typeof doc.version === 'string' && doc.files && typeof doc.files === 'object' ? (doc as SdkManifest) : null
|
|
53
|
+
} catch {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The manifest for the files as this CLI writes them: what `writeSolutionScaffold` and a refresh leave beside them. */
|
|
59
|
+
export function sdkManifestText(files: Record<string, string> = cliFiles()): string {
|
|
60
|
+
return `${JSON.stringify({ version: vendored.sdkVersion, files: Object.fromEntries(Object.entries(files).map(([rel, text]) => [rel, sha(text)])) }, null, 2)}\n`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface VendoredSdkState {
|
|
64
|
+
/** The project keeps the SDK in vendor/frontera/. */
|
|
65
|
+
vendored: boolean
|
|
66
|
+
/** The SDK version the manifest says, or null without one. */
|
|
67
|
+
version: string | null
|
|
68
|
+
/** Files that differ from this CLI's copy and are the CLI's unmodified copy: safe to replace. */
|
|
69
|
+
outdated: string[]
|
|
70
|
+
/** Files that differ from this CLI's copy and were edited since the CLI wrote them (or, with no manifest, can't be told). */
|
|
71
|
+
edited: string[]
|
|
72
|
+
/** Files this CLI vendors that the project lacks. */
|
|
73
|
+
missing: string[]
|
|
74
|
+
/** No manifest: nothing records which SDK wrote the folder (a CLI from before manifests, or a hand-made copy). */
|
|
75
|
+
unknown: boolean
|
|
76
|
+
/**
|
|
77
|
+
* The copy is known or likely to be newer than this CLI's, so replacing it would go back:
|
|
78
|
+
* `manifest`, a newer CLI wrote it; `pull`, no manifest, and the project was pulled from a
|
|
79
|
+
* newer platform release. Null when nothing says so.
|
|
80
|
+
*/
|
|
81
|
+
newer: { by: 'manifest' | 'pull'; version: string } | null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** This CLI's SDK version (`1.52.28`). */
|
|
85
|
+
export const CLI_SDK = vendored.sdkVersion
|
|
86
|
+
|
|
87
|
+
/** The platform release the project was last pulled from, when a pull recorded it. */
|
|
88
|
+
function pulledFromVersion(root: string): string | null {
|
|
89
|
+
const v = readSnapshot(root)?.platformVersion
|
|
90
|
+
return typeof v === 'string' && /^\d+\.\d+/.test(v) ? v : null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** How the project's vendored SDK compares with this CLI's. */
|
|
94
|
+
export function vendoredSdkState(root: string): VendoredSdkState {
|
|
95
|
+
const mine = cliFiles()
|
|
96
|
+
const vendoredHere = existsSync(join(root, 'vendor', 'frontera'))
|
|
97
|
+
const manifest = readManifest(root)
|
|
98
|
+
const state: VendoredSdkState = { vendored: vendoredHere, version: manifest?.version ?? null, outdated: [], edited: [], missing: [], unknown: vendoredHere && !manifest, newer: null }
|
|
99
|
+
if (!vendoredHere) return state
|
|
100
|
+
for (const [rel, text] of Object.entries(mine)) {
|
|
101
|
+
const path = join(root, rel)
|
|
102
|
+
if (!existsSync(path)) {
|
|
103
|
+
state.missing.push(rel)
|
|
104
|
+
continue
|
|
105
|
+
}
|
|
106
|
+
const here = readFileSync(path, 'utf8')
|
|
107
|
+
if (here === text) continue
|
|
108
|
+
const written = manifest?.files[rel]
|
|
109
|
+
if (written && sha(here) === written) state.outdated.push(rel)
|
|
110
|
+
else state.edited.push(rel)
|
|
111
|
+
}
|
|
112
|
+
if (state.outdated.length || state.edited.length) {
|
|
113
|
+
if (manifest && compareVersions(manifest.version, CLI_SDK) > 0) state.newer = { by: 'manifest', version: manifest.version }
|
|
114
|
+
else if (!manifest) {
|
|
115
|
+
const pulled = pulledFromVersion(root)
|
|
116
|
+
if (pulled && compareVersions(pulled, CLI_SDK) > 0) state.newer = { by: 'pull', version: pulled }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return state
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Bring the vendored SDK to this CLI's copy: the files the CLI wrote and
|
|
124
|
+
* nobody changed, and the missing ones; with `force`, the edited ones too.
|
|
125
|
+
* Without a manifest every differing file counts as replaceable (nothing
|
|
126
|
+
* tells an edit apart) and a copy of each is kept under
|
|
127
|
+
* `.frontera/local/sdk-backup/`. Writes the manifest. Returns what changed.
|
|
128
|
+
*/
|
|
129
|
+
export function refreshVendoredSdk(root: string, opts: { force?: boolean; backupAll?: boolean; now?: Date } = {}): { replaced: string[]; added: string[]; kept: string[]; backup: string | null } {
|
|
130
|
+
const state = vendoredSdkState(root)
|
|
131
|
+
const mine = cliFiles()
|
|
132
|
+
const replaceEdited = opts.force === true || (state.unknown && opts.backupAll === true)
|
|
133
|
+
const replace = [...state.outdated, ...(replaceEdited ? state.edited : [])]
|
|
134
|
+
const kept = replaceEdited ? [] : state.edited
|
|
135
|
+
let backup: string | null = null
|
|
136
|
+
const toBackUp = replaceEdited ? state.edited : []
|
|
137
|
+
if (toBackUp.length) {
|
|
138
|
+
backup = join('.frontera', 'local', 'sdk-backup', (opts.now ?? new Date()).toISOString().replace(/[:.]/g, '-'))
|
|
139
|
+
for (const rel of toBackUp) {
|
|
140
|
+
const to = join(root, backup, rel)
|
|
141
|
+
mkdirSync(dirname(to), { recursive: true })
|
|
142
|
+
writeFileSync(to, readFileSync(join(root, rel)))
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const rel of [...replace, ...state.missing]) {
|
|
146
|
+
mkdirSync(dirname(join(root, rel)), { recursive: true })
|
|
147
|
+
writeFileSync(join(root, rel), mine[rel]!)
|
|
148
|
+
}
|
|
149
|
+
// The manifest records what the CLI wrote; a kept edit keeps the hash it was written at, so it stays "edited".
|
|
150
|
+
const previous = readManifest(root)?.files ?? {}
|
|
151
|
+
const recorded = Object.fromEntries(Object.entries(mine).map(([rel, text]) => [rel, kept.includes(rel) && previous[rel] ? previous[rel]! : sha(text)]))
|
|
152
|
+
mkdirSync(dirname(join(root, SDK_MANIFEST)), { recursive: true })
|
|
153
|
+
writeFileSync(join(root, SDK_MANIFEST), `${JSON.stringify({ version: vendored.sdkVersion, files: recorded }, null, 2)}\n`)
|
|
154
|
+
return { replaced: replace, added: state.missing, kept, backup }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* What `local dev` and `local check` say when the vendored SDK is not this
|
|
159
|
+
* CLI's, with the command that refreshes it; null when it is.
|
|
160
|
+
*/
|
|
161
|
+
export function vendoredSdkLine(state: VendoredSdkState): string | null {
|
|
162
|
+
if (!state.vendored) return null
|
|
163
|
+
const names = (list: string[]) => `${list.slice(0, 3).map((f) => f.replace(/^vendor\//, '')).join(', ')}${list.length > 3 ? ', …' : ''}`
|
|
164
|
+
const differs = (list: string[]) => `vendor/frontera/ (the Functions SDK the project's tests and typecheck use) differs from this CLI's copy (${CLI_SDK}) in ${list.length} file${list.length === 1 ? '' : 's'} (${names(list)})`
|
|
165
|
+
const changed = [...state.outdated, ...state.edited]
|
|
166
|
+
if (state.newer?.by === 'manifest') {
|
|
167
|
+
return `${differs(changed)}: a newer CLI wrote it (SDK ${state.newer.version}), so this CLI's copy would be a step back. It is kept: upgrade the Frontera CLI to ${state.newer.version} or later rather than refreshing (\`frontera local sdk-refresh --force\` replaces it with this CLI's older copy, keeping a copy of each file)`
|
|
168
|
+
}
|
|
169
|
+
if (state.unknown && state.newer?.by === 'pull' && state.edited.length) {
|
|
170
|
+
return `${differs(state.edited)}. Nothing records which SDK wrote it (no vendor/frontera/.frontera-sdk.json), and the project was pulled from platform ${state.newer.version}, newer than this CLI: refreshing would replace what may be a newer copy with an older one. Upgrade the Frontera CLI to ${state.newer.version} or later, then \`frontera local sdk-refresh\` (\`--force\` refreshes with this CLI's copy anyway, keeping a copy of each file under .frontera/local/sdk-backup/)`
|
|
171
|
+
}
|
|
172
|
+
if (state.unknown && state.edited.length) {
|
|
173
|
+
return `${differs(state.edited)}. Nothing records which SDK wrote it (no vendor/frontera/.frontera-sdk.json), so it may be older or newer than this CLI's. If the project's tests or typecheck fail inside vendor/frontera/, \`frontera local sdk-refresh\` replaces those files with this CLI's copy and keeps a copy of each under .frontera/local/sdk-backup/`
|
|
174
|
+
}
|
|
175
|
+
if (state.edited.length) {
|
|
176
|
+
return `vendor/frontera/ has ${state.edited.length} file${state.edited.length === 1 ? '' : 's'} edited since the CLI wrote ${state.edited.length === 1 ? 'it' : 'them'} (${names(state.edited)}), which differ from this CLI's SDK (${vendored.sdkVersion}): kept. \`frontera local sdk-refresh --force\` replaces them and keeps a copy`
|
|
177
|
+
}
|
|
178
|
+
if (state.missing.length || state.outdated.length) {
|
|
179
|
+
const behind = state.version && compareVersions(state.version, CLI_SDK) < 0
|
|
180
|
+
return `vendor/frontera/ ${behind ? `is behind this CLI's SDK (it was written for ${state.version}; this CLI's is ${CLI_SDK})` : `differs from this CLI's SDK (${CLI_SDK})`}: \`frontera local sdk-refresh\` brings it to this CLI's copy`
|
|
181
|
+
}
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
@@ -107,11 +107,11 @@ async function setEnabled(serviceUrl: string, creds: LocalCredentials, slug: str
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
|
-
* After a push: promote and switch triggers as in the workspace, for a
|
|
110
|
+
* After a push (as `version`): promote and switch triggers as in the workspace, for a
|
|
111
111
|
* workflow the lock says is live there. Returns lines for the apply's output;
|
|
112
112
|
* a `warning:` line is an adaptation in the build.
|
|
113
113
|
*/
|
|
114
|
-
export async function mirrorWorkflowState(project: LocalProject, slug: string, file: string, serviceUrl: string, creds: LocalCredentials): Promise<string[]> {
|
|
114
|
+
export async function mirrorWorkflowState(project: LocalProject, slug: string, file: string, serviceUrl: string, creds: LocalCredentials, version?: number): Promise<string[]> {
|
|
115
115
|
const state = readLock(project.root)?.items[`workflow/${slug}`]?.state
|
|
116
116
|
if (!state?.live || !creds.workspaceKey) return []
|
|
117
117
|
let doc: unknown = null
|
|
@@ -121,7 +121,7 @@ export async function mirrorWorkflowState(project: LocalProject, slug: string, f
|
|
|
121
121
|
return []
|
|
122
122
|
}
|
|
123
123
|
const lines: string[] = []
|
|
124
|
-
const promoted = await promoteWorkflow(serviceUrl, creds, slug)
|
|
124
|
+
const promoted = await promoteWorkflow(serviceUrl, creds, slug, version)
|
|
125
125
|
if (promoted) lines.push(` promoted workflow ${slug} v${promoted}: it is live in the workspace`)
|
|
126
126
|
const want = localTriggersOn(doc, state.enabled)
|
|
127
127
|
const failed = await setEnabled(serviceUrl, creds, slug, want.on)
|
|
@@ -129,3 +129,16 @@ export async function mirrorWorkflowState(project: LocalProject, slug: string, f
|
|
|
129
129
|
else if (want.why) lines.push(` warning: ${want.why}`)
|
|
130
130
|
return lines
|
|
131
131
|
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* After a push (as `version`) of a workflow that already has a live version
|
|
135
|
+
* here (an Agent's binding promoted it, or it is live in the workspace): the
|
|
136
|
+
* pushed version is promoted too. An Agent, a webhook and a schedule start the
|
|
137
|
+
* live version, and on this runtime that has to be what the file says, not
|
|
138
|
+
* the push before it. A workflow that is only drafts stays drafts.
|
|
139
|
+
*/
|
|
140
|
+
export async function keepLiveCurrent(serviceUrl: string, creds: LocalCredentials, slug: string, version: number | undefined): Promise<string[]> {
|
|
141
|
+
if (!version) return []
|
|
142
|
+
const promoted = await promoteWorkflow(serviceUrl, creds, slug, version, { onlyIfLive: true })
|
|
143
|
+
return promoted ? [` promoted workflow ${slug} v${promoted}: it has a live version here, which Agents, webhooks and schedules start, and that now matches the file`] : []
|
|
144
|
+
}
|