forgeos 0.1.0-alpha.25 → 0.1.0-alpha.26
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 +11 -0
- package/adapters/java/target/forge-java-adapter-0.1.0-alpha.11.jar +0 -0
- package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar +0 -0
- package/docs/changelog.md +9 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11-all.jar +0 -0
- package/examples/java-billing/target/java-billing-0.1.0-alpha.11.jar +0 -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/agent-memory/bridge.ts +4 -1
- package/src/forge/cli/changed.ts +7 -6
- package/src/forge/cli/handoff.ts +5 -2
- package/src/forge/compiler/agent-contract/build.ts +50 -0
- package/src/forge/delta/recorder.ts +1 -1
- package/src/forge/delta/store.ts +36 -3
- package/src/forge/make/index.ts +11 -2
- package/src/forge/version.ts +1 -1
- package/src/forge/workspace/git-summary.ts +57 -8
package/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// @forge-generated generator=0.1.0-alpha.
|
|
1
|
+
// @forge-generated generator=0.1.0-alpha.26 input=23c00c5407aab4088b55b49905f2b195667a75b8544fa180786ac637ee67e1e5 content=0d493cf0e41b71cb652d5e0e1b0c1f83d2a1281b748321f0b00f0773ba93074e
|
|
2
2
|
# AGENTS.md
|
|
3
3
|
|
|
4
4
|
<!-- forge-generated:start -->
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# forgeos
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.26
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Harden the field-demo loop after the Team Onboarding app exercise.
|
|
8
|
+
|
|
9
|
+
- Let `forge changed` and `forge handoff` summarize non-git workspaces with a filesystem inventory instead of reporting zero useful changes.
|
|
10
|
+
- Keep `forge make resource` global by default unless a tenants table exists or `--tenant-scoped` is explicit.
|
|
11
|
+
- Expand capability-map table detection for aliased `ctx.db` usage.
|
|
12
|
+
- Wait through short-lived DeltaDB writer locks before reporting `FORGE_DELTA_BUSY`.
|
|
13
|
+
|
|
3
14
|
## Unreleased
|
|
4
15
|
|
|
5
16
|
## 0.1.0-alpha.25
|
|
Binary file
|
package/adapters/java-spring-boot-starter/target/forge-java-spring-boot-starter-0.1.0-alpha.11.jar
CHANGED
|
Binary file
|
package/docs/changelog.md
CHANGED
|
@@ -6,6 +6,15 @@ The canonical source file in the repository is `CHANGELOG.md`.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
## 0.1.0-alpha.26
|
|
10
|
+
|
|
11
|
+
- Hardened the field-demo loop after the Team Onboarding app exercise:
|
|
12
|
+
non-git workspaces now get a filesystem-backed `changed`/`handoff` summary,
|
|
13
|
+
`forge make resource` stays global unless tenant scope is explicit or already
|
|
14
|
+
modeled, capability-map extraction sees aliased `ctx.db` table usage, and
|
|
15
|
+
Agent Memory waits through short-lived DeltaDB writer locks before reporting
|
|
16
|
+
`FORGE_DELTA_BUSY`.
|
|
17
|
+
|
|
9
18
|
## 0.1.0-alpha.25
|
|
10
19
|
|
|
11
20
|
- Hardened DeltaDB and Agent Memory for concurrent `forge dev` usage:
|
|
Binary file
|
|
Binary file
|
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.26","releaseId":"forgeos@0.1.0-alpha.26+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.26 input=23c00c5407aab4088b55b49905f2b195667a75b8544fa180786ac637ee67e1e5 content=967d4e05aa540b3e3d843fa6ed4516658ab4c364c1d8c42a14f36c57b75dff71
|
|
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.26",
|
|
23
|
+
"releaseId": "forgeos@0.1.0-alpha.26+unknown",
|
|
24
24
|
"schemaVersion": "0.1.0"
|
|
25
25
|
} as const;
|
|
@@ -116,7 +116,10 @@ async function openMemoryStore(
|
|
|
116
116
|
const retryDelays = access === "write" ? [25, 75, 150] : [];
|
|
117
117
|
for (let attempt = 0; ; attempt += 1) {
|
|
118
118
|
try {
|
|
119
|
-
return await DeltaStore.open(workspaceRoot, {
|
|
119
|
+
return await DeltaStore.open(workspaceRoot, {
|
|
120
|
+
access,
|
|
121
|
+
...(access === "write" ? { waitMs: 1_500, retryDelayMs: 50 } : {}),
|
|
122
|
+
});
|
|
120
123
|
} catch (error) {
|
|
121
124
|
if (!(error instanceof DeltaStoreBusyError) || attempt >= retryDelays.length) {
|
|
122
125
|
return memoryUnavailable(error, workspaceRoot);
|
package/src/forge/cli/changed.ts
CHANGED
|
@@ -82,8 +82,7 @@ function buildRisks(git: WorkspaceGitSummary): string[] {
|
|
|
82
82
|
const risks: string[] = [];
|
|
83
83
|
const changed = git.changeSummary.changed;
|
|
84
84
|
if (!git.available) {
|
|
85
|
-
risks.push("git status is unavailable;
|
|
86
|
-
return risks;
|
|
85
|
+
risks.push("git status is unavailable; using filesystem inventory as untracked-file analysis");
|
|
87
86
|
}
|
|
88
87
|
if (git.untracked.count > 0) {
|
|
89
88
|
risks.push(`${git.untracked.count} untracked file(s) are not in git history`);
|
|
@@ -103,7 +102,7 @@ function buildRisks(git: WorkspaceGitSummary): string[] {
|
|
|
103
102
|
|
|
104
103
|
function buildRecommendedCommands(git: WorkspaceGitSummary): string[] {
|
|
105
104
|
if (!git.available) {
|
|
106
|
-
return ["
|
|
105
|
+
return ["forge status --json", "forge handoff --json", "git init"];
|
|
107
106
|
}
|
|
108
107
|
if (git.changeSummary.changed.total.count === 0) {
|
|
109
108
|
return ["forge status --json", "forge dev --once --json"];
|
|
@@ -202,12 +201,13 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
202
201
|
const reviewFocus = buildReviewFocus(humanChanges, viewDerivedChanges);
|
|
203
202
|
const generatedExplanation = buildGeneratedChangeExplanation(humanChanges, viewDerivedChanges);
|
|
204
203
|
const diffPlan: DiffPlan = buildDiffPlanFromChangeSummary(viewChanged);
|
|
204
|
+
const ok = git.available || git.source === "filesystem";
|
|
205
205
|
|
|
206
206
|
return {
|
|
207
|
-
ok
|
|
207
|
+
ok,
|
|
208
208
|
data: {
|
|
209
209
|
schemaVersion: "0.1.0",
|
|
210
|
-
ok
|
|
210
|
+
ok,
|
|
211
211
|
summary: {
|
|
212
212
|
branch: git.branch,
|
|
213
213
|
commit: git.commit,
|
|
@@ -223,6 +223,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
223
223
|
},
|
|
224
224
|
git: {
|
|
225
225
|
available: git.available,
|
|
226
|
+
source: git.source,
|
|
226
227
|
...(git.error ? { error: git.error } : {}),
|
|
227
228
|
branch: git.branch,
|
|
228
229
|
commit: git.commit,
|
|
@@ -240,7 +241,7 @@ export function runChangedCommand(workspaceRoot: string, options: { authoredOnly
|
|
|
240
241
|
recommendedCommands,
|
|
241
242
|
nextActions: recommendedCommands,
|
|
242
243
|
},
|
|
243
|
-
exitCode:
|
|
244
|
+
exitCode: ok ? 0 : 1,
|
|
244
245
|
};
|
|
245
246
|
}
|
|
246
247
|
|
package/src/forge/cli/handoff.ts
CHANGED
|
@@ -140,6 +140,7 @@ function buildOpeningBrief(input: {
|
|
|
140
140
|
}): string {
|
|
141
141
|
const agent = input.dev.summary.agentContext;
|
|
142
142
|
const changedByType = summarizeChangeTypes(input.git.changeSummary.changed);
|
|
143
|
+
const changedFiles = Math.max(agent.changedFiles, input.git.changed.count);
|
|
143
144
|
const tests = input.recentRuns.test
|
|
144
145
|
? input.recentRuns.test.ok
|
|
145
146
|
? "last test run passed"
|
|
@@ -150,7 +151,7 @@ function buildOpeningBrief(input: {
|
|
|
150
151
|
: "no blocking issues";
|
|
151
152
|
return [
|
|
152
153
|
`ForgeOS handoff: ${input.dev.ok ? "dev diagnostics are clean" : "dev diagnostics need attention"}.`,
|
|
153
|
-
`${
|
|
154
|
+
`${changedFiles} changed file(s)${changedByType ? `: ${changedByType}` : ""}; ${input.git.staged.count} staged, ${input.git.untracked.count} untracked.`,
|
|
154
155
|
`${tests}; ${blockers}.`,
|
|
155
156
|
`Next command: ${input.dev.summary.primaryAction?.command ?? input.dev.nextActions[0]?.command ?? "forge dev"}.`,
|
|
156
157
|
].join(" ");
|
|
@@ -168,6 +169,7 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
|
|
|
168
169
|
const agent = dev.summary.agentContext;
|
|
169
170
|
const risks = [
|
|
170
171
|
...agent.blockingIssues,
|
|
172
|
+
...(!git.available ? ["git status is unavailable; using filesystem inventory as untracked-file analysis"] : []),
|
|
171
173
|
...(git.untracked.count > 0 ? [`${git.untracked.count} untracked file(s) are not in git history`] : []),
|
|
172
174
|
...(recentRuns.test && !recentRuns.test.ok ? ["last test run failed"] : []),
|
|
173
175
|
...(recentRuns.ui && !recentRuns.ui.ok ? ["last UI run failed"] : []),
|
|
@@ -181,6 +183,7 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
|
|
|
181
183
|
agent.blockingIssues.length === 0 &&
|
|
182
184
|
(!recentRuns.test || recentRuns.test.ok) &&
|
|
183
185
|
(!recentRuns.ui || recentRuns.ui.ok);
|
|
186
|
+
const changedFiles = Math.max(agent.changedFiles, git.changed.count);
|
|
184
187
|
|
|
185
188
|
return {
|
|
186
189
|
schemaVersion: "0.1.0",
|
|
@@ -192,7 +195,7 @@ export async function runHandoffCommand(options: HandoffCommandOptions): Promise
|
|
|
192
195
|
generatedChanged: agent.generatedChanged,
|
|
193
196
|
generatedChangedFiles: agent.generatedChangedFiles,
|
|
194
197
|
frontendReady: agent.frontendReady,
|
|
195
|
-
changedFiles
|
|
198
|
+
changedFiles,
|
|
196
199
|
stagedFiles: git.staged.count,
|
|
197
200
|
unstagedFiles: git.unstaged.count,
|
|
198
201
|
untrackedFiles: git.untracked.count,
|
|
@@ -186,6 +186,38 @@ function sourceText(workspaceRoot: string, file: string | undefined): string {
|
|
|
186
186
|
return nodeFileSystem.readText(absolute) ?? "";
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
+
function escapeRegExp(value: string): string {
|
|
190
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function addDbAliasesForText(
|
|
194
|
+
text: string,
|
|
195
|
+
tableNames: Set<string>,
|
|
196
|
+
aliases: Map<string, string>,
|
|
197
|
+
): void {
|
|
198
|
+
for (const match of text.matchAll(
|
|
199
|
+
/\b(?:const|let|var)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*ctx\.db(?:\.([A-Za-z_$][A-Za-z0-9_$]*)|\[\s*["'`]([^"'`]+)["'`]\s*\])/g,
|
|
200
|
+
)) {
|
|
201
|
+
const alias = match[1] ?? "";
|
|
202
|
+
const table = match[2] ?? match[3] ?? "";
|
|
203
|
+
if (alias && tableNames.has(table)) {
|
|
204
|
+
aliases.set(alias, table);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
for (const match of text.matchAll(/\b(?:const|let|var)\s*\{([^}]+)\}\s*=\s*ctx\.db/g)) {
|
|
209
|
+
const body = match[1] ?? "";
|
|
210
|
+
for (const part of body.split(",")) {
|
|
211
|
+
const [rawTable, rawAlias] = part.split(":").map((value) => value.trim());
|
|
212
|
+
const table = rawTable?.replace(/["'`]/g, "") ?? "";
|
|
213
|
+
const alias = (rawAlias ?? rawTable ?? "").replace(/\s*=.*$/, "").trim();
|
|
214
|
+
if (tableNames.has(table) && alias) {
|
|
215
|
+
aliases.set(alias, table);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
189
221
|
function dbTablesForText(
|
|
190
222
|
text: string,
|
|
191
223
|
tableNames: Set<string>,
|
|
@@ -199,6 +231,24 @@ function dbTablesForText(
|
|
|
199
231
|
tables.push(table);
|
|
200
232
|
}
|
|
201
233
|
}
|
|
234
|
+
for (const match of text.matchAll(/ctx\.db\s*\[\s*["'`]([^"'`]+)["'`]\s*\]\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)/g)) {
|
|
235
|
+
const table = match[1] ?? "";
|
|
236
|
+
const op = match[2] ?? "";
|
|
237
|
+
if (tableNames.has(table) && ops.has(op)) {
|
|
238
|
+
tables.push(table);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const aliases = new Map<string, string>();
|
|
242
|
+
addDbAliasesForText(text, tableNames, aliases);
|
|
243
|
+
for (const [alias, table] of aliases) {
|
|
244
|
+
const aliasPattern = new RegExp(`\\b${escapeRegExp(alias)}\\s*\\.\\s*([A-Za-z_$][A-Za-z0-9_$]*)`, "g");
|
|
245
|
+
for (const match of text.matchAll(aliasPattern)) {
|
|
246
|
+
const op = match[1] ?? "";
|
|
247
|
+
if (ops.has(op)) {
|
|
248
|
+
tables.push(table);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
202
252
|
return uniqueSorted(tables);
|
|
203
253
|
}
|
|
204
254
|
|
|
@@ -407,7 +407,7 @@ const DELTA_STORE_RETRY_DELAYS_MS = [25, 75, 150];
|
|
|
407
407
|
async function openDeltaStoreWithRetry(workspaceRoot: string): Promise<DeltaStore> {
|
|
408
408
|
for (let attempt = 0; ; attempt += 1) {
|
|
409
409
|
try {
|
|
410
|
-
return await DeltaStore.open(workspaceRoot);
|
|
410
|
+
return await DeltaStore.open(workspaceRoot, { waitMs: 1_500, retryDelayMs: 50 });
|
|
411
411
|
} catch (error) {
|
|
412
412
|
if (!(error instanceof DeltaStoreBusyError) || attempt >= DELTA_STORE_RETRY_DELAYS_MS.length) {
|
|
413
413
|
throw error;
|
package/src/forge/delta/store.ts
CHANGED
|
@@ -348,6 +348,12 @@ interface DeltaStoreLock {
|
|
|
348
348
|
token: string;
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
+
export interface DeltaStoreOpenOptions {
|
|
352
|
+
access?: DeltaStoreAccess;
|
|
353
|
+
waitMs?: number;
|
|
354
|
+
retryDelayMs?: number;
|
|
355
|
+
}
|
|
356
|
+
|
|
351
357
|
function getDeltaLockPath(workspaceRoot: string): string {
|
|
352
358
|
return join(workspaceRoot, ".forge", "delta", "delta.lock");
|
|
353
359
|
}
|
|
@@ -488,6 +494,33 @@ function acquireDeltaStoreLock(workspaceRoot: string): DeltaStoreLock {
|
|
|
488
494
|
throw new DeltaStoreBusyError(lockPath, readLockHolder(lockPath));
|
|
489
495
|
}
|
|
490
496
|
|
|
497
|
+
function sleep(ms: number): Promise<void> {
|
|
498
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function acquireDeltaStoreLockWithWait(
|
|
502
|
+
workspaceRoot: string,
|
|
503
|
+
options: { waitMs?: number; retryDelayMs?: number } = {},
|
|
504
|
+
): Promise<DeltaStoreLock> {
|
|
505
|
+
const waitMs = Math.max(0, options.waitMs ?? 0);
|
|
506
|
+
const retryDelayMs = Math.max(10, options.retryDelayMs ?? 50);
|
|
507
|
+
const started = Date.now();
|
|
508
|
+
for (;;) {
|
|
509
|
+
try {
|
|
510
|
+
return acquireDeltaStoreLock(workspaceRoot);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
if (!(error instanceof DeltaStoreBusyError)) {
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
const elapsed = Date.now() - started;
|
|
516
|
+
if (elapsed >= waitMs) {
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
await sleep(Math.min(retryDelayMs, waitMs - elapsed));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
491
524
|
export function probeDeltaStoreBusy(workspaceRoot: string): DeltaStoreBusyError | null {
|
|
492
525
|
const lockPath = getDeltaLockPath(workspaceRoot);
|
|
493
526
|
if (!existsSync(lockPath)) {
|
|
@@ -551,11 +584,11 @@ export class DeltaStore {
|
|
|
551
584
|
private readonly lock: DeltaStoreLock | null,
|
|
552
585
|
) {}
|
|
553
586
|
|
|
554
|
-
static async open(workspaceRoot: string, options:
|
|
587
|
+
static async open(workspaceRoot: string, options: DeltaStoreOpenOptions = {}): Promise<DeltaStore> {
|
|
555
588
|
const storePath = getDeltaStorePath(workspaceRoot);
|
|
556
589
|
mkdirSync(dirname(storePath), { recursive: true });
|
|
557
590
|
const initializedBeforeOpen = deltaStoreInitialized(storePath);
|
|
558
|
-
const lock = options.access === "read" ? null :
|
|
591
|
+
const lock = options.access === "read" ? null : await acquireDeltaStoreLockWithWait(workspaceRoot, options);
|
|
559
592
|
let store: DeltaStore | null = null;
|
|
560
593
|
try {
|
|
561
594
|
const adapter = await createPgliteAdapter(storePath);
|
|
@@ -565,7 +598,7 @@ export class DeltaStore {
|
|
|
565
598
|
} else if (await store.needsSchemaInit()) {
|
|
566
599
|
await store.close();
|
|
567
600
|
store = null;
|
|
568
|
-
const migrateLock =
|
|
601
|
+
const migrateLock = await acquireDeltaStoreLockWithWait(workspaceRoot, options);
|
|
569
602
|
try {
|
|
570
603
|
const migrateAdapter = await createPgliteAdapter(storePath);
|
|
571
604
|
store = new DeltaStore(workspaceRoot, storePath, migrateAdapter, migrateLock);
|
package/src/forge/make/index.ts
CHANGED
|
@@ -200,6 +200,13 @@ function chooseSchemaFile(workspaceRoot: string): string {
|
|
|
200
200
|
return "src/forge/schema.ts";
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
function schemaHasTenantsTable(workspaceRoot: string): boolean {
|
|
204
|
+
const schema = readIfExists(workspaceRoot, chooseSchemaFile(workspaceRoot)) ?? "";
|
|
205
|
+
return schema.includes('name: "tenants"') ||
|
|
206
|
+
schema.includes("name: 'tenants'") ||
|
|
207
|
+
/\btenants\s*=\s*(?:defineTable|table)\s*\(/.test(schema);
|
|
208
|
+
}
|
|
209
|
+
|
|
203
210
|
function choosePolicyFile(workspaceRoot: string): string {
|
|
204
211
|
if (fileExists(workspaceRoot, "src/policies.ts")) {
|
|
205
212
|
return "src/policies.ts";
|
|
@@ -551,6 +558,8 @@ function buildIntent(options: MakeCommandOptions): {
|
|
|
551
558
|
kind === "command"
|
|
552
559
|
? actionName?.replace(/^(create|update|delete)/, "") || "create"
|
|
553
560
|
: "read";
|
|
561
|
+
const tenantScoped =
|
|
562
|
+
options.tenantScoped || (kind === "resource" && schemaHasTenantsTable(options.workspaceRoot));
|
|
554
563
|
|
|
555
564
|
return {
|
|
556
565
|
diagnostics,
|
|
@@ -560,7 +569,7 @@ function buildIntent(options: MakeCommandOptions): {
|
|
|
560
569
|
table,
|
|
561
570
|
field: fieldOptions.field,
|
|
562
571
|
fields,
|
|
563
|
-
tenantScoped
|
|
572
|
+
tenantScoped,
|
|
564
573
|
crud: options.withCrud || kind === "resource",
|
|
565
574
|
liveQuery: options.withLiveQuery || kind === "resource" || kind === "livequery",
|
|
566
575
|
react:
|
|
@@ -898,7 +907,7 @@ function buildPlan(options: MakeCommandOptions): MakePlan {
|
|
|
898
907
|
kind: "resource" as const,
|
|
899
908
|
name: options.name ?? "resource",
|
|
900
909
|
fields: [],
|
|
901
|
-
tenantScoped:
|
|
910
|
+
tenantScoped: false,
|
|
902
911
|
crud: true,
|
|
903
912
|
liveQuery: true,
|
|
904
913
|
react: true,
|
package/src/forge/version.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join, relative } from "node:path";
|
|
4
|
+
import { normalizePath } from "../compiler/primitives/paths.ts";
|
|
4
5
|
import {
|
|
5
6
|
categorizeFiles,
|
|
6
7
|
classifyChangeType,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
|
|
14
15
|
export interface WorkspaceGitSummary {
|
|
15
16
|
available: boolean;
|
|
17
|
+
source?: "git" | "filesystem";
|
|
16
18
|
branch?: string;
|
|
17
19
|
commit?: string;
|
|
18
20
|
changed: FileListSummary;
|
|
@@ -28,18 +30,64 @@ export interface WorkspaceGitSummary {
|
|
|
28
30
|
error?: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
const FALLBACK_IGNORED_DIRS = new Set([
|
|
34
|
+
".git",
|
|
35
|
+
"node_modules",
|
|
36
|
+
"dist",
|
|
37
|
+
"build",
|
|
38
|
+
"coverage",
|
|
39
|
+
".next",
|
|
40
|
+
".nuxt",
|
|
41
|
+
".turbo",
|
|
42
|
+
".cache",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function listWorkspaceFiles(root: string): string[] {
|
|
46
|
+
const files: string[] = [];
|
|
47
|
+
const visit = (dir: string): void => {
|
|
48
|
+
let entries: Array<{ name: string; isDirectory: () => boolean; isFile: () => boolean }> = [];
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
if (entry.isDirectory() && FALLBACK_IGNORED_DIRS.has(entry.name)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const absolute = join(dir, entry.name);
|
|
59
|
+
const rel = normalizePath(relative(root, absolute));
|
|
60
|
+
if (!rel || rel === ".") {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
visit(absolute);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (entry.isFile()) {
|
|
68
|
+
files.push(rel);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
visit(root);
|
|
73
|
+
return filterVolatileForgeState(files).sort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function filesystemSummary(workspaceRoot: string, error?: string): WorkspaceGitSummary {
|
|
77
|
+
const files = listWorkspaceFiles(workspaceRoot);
|
|
78
|
+
const classify = workspaceChangeClassifier(workspaceRoot);
|
|
32
79
|
return {
|
|
33
80
|
available: false,
|
|
34
|
-
|
|
81
|
+
source: "filesystem",
|
|
82
|
+
changed: compactFiles(files),
|
|
35
83
|
staged: compactFiles([]),
|
|
36
84
|
unstaged: compactFiles([]),
|
|
37
|
-
untracked: compactFiles(
|
|
85
|
+
untracked: compactFiles(files),
|
|
38
86
|
changeSummary: {
|
|
39
|
-
changed: categorizeFiles(
|
|
87
|
+
changed: categorizeFiles(files, 8, classify),
|
|
40
88
|
staged: categorizeFiles([]),
|
|
41
89
|
unstaged: categorizeFiles([]),
|
|
42
|
-
untracked: categorizeFiles(
|
|
90
|
+
untracked: categorizeFiles(files, 8, classify),
|
|
43
91
|
},
|
|
44
92
|
...(error ? { error } : {}),
|
|
45
93
|
};
|
|
@@ -227,7 +275,7 @@ function parseStatusPath(line: string): string {
|
|
|
227
275
|
export function buildWorkspaceGitSummary(workspaceRoot: string): WorkspaceGitSummary {
|
|
228
276
|
const root = runGit(["rev-parse", "--show-toplevel"], workspaceRoot);
|
|
229
277
|
if (!root.ok) {
|
|
230
|
-
return
|
|
278
|
+
return filesystemSummary(workspaceRoot, root.error);
|
|
231
279
|
}
|
|
232
280
|
|
|
233
281
|
const status = runGit(["status", "--porcelain=v1", "-uall"], workspaceRoot, { trim: false });
|
|
@@ -262,6 +310,7 @@ export function buildWorkspaceGitSummary(workspaceRoot: string): WorkspaceGitSum
|
|
|
262
310
|
|
|
263
311
|
return {
|
|
264
312
|
available: true,
|
|
313
|
+
source: "git",
|
|
265
314
|
...(branch.ok ? { branch: branch.stdout } : {}),
|
|
266
315
|
...(commit.ok ? { commit: commit.stdout } : {}),
|
|
267
316
|
changed: compactFiles(changedFiles),
|