@frontera-sdk/cli 1.52.39 → 1.52.41
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 +49 -5
- 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/draft-changes.ts +1 -1
- 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 +332 -32
- package/src/commands/blueprint/editable.ts +3 -3
- 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 +876 -89
- 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 +321 -14
- package/src/commands/local/sync.ts +103 -6
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +54 -10
- 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/claude-commands.ts +11 -6
- package/src/forge/claude-skills.ts +4 -4
- 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 +113 -2
- package/src/kit.ts +1 -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 +212 -22
- 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 +181 -8
- 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 +347 -36
- package/src/local/local-dev-sync.ts +141 -12
- 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-documents.ts +1 -1
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-lessons.ts +1 -1
- package/src/local/local-library.ts +1 -1
- 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 +206 -8
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +143 -18
- 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 +41 -11
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +3 -3
- 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-merge.ts +1 -1
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +19 -8
- package/src/local/local-sync-push.ts +15 -12
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +349 -38
- package/src/local/local-sync-snapshot.ts +18 -13
- 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/scopes.ts +1 -1
- 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 +17 -14
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { SCAFFOLD_SAMPLE_ROWS } from './local-scaffold'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The rule set `frontera local data --synthetic` writes sample rows by,
|
|
8
|
+
* stamped into each dataset it writes (in its description, the only free
|
|
9
|
+
* text a dataset definition takes). Raise it whenever a change to the
|
|
10
|
+
* generator makes rows written before it break a rule it now keeps — a key
|
|
11
|
+
* that is another type's key taking each record once, a share staying
|
|
12
|
+
* between 0 and 100, times of day as HH:MM, a cancelled record carrying no
|
|
13
|
+
* resolution — so a project whose sample data is older is told to remake it.
|
|
14
|
+
*
|
|
15
|
+
* 1 (unstamped) everything before the stamp
|
|
16
|
+
* 2 1:1 key links, shares 0–100, HH:MM times, state-dependent fields, descriptions by kind
|
|
17
|
+
* 3 a link to a media-backed type's file holds a placeholder's path, so it resolves
|
|
18
|
+
* 4 a property without `required` is optional (state rules apply to it); an id named
|
|
19
|
+
* for another type takes its keys; counts, periods, fiscal years and keys are plausible
|
|
20
|
+
* 5 a property implementing a shared field takes the value of the linked record that
|
|
21
|
+
* implements it too (a work order's service region is its site's)
|
|
22
|
+
* 6 a field conditional on something its record has nothing for ("empty unless
|
|
23
|
+
* escalated", no `escalated` property or value) stays empty instead of random
|
|
24
|
+
* 7 a number keeps to the range its description gives ("(1 to 12)", "between 1 and 5"),
|
|
25
|
+
* a "How many" count stays up to 10, and a city is the large city a custom point is near
|
|
26
|
+
*/
|
|
27
|
+
export const SAMPLE_DATA_RULES = 7
|
|
28
|
+
|
|
29
|
+
const COMMAND = '`frontera local data --synthetic`'
|
|
30
|
+
const MADE_BY = /made by `frontera local data --synthetic`(?: \(sample rules (\d+)\))?/
|
|
31
|
+
|
|
32
|
+
/** The words a sample dataset's description carries: who made it, by which rules. */
|
|
33
|
+
export function sampleStamp(rules = SAMPLE_DATA_RULES): string {
|
|
34
|
+
return `made by ${COMMAND} (sample rules ${rules})`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The description of a sample dataset, with the current stamp: the one it had, restamped, or a new one. */
|
|
38
|
+
export function stampedDescription(before: string | undefined, fresh: string): string {
|
|
39
|
+
if (!before) return fresh
|
|
40
|
+
if (MADE_BY.test(before)) return before.replace(MADE_BY, sampleStamp())
|
|
41
|
+
// A description of its own, on a dataset whose file had no rows yet: kept, and said to hold sample rows.
|
|
42
|
+
return `${before.replace(/\s+$/, '')} Its rows are sample records ${sampleStamp()}: not real records.`
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The rule set a dataset's description says its rows were made by: 1 for an unstamped sample, null when not sample data. */
|
|
46
|
+
export function sampleRulesOf(description: unknown): number | null {
|
|
47
|
+
if (typeof description !== 'string') return null
|
|
48
|
+
const m = MADE_BY.exec(description)
|
|
49
|
+
return m ? (m[1] ? Number(m[1]) : 1) : null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Whether `frontera local data --synthetic` may write a dataset's rows: a
|
|
54
|
+
* dataset it made (its description says so), one with no rows yet, or the
|
|
55
|
+
* scaffold's sample rows unedited. Anything else — a dataset someone wrote,
|
|
56
|
+
* edited, loaded from a Source or brought in — is kept as it is.
|
|
57
|
+
*/
|
|
58
|
+
export function isSampleDataset(root: string, dataset: string): boolean {
|
|
59
|
+
const definition = readJson(join(root, 'datasets', `${dataset}.json`))
|
|
60
|
+
const csvPath = join(root, 'datasets', `${dataset}.csv`)
|
|
61
|
+
if (!definition) return !existsSync(csvPath) || !hasRows(readFileSync(csvPath, 'utf8'))
|
|
62
|
+
if (sampleRulesOf(definition.description) !== null) return true
|
|
63
|
+
if (definition.kind !== undefined && definition.kind !== 'file') return false
|
|
64
|
+
const rel = typeof definition.path === 'string' ? definition.path.replace(/^\.\//, '') : `${dataset}.csv`
|
|
65
|
+
const csv = join(root, 'datasets', rel)
|
|
66
|
+
if (!existsSync(csv)) return true
|
|
67
|
+
const text = readFileSync(csv, 'utf8')
|
|
68
|
+
// Empty, or a header and nothing under it: no rows yet.
|
|
69
|
+
if (!hasRows(text)) return true
|
|
70
|
+
const scaffold = SCAFFOLD_SAMPLE_ROWS[dataset]
|
|
71
|
+
return scaffold !== undefined && text.replace(/\r\n/g, '\n') === scaffold
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The sample datasets made by an older rule set than today's: their rows can
|
|
76
|
+
* break rules the generator keeps now. Each by its file, oldest rules first.
|
|
77
|
+
*/
|
|
78
|
+
export function staleSampleData(root: string, written?: ReadonlySet<string>): Array<{ file: string; rules: number }> {
|
|
79
|
+
const dir = join(root, 'datasets')
|
|
80
|
+
if (!existsSync(dir)) return []
|
|
81
|
+
return readdirSync(dir)
|
|
82
|
+
// Only the datasets the generator writes (`sampleDatasetNames`): a media-backed type's is never remade.
|
|
83
|
+
.filter((f) => f.endsWith('.json') && (!written || written.has(f.slice(0, -'.json'.length))))
|
|
84
|
+
.flatMap((f) => {
|
|
85
|
+
const rules = sampleRulesOf(readJson(join(dir, f))?.description)
|
|
86
|
+
return rules !== null && rules < SAMPLE_DATA_RULES ? [{ file: `datasets/${f}`, rules }] : []
|
|
87
|
+
})
|
|
88
|
+
.sort((a, b) => a.rules - b.rules || a.file.localeCompare(b.file))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** What `local dev` and `local status` say about stale sample data, or null when there is none. */
|
|
92
|
+
export function staleSampleDataLine(root: string, written?: ReadonlySet<string>): string | null {
|
|
93
|
+
const stale = staleSampleData(root, written)
|
|
94
|
+
if (!stale.length) return null
|
|
95
|
+
const names = stale.slice(0, 4).map((s) => s.file.replace(/^datasets\/|\.json$/g, '')).join(', ')
|
|
96
|
+
return `sample data made by an older generator (${stale.length} dataset${stale.length === 1 ? '' : 's'}: ${names}${stale.length > 4 ? ', …' : ''}): its rows can break rules the generator keeps now (links by a shared key or by an id's name, shares 0–100, HH:MM times, links to media files, fields empty by the record's state); \`frontera local dev --remake-sample-data\` or the next \`frontera local sync pull\` makes them again with the kept seed, rows and day, or ${COMMAND} remakes the sample datasets and leaves every other data file as it is`
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Whether a CSV holds a record under its header: an empty file or a header alone holds none. */
|
|
100
|
+
function hasRows(text: string): boolean {
|
|
101
|
+
return readCsvRows(text).length > 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readJson(path: string): Record<string, unknown> | null {
|
|
105
|
+
try {
|
|
106
|
+
const doc = JSON.parse(readFileSync(path, 'utf8')) as unknown
|
|
107
|
+
return doc && typeof doc === 'object' && !Array.isArray(doc) ? (doc as Record<string, unknown>) : null
|
|
108
|
+
} catch {
|
|
109
|
+
return null
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** A CSV's rows by header (RFC 4180 quoting), for a dataset sample data keeps and links into. */
|
|
114
|
+
export function readCsvRows(text: string): Array<Record<string, string>> {
|
|
115
|
+
const records: string[][] = []
|
|
116
|
+
let field = ''
|
|
117
|
+
let record: string[] = []
|
|
118
|
+
let quoted = false
|
|
119
|
+
for (let i = 0; i < text.length; i++) {
|
|
120
|
+
const ch = text[i]!
|
|
121
|
+
if (quoted) {
|
|
122
|
+
if (ch === '"' && text[i + 1] === '"') { field += '"'; i++ }
|
|
123
|
+
else if (ch === '"') quoted = false
|
|
124
|
+
else field += ch
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
if (ch === '"') quoted = true
|
|
128
|
+
else if (ch === ',') { record.push(field); field = '' }
|
|
129
|
+
else if (ch === '\n' || ch === '\r') {
|
|
130
|
+
if (ch === '\r' && text[i + 1] === '\n') i++
|
|
131
|
+
record.push(field); field = ''
|
|
132
|
+
if (record.some((v) => v !== '')) records.push(record)
|
|
133
|
+
record = []
|
|
134
|
+
} else field += ch
|
|
135
|
+
}
|
|
136
|
+
record.push(field)
|
|
137
|
+
if (record.some((v) => v !== '')) records.push(record)
|
|
138
|
+
const [header, ...rows] = records
|
|
139
|
+
if (!header) return []
|
|
140
|
+
return rows.map((r) => Object.fromEntries(header.map((h, i) => [h, r[i] ?? ''])))
|
|
141
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { SAMPLE_DATA_RULES, sampleRulesOf, staleSampleData } from './local-sample-data'
|
|
5
|
+
import { readLock } from './local-sync-plan'
|
|
6
|
+
import { generateSyntheticData, loadScenario, parseRegion, sampleDatasetNames, type GeneratedDataset } from './local-synthetic-data'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Sample data made again on its own: after a pull that brought Blueprint
|
|
10
|
+
* items (a type's properties or links may have changed), or when the sample
|
|
11
|
+
* datasets were made by an older generator (`staleSampleData`). Every sample
|
|
12
|
+
* dataset is made again together, so their links agree; only the datasets
|
|
13
|
+
* `isSampleDataset` lets the generator write (stamped by it, scaffold rows
|
|
14
|
+
* unedited, or empty), never a dataset someone wrote, edited, loaded or
|
|
15
|
+
* pulled. A project that has no generated sample data is left alone, and so
|
|
16
|
+
* are the Blueprint files (no backing is added, as `local data` adds one).
|
|
17
|
+
*
|
|
18
|
+
* The generator's options are kept in `.frontera/sample-data.json`, so a
|
|
19
|
+
* remake writes the same keys and the same rows as the last run did.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export const SAMPLE_OPTIONS_FILE = join('.frontera', 'sample-data.json')
|
|
23
|
+
|
|
24
|
+
export interface SampleOptions {
|
|
25
|
+
seed: number
|
|
26
|
+
rows: number
|
|
27
|
+
/** The day due dates are spread around, YYYY-MM-DD. */
|
|
28
|
+
today: string
|
|
29
|
+
/** `--region` as it was given, when it was. */
|
|
30
|
+
region?: string
|
|
31
|
+
/**
|
|
32
|
+
* What last made the sample data: `pull` (a `local sync pull`, or `local
|
|
33
|
+
* init --from-workspace`, wrote it with the pulled files) or `local` (`local
|
|
34
|
+
* data --synthetic`, or `local dev` making it again). Absent in a record
|
|
35
|
+
* from an older CLI.
|
|
36
|
+
*/
|
|
37
|
+
madeBy?: 'pull' | 'local'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/
|
|
41
|
+
|
|
42
|
+
export const isoDay = (at: Date) => at.toISOString().slice(0, 10)
|
|
43
|
+
|
|
44
|
+
export function readSampleOptions(root: string): SampleOptions | null {
|
|
45
|
+
try {
|
|
46
|
+
const doc = JSON.parse(readFileSync(join(root, SAMPLE_OPTIONS_FILE), 'utf8')) as Partial<SampleOptions>
|
|
47
|
+
if (!Number.isInteger(doc.seed) || !Number.isInteger(doc.rows) || typeof doc.today !== 'string' || !DAY.test(doc.today)) return null
|
|
48
|
+
return { seed: doc.seed!, rows: doc.rows!, today: doc.today, ...(typeof doc.region === 'string' && doc.region ? { region: doc.region } : {}), ...(doc.madeBy === 'pull' || doc.madeBy === 'local' ? { madeBy: doc.madeBy } : {}) }
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function writeSampleOptions(root: string, options: SampleOptions): void {
|
|
55
|
+
const path = join(root, SAMPLE_OPTIONS_FILE)
|
|
56
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
57
|
+
writeFileSync(path, `${JSON.stringify(options, null, 2)}\n`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Whether the project holds sample data the generator made (a stamped dataset), so it has some to remake. */
|
|
61
|
+
export function hasGeneratedSampleData(root: string, names: ReadonlySet<string> = sampleDatasetNames(root)): boolean {
|
|
62
|
+
for (const name of names) {
|
|
63
|
+
const path = join(root, 'datasets', `${name}.json`)
|
|
64
|
+
if (!existsSync(path)) continue
|
|
65
|
+
try {
|
|
66
|
+
if (sampleRulesOf((JSON.parse(readFileSync(path, 'utf8')) as { description?: unknown }).description) !== null) return true
|
|
67
|
+
} catch {
|
|
68
|
+
// Not a definition the generator wrote.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface SampleRemake {
|
|
75
|
+
/** Why it was made again, in words. */
|
|
76
|
+
why: string
|
|
77
|
+
options: SampleOptions
|
|
78
|
+
written: GeneratedDataset[]
|
|
79
|
+
kept: GeneratedDataset[]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Make the project's sample data again with its kept options (seed 7, 200
|
|
84
|
+
* rows and today when none are kept, which are then kept). Null when the
|
|
85
|
+
* project has no generated sample data.
|
|
86
|
+
*/
|
|
87
|
+
export async function remakeSampleData(root: string, why: string, now: Date = new Date(), madeBy: 'pull' | 'local' = 'local'): Promise<SampleRemake | null> {
|
|
88
|
+
const names = sampleDatasetNames(root)
|
|
89
|
+
if (!names.size || !hasGeneratedSampleData(root, names)) return null
|
|
90
|
+
const { madeBy: _before, ...kept } = readSampleOptions(root) ?? { seed: 7, rows: 200, today: isoDay(now) }
|
|
91
|
+
const options: SampleOptions = { ...kept, madeBy }
|
|
92
|
+
const region = options.region ? parseRegion(options.region) ?? undefined : undefined
|
|
93
|
+
const scenario = await loadScenario(root, options.seed)
|
|
94
|
+
const made = generateSyntheticData(root, { rows: options.rows, seed: options.seed, today: new Date(`${options.today}T00:00:00Z`), ...(region ? { region } : {}), ...(scenario ? { scenario } : {}) })
|
|
95
|
+
writeSampleOptions(root, options)
|
|
96
|
+
return { why, options, written: made.filter((m) => !m.kept), kept: made.filter((m) => m.kept) }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Why the project's sample data should be made again now, or null: the pull
|
|
101
|
+
* brought Blueprint items, or an older generator made it.
|
|
102
|
+
*/
|
|
103
|
+
export function remakeReason(root: string, pulledBlueprint: boolean): string | null {
|
|
104
|
+
const stale = staleSampleData(root, sampleDatasetNames(root))
|
|
105
|
+
if (stale.length) return `made by an older generator (sample rules ${stale[0]!.rules})`
|
|
106
|
+
if (pulledBlueprint) return 'the pull brought Blueprint items'
|
|
107
|
+
return null
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether a pulled project's sample data is the pull's own: the project was
|
|
112
|
+
* pulled (`frontera.lock`) and nothing recorded says the local generator made
|
|
113
|
+
* it since (`madeBy: local` in .frontera/sample-data.json, which `local data
|
|
114
|
+
* --synthetic` and a remake by `local dev` write). A record from an older CLI
|
|
115
|
+
* says neither, and counts as the pull's: the data predates this CLI's runs.
|
|
116
|
+
* `local dev` doesn't make such data again unasked; `local dev
|
|
117
|
+
* --remake-sample-data`, a pull, or `local data --synthetic` does.
|
|
118
|
+
*/
|
|
119
|
+
export function pulledSampleData(root: string): boolean {
|
|
120
|
+
return readLock(root) !== null && readSampleOptions(root)?.madeBy !== 'local'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** What `local dev` says instead of making the pull's sample data again: one line, with the command that does. */
|
|
124
|
+
export function pulledSampleDataNote(root: string): string {
|
|
125
|
+
const stale = staleSampleData(root, sampleDatasetNames(root))
|
|
126
|
+
const n = new Set(stale.map((s) => s.file)).size
|
|
127
|
+
return `Sample data: newer sample rules exist (${SAMPLE_DATA_RULES}; ${n} dataset${n === 1 ? '' : 's'} came with the pull at rules ${stale[0]?.rules ?? 'older'}), kept as pulled; \`frontera local dev --remake-sample-data\` makes it again with the kept seed, rows and day`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The lines a command says of a remake. */
|
|
131
|
+
export function remakeLines(r: SampleRemake): string[] {
|
|
132
|
+
const rows = r.written.reduce((n, d) => n + d.rows, 0)
|
|
133
|
+
return [
|
|
134
|
+
`Sample data made again (${r.why}): ${rows} rows for ${r.written.length} object type${r.written.length === 1 ? '' : 's'}, seed ${r.options.seed}, ${r.options.rows} rows a type, dates around ${r.options.today}${r.options.region ? `, region ${r.options.region}` : ''} (kept in ${SAMPLE_OPTIONS_FILE}); --keep-sample-data leaves it as it was`,
|
|
135
|
+
...r.written.map((d) => ` ${d.objectType.padEnd(24)} ${String(d.rows).padStart(6)} ${d.csvPath}`),
|
|
136
|
+
...(r.kept.length ? [` kept as they are (not sample data): ${r.kept.map((d) => d.csvPath).join(', ')}`] : []),
|
|
137
|
+
]
|
|
138
|
+
}
|