forgeos 0.1.0-alpha.33 → 0.1.0-alpha.34
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/AGENTS.md +1 -1
- package/CHANGELOG.md +15 -0
- package/docs/changelog.md +27 -0
- package/package.json +1 -1
- package/src/forge/_generated/releaseManifest.json +1 -1
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/cli/changed.ts +102 -8
- package/src/forge/cli/commands.ts +116 -4
- package/src/forge/cli/dev.ts +228 -0
- package/src/forge/cli/handoff.ts +17 -0
- package/src/forge/cli/main.ts +36 -0
- package/src/forge/cli/new.ts +4 -0
- package/src/forge/cli/parse.ts +24 -6
- package/src/forge/cli/workos.ts +250 -23
- package/src/forge/compiler/app-graph/build.ts +44 -1
- package/src/forge/compiler/client-sdk/render-client.ts +31 -0
- package/src/forge/compiler/data-graph/parse.ts +17 -1
- package/src/forge/compiler/data-graph/sql/ddl.ts +24 -13
- package/src/forge/compiler/diagnostics/codes.ts +6 -0
- package/src/forge/compiler/diagnostics/create.ts +14 -0
- package/src/forge/compiler/integration/plan.ts +6 -0
- package/src/forge/compiler/integration/render.ts +9 -0
- package/src/forge/compiler/integration/templates/render.ts +2 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +165 -99
- package/src/forge/compiler/orchestrator/plan.ts +7 -0
- package/src/forge/delta/status.ts +10 -1
- package/src/forge/impact/index.ts +158 -1
- package/src/forge/impact/types.ts +27 -1
- package/src/forge/react/index.ts +19 -2
- package/src/forge/runtime/auth/resolve.ts +33 -3
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/server.ts +5 -1
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +16 -1
- package/src/forge/workspace/git-summary.ts +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.34 input=ce9cf8b6c553d7de8113eb2a388d269cb8c90d4bb15c013a7ab907ef207619b9 content=721818a6f9a664aa898092d4aa5cd68cbbd8ad6e150c7d3ad3ef785a2e3fcf53
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.34
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden the ForgeOS app-building DX after the Vendor Access field test.
|
|
8
|
+
|
|
9
|
+
- Make `forge workos doctor` and `forge workos seed --dry-run` app-aware by deriving active permissions and resource types from generated policies, data graph, agent contract, authored WorkOS policies, and `workos-seed.yml` instead of assuming onboarding-specific slugs.
|
|
10
|
+
- Add strong diagnostics for unnamed runtime default exports so `export default command(...)` reports `FORGE_RUNTIME_EXPORT_NAME_REQUIRED` with a named-export fix hint before generated API, frontend bindings, or capability maps drift.
|
|
11
|
+
- Extend local `devAuth` for React, Vue, generated clients, and runtime auth parsing with WorkOS-like `permissions`, `roles`, `claims`, `organizationId`, and `organizationMembershipId`.
|
|
12
|
+
- Add nullable timestamp schema support through `timestamp?` / `nullable("timestamp")` and teach `forge check` to flag empty timestamp literals in commands, queries, and liveQueries.
|
|
13
|
+
- Add `forge dev --detach`, `forge dev status`, `forge dev stop`, and command-specific `forge dev --help` with explicit DB/port examples for agent-run app previews.
|
|
14
|
+
- Add `forge changed --commit-ready` and `forge handoff --commit-ready` so agents can stage exactly authored commit files while excluding generated and operational artifacts.
|
|
15
|
+
- Add `forge test authz` for a cheap generated-contract proof of tenant scope, policy bindings, and capability-map authz coverage.
|
|
16
|
+
- Expose static UI/UX readiness through `forge inspect ui --ergonomics`.
|
|
17
|
+
|
|
3
18
|
## 0.1.0-alpha.33
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,33 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.34
|
|
10
|
+
|
|
11
|
+
- Hardened the ForgeOS app-building DX after the Vendor Access field test.
|
|
12
|
+
- Made `forge workos doctor` and `forge workos seed --dry-run` app-aware by
|
|
13
|
+
deriving active permissions and resource types from generated policies, data
|
|
14
|
+
graph, agent contract, authored WorkOS policies, and `workos-seed.yml`
|
|
15
|
+
instead of assuming onboarding-specific slugs.
|
|
16
|
+
- Added strong diagnostics for unnamed runtime default exports so
|
|
17
|
+
`export default command(...)` reports `FORGE_RUNTIME_EXPORT_NAME_REQUIRED`
|
|
18
|
+
with a named-export fix hint before generated API, frontend bindings, or
|
|
19
|
+
capability maps drift.
|
|
20
|
+
- Extended local `devAuth` for React, Vue, generated clients, and runtime auth
|
|
21
|
+
parsing with WorkOS-like `permissions`, `roles`, `claims`, `organizationId`,
|
|
22
|
+
and `organizationMembershipId`.
|
|
23
|
+
- Added nullable timestamp schema support through `timestamp?` /
|
|
24
|
+
`nullable("timestamp")` and taught `forge check` to flag empty timestamp
|
|
25
|
+
literals in commands, queries, and liveQueries.
|
|
26
|
+
- Added `forge dev --detach`, `forge dev status`, `forge dev stop`, and
|
|
27
|
+
command-specific `forge dev --help` with explicit DB/port examples for
|
|
28
|
+
agent-run app previews.
|
|
29
|
+
- Added `forge changed --commit-ready` and `forge handoff --commit-ready` so
|
|
30
|
+
agents can stage exactly authored commit files while excluding generated and
|
|
31
|
+
operational artifacts.
|
|
32
|
+
- Added `forge test authz` for a cheap generated-contract proof of tenant
|
|
33
|
+
scope, policy bindings, and capability-map authz coverage.
|
|
34
|
+
- Exposed static UI/UX readiness through `forge inspect ui --ergonomics`.
|
|
35
|
+
|
|
9
36
|
## 0.1.0-alpha.33
|
|
10
37
|
|
|
11
38
|
- Fixed the Nuxt template smoke by adding the explicit `vue-tsc` dev
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.
|
|
1
|
+
{"defaultProvider":"local","diagnostics":[],"env":{"deployEnv":"FORGE_DEPLOY_ENV","deployId":"FORGE_DEPLOY_ID","publicReleaseId":"NEXT_PUBLIC_FORGE_RELEASE_ID","releaseId":"FORGE_RELEASE_ID"},"gitSha":"unknown","optionalProviders":["local","sentry-compatible","sentry","glitchtip","bugsink","otel","custom"],"packageName":"forgeos","packageVersion":"0.1.0-alpha.34","releaseId":"forgeos@0.1.0-alpha.34+unknown","schemaVersion":"0.1.0"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.34 input=ce9cf8b6c553d7de8113eb2a388d269cb8c90d4bb15c013a7ab907ef207619b9 content=1a3f239bc322349796830461349167bb154a27e57fb1f10868c788194b3be97e
|
|
2
2
|
export const releaseManifest = {
|
|
3
3
|
"defaultProvider": "local",
|
|
4
4
|
"diagnostics": [],
|
|
@@ -19,7 +19,7 @@ export const releaseManifest = {
|
|
|
19
19
|
"custom"
|
|
20
20
|
],
|
|
21
21
|
"packageName": "forgeos",
|
|
22
|
-
"packageVersion": "0.1.0-alpha.
|
|
23
|
-
"releaseId": "forgeos@0.1.0-alpha.
|
|
22
|
+
"packageVersion": "0.1.0-alpha.34",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.34+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
package/src/forge/cli/changed.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
1
2
|
import type { CategorizedFileSummary, DiffPlan } from "../workspace/change-summary.ts";
|
|
2
3
|
import {
|
|
3
4
|
buildDiffPlanFromChangeSummary,
|
|
5
|
+
categorizeFiles,
|
|
4
6
|
filterCategorizedSummary,
|
|
5
7
|
summarizeChangeTypes,
|
|
6
8
|
} from "../workspace/change-summary.ts";
|
|
7
9
|
import { forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
|
|
8
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
buildWorkspaceGitSummary,
|
|
12
|
+
listWorkspaceFiles,
|
|
13
|
+
workspaceChangeClassifier,
|
|
14
|
+
type WorkspaceGitSummary,
|
|
15
|
+
} from "../workspace/git-summary.ts";
|
|
9
16
|
|
|
10
17
|
export interface ChangedCommandResult {
|
|
11
18
|
ok: boolean;
|
|
@@ -148,6 +155,89 @@ function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
|
148
155
|
];
|
|
149
156
|
}
|
|
150
157
|
|
|
158
|
+
function parseStatusPath(line: string): string {
|
|
159
|
+
const raw = line.slice(2).trimStart();
|
|
160
|
+
const renamed = raw.split(" -> ");
|
|
161
|
+
return (renamed[renamed.length - 1] ?? raw).replace(/\\/g, "/");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function gitStatusFiles(workspaceRoot: string): {
|
|
165
|
+
changed: string[];
|
|
166
|
+
staged: string[];
|
|
167
|
+
unstaged: string[];
|
|
168
|
+
untracked: string[];
|
|
169
|
+
} | null {
|
|
170
|
+
const result = spawnSync("git", ["status", "--porcelain=v1", "-uall"], {
|
|
171
|
+
cwd: workspaceRoot,
|
|
172
|
+
encoding: "utf8",
|
|
173
|
+
windowsHide: true,
|
|
174
|
+
});
|
|
175
|
+
if (result.status !== 0) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const staged = new Set<string>();
|
|
179
|
+
const unstaged = new Set<string>();
|
|
180
|
+
const untracked = new Set<string>();
|
|
181
|
+
for (const line of result.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
182
|
+
const x = line[0] ?? " ";
|
|
183
|
+
const y = line[1] ?? " ";
|
|
184
|
+
const file = parseStatusPath(line);
|
|
185
|
+
if (x === "?" && y === "?") {
|
|
186
|
+
untracked.add(file);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (x !== " ") {
|
|
190
|
+
staged.add(file);
|
|
191
|
+
}
|
|
192
|
+
if (y !== " ") {
|
|
193
|
+
unstaged.add(file);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
changed: [...new Set([...staged, ...unstaged, ...untracked])].sort(),
|
|
198
|
+
staged: [...staged].sort(),
|
|
199
|
+
unstaged: [...unstaged].sort(),
|
|
200
|
+
untracked: [...untracked].sort(),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function isCommitReadyPath(file: string): boolean {
|
|
205
|
+
const lower = file.replace(/\\/g, "/").replace(/^\.\//, "").toLowerCase();
|
|
206
|
+
return !(
|
|
207
|
+
lower.startsWith(".forge/") ||
|
|
208
|
+
lower.startsWith(".codex/") ||
|
|
209
|
+
lower.startsWith("src/forge/_generated/") ||
|
|
210
|
+
lower === "forge.lock" ||
|
|
211
|
+
lower.endsWith("/forge.lock")
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function commitReadySummary(workspaceRoot: string): {
|
|
216
|
+
changed: CategorizedFileSummary;
|
|
217
|
+
staged: CategorizedFileSummary;
|
|
218
|
+
unstaged: CategorizedFileSummary;
|
|
219
|
+
untracked: CategorizedFileSummary;
|
|
220
|
+
files: string[];
|
|
221
|
+
} {
|
|
222
|
+
const classifier = workspaceChangeClassifier(workspaceRoot);
|
|
223
|
+
const status = gitStatusFiles(workspaceRoot) ?? {
|
|
224
|
+
changed: listWorkspaceFiles(workspaceRoot),
|
|
225
|
+
staged: [],
|
|
226
|
+
unstaged: [],
|
|
227
|
+
untracked: listWorkspaceFiles(workspaceRoot),
|
|
228
|
+
};
|
|
229
|
+
const filter = (files: string[]) =>
|
|
230
|
+
files.filter((file) => isCommitReadyPath(file) && !["generated", "operational"].includes(classifier(file)));
|
|
231
|
+
const changed = filter(status.changed);
|
|
232
|
+
return {
|
|
233
|
+
changed: categorizeFiles(changed, 12, classifier),
|
|
234
|
+
staged: categorizeFiles(filter(status.staged), 12, classifier),
|
|
235
|
+
unstaged: categorizeFiles(filter(status.unstaged), 12, classifier),
|
|
236
|
+
untracked: categorizeFiles(filter(status.untracked), 12, classifier),
|
|
237
|
+
files: changed,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
151
241
|
function buildReviewFocus(humanChanges: HumanChangeSummary, derivedChanges: DerivedChangeSummary): ReviewFocus {
|
|
152
242
|
const authoredOrder = ([
|
|
153
243
|
"source",
|
|
@@ -202,7 +292,7 @@ function buildGeneratedChangeExplanation(
|
|
|
202
292
|
};
|
|
203
293
|
}
|
|
204
294
|
|
|
205
|
-
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean; reviewOnly?: boolean } = {}): ChangedCommandResult {
|
|
295
|
+
export function runChangedCommand(workspaceRoot: string, options: { authoredOnly?: boolean; reviewOnly?: boolean; commitReady?: boolean } = {}): ChangedCommandResult {
|
|
206
296
|
const git = buildWorkspaceGitSummary(workspaceRoot);
|
|
207
297
|
const changed = git.changeSummary.changed;
|
|
208
298
|
const humanChanges = selectHumanChangeSummary(changed);
|
|
@@ -215,16 +305,19 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
215
305
|
const reviewStaged = filterCategorizedSummary(git.changeSummary.staged, [...REVIEW_CHANGE_TYPES]);
|
|
216
306
|
const reviewUnstaged = filterCategorizedSummary(git.changeSummary.unstaged, [...REVIEW_CHANGE_TYPES]);
|
|
217
307
|
const reviewUntracked = filterCategorizedSummary(git.changeSummary.untracked, [...REVIEW_CHANGE_TYPES]);
|
|
308
|
+
const commitReady = options.commitReady ? commitReadySummary(workspaceRoot) : null;
|
|
218
309
|
const viewHumanChanges = options.reviewOnly
|
|
219
310
|
? selectHumanChangeSummary(reviewChanged)
|
|
311
|
+
: options.commitReady && commitReady
|
|
312
|
+
? selectHumanChangeSummary(commitReady.changed)
|
|
220
313
|
: options.authoredOnly
|
|
221
314
|
? selectHumanChangeSummary(authoredChanged)
|
|
222
315
|
: humanChanges;
|
|
223
|
-
const viewChanged = options.reviewOnly ? reviewChanged : options.authoredOnly ? authoredChanged : changed;
|
|
224
|
-
const viewStaged = options.reviewOnly ? reviewStaged : options.authoredOnly ? authoredStaged : git.changeSummary.staged;
|
|
225
|
-
const viewUnstaged = options.reviewOnly ? reviewUnstaged : options.authoredOnly ? authoredUnstaged : git.changeSummary.unstaged;
|
|
226
|
-
const viewUntracked = options.reviewOnly ? reviewUntracked : options.authoredOnly ? authoredUntracked : git.changeSummary.untracked;
|
|
227
|
-
const viewDerivedChanges: DerivedChangeSummary = options.authoredOnly || options.reviewOnly
|
|
316
|
+
const viewChanged = options.reviewOnly ? reviewChanged : options.commitReady && commitReady ? commitReady.changed : options.authoredOnly ? authoredChanged : changed;
|
|
317
|
+
const viewStaged = options.reviewOnly ? reviewStaged : options.commitReady && commitReady ? commitReady.staged : options.authoredOnly ? authoredStaged : git.changeSummary.staged;
|
|
318
|
+
const viewUnstaged = options.reviewOnly ? reviewUnstaged : options.commitReady && commitReady ? commitReady.unstaged : options.authoredOnly ? authoredUnstaged : git.changeSummary.unstaged;
|
|
319
|
+
const viewUntracked = options.reviewOnly ? reviewUntracked : options.commitReady && commitReady ? commitReady.untracked : options.authoredOnly ? authoredUntracked : git.changeSummary.untracked;
|
|
320
|
+
const viewDerivedChanges: DerivedChangeSummary = options.authoredOnly || options.reviewOnly || options.commitReady
|
|
228
321
|
? { total: 0, generated: { count: 0, sample: [], hidden: 0 } }
|
|
229
322
|
: derivedChanges;
|
|
230
323
|
const risks = buildRisks(git);
|
|
@@ -245,7 +338,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
245
338
|
commit: git.commit,
|
|
246
339
|
workspaceMode: git.workspaceMode ?? (git.available ? "git" : "nonGit"),
|
|
247
340
|
tracking: git.tracking ?? git.source,
|
|
248
|
-
view: options.reviewOnly ? "review" : options.authoredOnly ? "authored" : "all",
|
|
341
|
+
view: options.reviewOnly ? "review" : options.commitReady ? "commit-ready" : options.authoredOnly ? "authored" : "all",
|
|
249
342
|
changedFiles: viewChanged.total.count,
|
|
250
343
|
humanFiles: viewHumanChanges.total,
|
|
251
344
|
generatedFiles: viewDerivedChanges.total,
|
|
@@ -274,6 +367,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
274
367
|
reviewFocus,
|
|
275
368
|
generatedExplanation,
|
|
276
369
|
diffPlan,
|
|
370
|
+
...(commitReady ? { commitReady: { files: commitReady.files, count: commitReady.files.length } } : {}),
|
|
277
371
|
risks,
|
|
278
372
|
advisories,
|
|
279
373
|
recommendedCommands,
|
|
@@ -4,13 +4,18 @@ import { join, resolve } from "node:path";
|
|
|
4
4
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
5
5
|
import { classify } from "../compiler/classifier/classify.ts";
|
|
6
6
|
import { buildRuntimeMatrix } from "../compiler/classifier/runtime-matrix.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
FORGE_DB_EMPTY_TIMESTAMP_LITERAL,
|
|
9
|
+
FORGE_RELEASE_PACKAGE_PACK_FAILED,
|
|
10
|
+
} from "../compiler/diagnostics/codes.ts";
|
|
8
11
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
9
12
|
import { forgeAdd } from "../compiler/integration/add.ts";
|
|
10
13
|
import { checkImportGuards } from "../compiler/guards/check-import-guards.ts";
|
|
11
14
|
import { checkDirectProcessEnvUsage } from "../compiler/guards/check-process-env.ts";
|
|
12
15
|
import { checkAiUsageInApp } from "../compiler/guards/check-ai-usage.ts";
|
|
13
16
|
import { checkQueryUsageInApp } from "../compiler/guards/check-query-usage.ts";
|
|
17
|
+
import { buildDataGraph } from "../compiler/data-graph/build.ts";
|
|
18
|
+
import { buildSqlPlan } from "../compiler/data-graph/sql/ddl.ts";
|
|
14
19
|
import { loadSecretRegistry } from "../runtime/secrets/check.ts";
|
|
15
20
|
import { run } from "../compiler/orchestrator/run.ts";
|
|
16
21
|
import {
|
|
@@ -1003,12 +1008,70 @@ async function loadRuntimeMatrixForCheck(
|
|
|
1003
1008
|
return buildRuntimeMatrix(classified);
|
|
1004
1009
|
}
|
|
1005
1010
|
|
|
1011
|
+
function normalizeSchemaType(type: string): string {
|
|
1012
|
+
return type.trim().replace(/\?$/, "").toLowerCase();
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function timestampFieldNames(dataGraph: DataGraph): Set<string> {
|
|
1016
|
+
const fields = new Set<string>();
|
|
1017
|
+
for (const table of dataGraph.tables) {
|
|
1018
|
+
for (const field of table.fields) {
|
|
1019
|
+
if (["timestamp", "timestamptz"].includes(normalizeSchemaType(field.type))) {
|
|
1020
|
+
fields.add(field.name);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return fields;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
function checkEmptyTimestampLiterals(
|
|
1028
|
+
appGraph: Awaited<ReturnType<typeof buildAppGraphForSession>>,
|
|
1029
|
+
dataGraph: DataGraph,
|
|
1030
|
+
): ReturnType<typeof createDiagnostic>[] {
|
|
1031
|
+
const fields = timestampFieldNames(dataGraph);
|
|
1032
|
+
if (fields.size === 0) {
|
|
1033
|
+
return [];
|
|
1034
|
+
}
|
|
1035
|
+
const diagnostics: ReturnType<typeof createDiagnostic>[] = [];
|
|
1036
|
+
const runtimeKinds = new Set(["command", "query", "liveQuery", "action", "workflow"]);
|
|
1037
|
+
for (const symbol of appGraph.symbols) {
|
|
1038
|
+
if (!runtimeKinds.has(symbol.kind)) {
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
const sourceSlice = typeof symbol.meta.sourceSlice === "string" ? symbol.meta.sourceSlice : "";
|
|
1042
|
+
if (!sourceSlice) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
for (const field of fields) {
|
|
1046
|
+
const escaped = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1047
|
+
const pattern = new RegExp(`\\b${escaped}\\s*:\\s*(["']{2})`);
|
|
1048
|
+
const emptyTemplatePattern = new RegExp(`\\b${escaped}\\s*:\\s*\`\``);
|
|
1049
|
+
if (!pattern.test(sourceSlice) && !emptyTemplatePattern.test(sourceSlice)) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
diagnostics.push(
|
|
1053
|
+
createDiagnostic({
|
|
1054
|
+
severity: "error",
|
|
1055
|
+
code: FORGE_DB_EMPTY_TIMESTAMP_LITERAL,
|
|
1056
|
+
message:
|
|
1057
|
+
`Forge timestamp field '${field}' is assigned an empty string in ${symbol.kind} '${symbol.name}'.`,
|
|
1058
|
+
file: symbol.file,
|
|
1059
|
+
span: symbol.span,
|
|
1060
|
+
}),
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return diagnostics;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1006
1067
|
export async function runCheckCommand(
|
|
1007
1068
|
workspaceRoot: string,
|
|
1008
1069
|
options?: { strictSecrets?: boolean },
|
|
1009
1070
|
): Promise<GenerateResult> {
|
|
1010
1071
|
const session = getCompileSession(workspaceRoot);
|
|
1011
1072
|
const appGraph = await buildAppGraphForSession(session);
|
|
1073
|
+
const dataGraph = buildDataGraph(appGraph);
|
|
1074
|
+
const sqlPlan = buildSqlPlan(dataGraph);
|
|
1012
1075
|
|
|
1013
1076
|
const matrix = await loadRuntimeMatrixForCheck(workspaceRoot);
|
|
1014
1077
|
const guardDiagnostics = checkImportGuards(appGraph.moduleGraph, matrix);
|
|
@@ -1029,12 +1092,17 @@ export async function runCheckCommand(
|
|
|
1029
1092
|
`${GENERATED_DIR}/capabilityMap.json`,
|
|
1030
1093
|
)?.diagnostics ?? [];
|
|
1031
1094
|
const externalDiagnostics = buildExternalServiceGraph(workspaceRoot).diagnostics;
|
|
1095
|
+
const timestampLiteralDiagnostics = checkEmptyTimestampLiterals(appGraph, dataGraph);
|
|
1032
1096
|
|
|
1033
1097
|
const allDiagnostics = [
|
|
1098
|
+
...appGraph.diagnostics,
|
|
1099
|
+
...dataGraph.diagnostics,
|
|
1100
|
+
...sqlPlan.diagnostics,
|
|
1034
1101
|
...guardDiagnostics,
|
|
1035
1102
|
...processEnvDiagnostics,
|
|
1036
1103
|
...aiDiagnostics,
|
|
1037
1104
|
...queryDiagnostics,
|
|
1105
|
+
...timestampLiteralDiagnostics,
|
|
1038
1106
|
...frontendDiagnostics,
|
|
1039
1107
|
...capabilityDiagnostics,
|
|
1040
1108
|
...externalDiagnostics,
|
|
@@ -1314,7 +1382,7 @@ function runManifestCommand(command: Extract<ForgeCommand, { kind: "manifest" }>
|
|
|
1314
1382
|
export async function runInspectCommand(
|
|
1315
1383
|
target: InspectTarget,
|
|
1316
1384
|
workspaceRoot: string,
|
|
1317
|
-
options: { full?: boolean; brief?: boolean } = {},
|
|
1385
|
+
options: { full?: boolean; brief?: boolean; ergonomics?: boolean } = {},
|
|
1318
1386
|
): Promise<InspectResult> {
|
|
1319
1387
|
const dataPaths: Partial<Record<InspectTarget, string>> = {
|
|
1320
1388
|
app: `${GENERATED_DIR}/appGraph.json`,
|
|
@@ -1411,6 +1479,47 @@ export async function runInspectCommand(
|
|
|
1411
1479
|
};
|
|
1412
1480
|
}
|
|
1413
1481
|
|
|
1482
|
+
if (target === "ui" && options.ergonomics) {
|
|
1483
|
+
const result = await runUiCommand({
|
|
1484
|
+
subcommand: "audit",
|
|
1485
|
+
workspaceRoot,
|
|
1486
|
+
json: true,
|
|
1487
|
+
headed: false,
|
|
1488
|
+
browser: "chromium",
|
|
1489
|
+
trace: "off",
|
|
1490
|
+
screenshot: "off",
|
|
1491
|
+
video: "off",
|
|
1492
|
+
baseUrl: "http://127.0.0.1:3000",
|
|
1493
|
+
runtimeUrl: "http://127.0.0.1:3765",
|
|
1494
|
+
reuseServers: false,
|
|
1495
|
+
startServers: false,
|
|
1496
|
+
all: true,
|
|
1497
|
+
changed: false,
|
|
1498
|
+
ci: false,
|
|
1499
|
+
timeoutMs: 30_000,
|
|
1500
|
+
});
|
|
1501
|
+
return {
|
|
1502
|
+
target,
|
|
1503
|
+
data: {
|
|
1504
|
+
schemaVersion: "0.1.0",
|
|
1505
|
+
mode: "ergonomics",
|
|
1506
|
+
ok: result.ok,
|
|
1507
|
+
manifest: result.manifest,
|
|
1508
|
+
scenarios: result.scenarios ?? [],
|
|
1509
|
+
diagnostics: result.diagnostics,
|
|
1510
|
+
nextActions: [
|
|
1511
|
+
"forge ui audit --json",
|
|
1512
|
+
"forge inspect frontend --json",
|
|
1513
|
+
"forge inspect capabilities --json",
|
|
1514
|
+
],
|
|
1515
|
+
},
|
|
1516
|
+
warnings: result.diagnostics.filter((diagnostic) => diagnostic.severity === "warning"),
|
|
1517
|
+
errors: result.diagnostics.filter((diagnostic) => diagnostic.severity === "error"),
|
|
1518
|
+
exitCode: result.exitCode,
|
|
1519
|
+
failureKind: result.exitCode === 0 ? undefined : "ui_ergonomics",
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1414
1523
|
if (target === "imported") {
|
|
1415
1524
|
const result = inspectBrownfieldImport(workspaceRoot);
|
|
1416
1525
|
return {
|
|
@@ -2106,6 +2215,7 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2106
2215
|
const result = runChangedCommand(command.workspaceRoot, {
|
|
2107
2216
|
authoredOnly: command.authoredOnly,
|
|
2108
2217
|
reviewOnly: command.reviewOnly,
|
|
2218
|
+
commitReady: command.commitReady,
|
|
2109
2219
|
});
|
|
2110
2220
|
if (command.json) {
|
|
2111
2221
|
process.stdout.write(formatJsonResult(result.data));
|
|
@@ -2203,8 +2313,8 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2203
2313
|
case "inspect": {
|
|
2204
2314
|
const result = await runInspectCommand(
|
|
2205
2315
|
command.target,
|
|
2206
|
-
|
|
2207
|
-
{ full: command.full, brief: command.brief },
|
|
2316
|
+
command.workspaceRoot,
|
|
2317
|
+
{ full: command.full, brief: command.brief, ergonomics: command.ergonomics },
|
|
2208
2318
|
);
|
|
2209
2319
|
if (command.json) {
|
|
2210
2320
|
process.stdout.write(formatJsonResult(buildInspectJson(result)));
|
|
@@ -2346,6 +2456,8 @@ export async function executeCommand(command: ForgeCommand): Promise<number> {
|
|
|
2346
2456
|
telemetry: command.telemetry,
|
|
2347
2457
|
envFile: command.envFile,
|
|
2348
2458
|
skipStartupConsole: command.skipStartupConsole,
|
|
2459
|
+
detach: command.detach,
|
|
2460
|
+
lifecycle: command.lifecycle,
|
|
2349
2461
|
});
|
|
2350
2462
|
return result.exitCode;
|
|
2351
2463
|
}
|