@lark-base-open/base-database-cli 1.1.1 → 1.3.8
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/CHANGELOG.md +97 -0
- package/LICENSE +6 -0
- package/README.md +47 -27
- package/dist/ai-functions.d.ts +53 -0
- package/dist/ai-functions.d.ts.map +1 -0
- package/dist/ai-functions.js +436 -0
- package/dist/ai-functions.js.map +1 -0
- package/dist/archive.d.ts +21 -0
- package/dist/archive.d.ts.map +1 -1
- package/dist/archive.js +69 -10
- package/dist/archive.js.map +1 -1
- package/dist/auth.d.ts +5 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +26 -3
- package/dist/auth.js.map +1 -1
- package/dist/check.d.ts +74 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +283 -0
- package/dist/check.js.map +1 -0
- package/dist/dependency-policy.d.ts +10 -0
- package/dist/dependency-policy.d.ts.map +1 -0
- package/dist/dependency-policy.js +162 -0
- package/dist/dependency-policy.js.map +1 -0
- package/dist/deploy.d.ts +5 -0
- package/dist/deploy.d.ts.map +1 -1
- package/dist/deploy.js +141 -176
- package/dist/deploy.js.map +1 -1
- package/dist/environment-variables.d.ts +52 -0
- package/dist/environment-variables.d.ts.map +1 -0
- package/dist/environment-variables.js +563 -0
- package/dist/environment-variables.js.map +1 -0
- package/dist/environment.d.ts +1 -1
- package/dist/environment.js +1 -1
- package/dist/environment.js.map +1 -1
- package/dist/function-input-types.d.ts +10 -0
- package/dist/function-input-types.d.ts.map +1 -0
- package/dist/function-input-types.js +160 -0
- package/dist/function-input-types.js.map +1 -0
- package/dist/function-schema.d.ts.map +1 -1
- package/dist/function-schema.js +89 -36
- package/dist/function-schema.js.map +1 -1
- package/dist/init-field-retry.d.ts +17 -0
- package/dist/init-field-retry.d.ts.map +1 -0
- package/dist/init-field-retry.js +127 -0
- package/dist/init-field-retry.js.map +1 -0
- package/dist/init-journal.d.ts +98 -0
- package/dist/init-journal.d.ts.map +1 -0
- package/dist/init-journal.js +492 -0
- package/dist/init-journal.js.map +1 -0
- package/dist/init-schema.d.ts +8 -0
- package/dist/init-schema.d.ts.map +1 -0
- package/dist/init-schema.js +423 -0
- package/dist/init-schema.js.map +1 -0
- package/dist/init.d.ts +15 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +884 -261
- package/dist/init.js.map +1 -1
- package/dist/lark-cli-error.d.ts +47 -0
- package/dist/lark-cli-error.d.ts.map +1 -0
- package/dist/lark-cli-error.js +201 -0
- package/dist/lark-cli-error.js.map +1 -0
- package/dist/lark-cli.d.ts +1 -1
- package/dist/lark-cli.d.ts.map +1 -1
- package/dist/lark-cli.js +78 -18
- package/dist/lark-cli.js.map +1 -1
- package/dist/mutation-schema.d.ts +146 -0
- package/dist/mutation-schema.d.ts.map +1 -0
- package/dist/mutation-schema.js +153 -0
- package/dist/mutation-schema.js.map +1 -0
- package/dist/mutation.d.ts +10 -0
- package/dist/mutation.d.ts.map +1 -0
- package/dist/mutation.js +131 -0
- package/dist/mutation.js.map +1 -0
- package/dist/npm.d.ts +1 -0
- package/dist/npm.d.ts.map +1 -1
- package/dist/npm.js +2 -2
- package/dist/npm.js.map +1 -1
- package/dist/preview-receipt.d.ts.map +1 -1
- package/dist/preview-receipt.js +15 -5
- package/dist/preview-receipt.js.map +1 -1
- package/dist/process.d.ts +3 -1
- package/dist/process.d.ts.map +1 -1
- package/dist/process.js +16 -4
- package/dist/process.js.map +1 -1
- package/dist/program.d.ts.map +1 -1
- package/dist/program.js +246 -42
- package/dist/program.js.map +1 -1
- package/dist/project-build.d.ts +25 -0
- package/dist/project-build.d.ts.map +1 -0
- package/dist/project-build.js +229 -0
- package/dist/project-build.js.map +1 -0
- package/dist/project-context.d.ts +344 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/project-context.js +485 -0
- package/dist/project-context.js.map +1 -0
- package/dist/project-layout.d.ts +5 -0
- package/dist/project-layout.d.ts.map +1 -0
- package/dist/project-layout.js +24 -0
- package/dist/project-layout.js.map +1 -0
- package/dist/public-sql-error.d.ts +25 -0
- package/dist/public-sql-error.d.ts.map +1 -0
- package/dist/public-sql-error.js +65 -0
- package/dist/public-sql-error.js.map +1 -0
- package/dist/pull.d.ts +1 -0
- package/dist/pull.d.ts.map +1 -1
- package/dist/pull.js +67 -15
- package/dist/pull.js.map +1 -1
- package/dist/site-readme.d.ts +3 -0
- package/dist/site-readme.d.ts.map +1 -0
- package/dist/site-readme.js +83 -0
- package/dist/site-readme.js.map +1 -0
- package/dist/workspace-fingerprint.d.ts +21 -1
- package/dist/workspace-fingerprint.d.ts.map +1 -1
- package/dist/workspace-fingerprint.js +81 -2
- package/dist/workspace-fingerprint.js.map +1 -1
- package/package.json +2 -1
- package/skills/lark-base-database/SKILL.md +86 -52
- package/skills/lark-base-database/references/ai-function.md +479 -0
- package/skills/lark-base-database/references/base-capability-routing.md +7 -7
- package/skills/lark-base-database/references/base-sql-errors.generated.md +403 -0
- package/skills/lark-base-database/references/database-function-limitations.md +29 -0
- package/skills/lark-base-database/references/database-function.md +323 -0
- package/skills/lark-base-database/references/development.md +166 -33
- package/skills/lark-base-database/references/environment-variables.md +55 -0
- package/skills/lark-base-database/references/faas.md +46 -53
- package/skills/lark-base-database/references/file-transfer.md +25 -25
- package/skills/lark-base-database/references/frontend-design-guide.md +151 -65
- package/skills/lark-base-database/references/mutations.md +295 -0
- package/skills/lark-base-database/references/notifications.md +9 -9
- package/skills/lark-base-database/references/permission-design.md +24 -24
- package/skills/lark-base-database/references/release.md +57 -30
- package/skills/lark-base-database/references/style-guide.md +10 -67
- package/skills/lark-base-database/references/verification.md +19 -13
- package/skills/lark-base-database/references/workspace.md +57 -17
- package/templates/database-starter/README.md +34 -4
- package/templates/database-starter/database-functions/README.md +8 -0
- package/templates/database-starter/gitignore +7 -0
- package/templates/database-starter/mutations/README.md +19 -0
- package/templates/database-starter/page/README.md +25 -19
- package/templates/database-starter/page/package-lock.json +8 -16
- package/templates/database-starter/page/package.json +2 -3
- package/templates/database-starter/page/src/App.tsx +23 -3
- package/templates/database-starter/page/src/components/UserSelectorEditor/index.tsx +32 -4
- package/templates/database-starter/page/src/components/UserSelectorPanel/index.tsx +104 -34
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.test.ts +55 -11
- package/templates/database-starter/page/src/components/UserSelectorPanel/user-search.ts +68 -25
- package/templates/database-starter/page/src/components/database-runtime-ready.tsx +1 -1
- package/templates/database-starter/page/src/components/database-user-menu/index.tsx +4 -7
- package/templates/database-starter/page/src/components/feedback/page-state.tsx +40 -5
- package/templates/database-starter/page/src/components/floating-button/assets/edit-active.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/edit-default.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/toast-close.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/assets/toast-info.svg +5 -0
- package/templates/database-starter/page/src/components/floating-button/assets/tooltip-caret.svg +3 -0
- package/templates/database-starter/page/src/components/floating-button/index.tsx +193 -113
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.test.ts +107 -37
- package/templates/database-starter/page/src/components/floating-button/utils/project-meta.ts +13 -50
- package/templates/database-starter/page/src/components/ui-language.test.tsx +101 -0
- package/templates/database-starter/page/src/examples/ai-function/ImageTaskOperation.tsx +202 -0
- package/templates/database-starter/page/src/examples/ai-function/README.md +14 -0
- package/templates/database-starter/page/src/examples/ai-function/TextStreamOperation.tsx +184 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-copy.ts +173 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.test.ts +30 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-delta.ts +26 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-language.test.tsx +86 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.test.ts +201 -0
- package/templates/database-starter/page/src/examples/ai-function/ai-media-panel.tsx +382 -0
- package/templates/database-starter/page/src/lib/attachment-image.tsx +144 -0
- package/templates/database-starter/page/src/lib/database-function-inputs.generated.ts +4 -0
- package/templates/database-starter/page/src/lib/database-runtime-attachment.test.ts +180 -76
- package/templates/database-starter/page/src/lib/database-runtime.test.ts +130 -10
- package/templates/database-starter/page/src/lib/database-runtime.ts +484 -51
- package/templates/database-starter/page/src/lib/date-time.ts +61 -0
- package/templates/database-starter/page/src/lib/ui-language.ts +86 -0
- package/templates/database-starter/page/src/main.tsx +1 -1
- package/templates/database-starter/page/tsconfig.json +2 -2
- package/templates/database-starter/page/vite.config.ts +1 -2
- package/templates/database-starter/prettierignore +1 -1
- package/templates/database-starter/server-functions/README.md +2 -2
- package/templates/database-starter/server-functions/package-lock.json +4 -4
- package/templates/database-starter/server-functions/package.json +1 -1
- package/dist/agent-guide.d.ts +0 -19
- package/dist/agent-guide.d.ts.map +0 -1
- package/dist/agent-guide.js +0 -31
- package/dist/agent-guide.js.map +0 -1
- package/dist/database-skill.d.ts +0 -24
- package/dist/database-skill.d.ts.map +0 -1
- package/dist/database-skill.js +0 -148
- package/dist/database-skill.js.map +0 -1
- package/skills/lark-base-database/references/base-sql-unsupported.md +0 -42
- package/skills/lark-base-database/references/database-capability-skill.md +0 -40
- package/skills/lark-base-database/references/sql-function.md +0 -289
- package/templates/database-capability/SKILL.md.tmpl +0 -58
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.test.ts +0 -23
- package/templates/database-starter/page/src/components/floating-button/chatbot-entry.tsx +0 -873
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.test.ts +0 -25
- package/templates/database-starter/page/src/components/floating-button/local-chat-panel.tsx +0 -197
- package/templates/database-starter/page/src/components/floating-button/utils/env.ts +0 -18
- package/templates/database-starter/sql/README.md +0 -8
package/dist/init.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
2
|
-
import { basename, join, resolve } from 'node:path';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { InitJournal, InitFailureError, assertNoInitReceipt, } from './init-journal.js';
|
|
5
|
+
import { readInitSchema, InitSchemaError } from './init-schema.js';
|
|
6
|
+
import { createFieldsWithRetry } from './init-field-retry.js';
|
|
7
|
+
import { isRecord } from './lark-cli-error.js';
|
|
3
8
|
import { starterRoot } from './assets.js';
|
|
4
|
-
import {
|
|
9
|
+
import { DATABASE_FUNCTIONS_DIRECTORY, LEGACY_DATABASE_FUNCTIONS_DIRECTORY, databaseFunctionsDirectory, } from './project-layout.js';
|
|
10
|
+
import { CONFIG_FILE_NAME, readConfig, writeConfig } from './config.js';
|
|
5
11
|
import { ONLINE_RUNTIME_PROFILE, resolveRuntimeProfile, } from './environment.js';
|
|
6
12
|
import { responseData, runLarkCli, resolveLarkProfileEnvironment, } from './lark-cli.js';
|
|
7
13
|
import { writePageTitle } from './page-title.js';
|
|
@@ -12,46 +18,6 @@ function requiredString(value, label) {
|
|
|
12
18
|
}
|
|
13
19
|
return value.trim();
|
|
14
20
|
}
|
|
15
|
-
function parseSchema(path) {
|
|
16
|
-
const absolutePath = resolve(path);
|
|
17
|
-
let parsed;
|
|
18
|
-
try {
|
|
19
|
-
parsed = JSON.parse(readFileSync(absolutePath, 'utf8'));
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
throw new Error(`Unable to read schema ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
23
|
-
}
|
|
24
|
-
if (!parsed || typeof parsed !== 'object') {
|
|
25
|
-
throw new Error('Schema must be an object');
|
|
26
|
-
}
|
|
27
|
-
const tables = parsed.tables;
|
|
28
|
-
if (!Array.isArray(tables) || tables.length === 0) {
|
|
29
|
-
throw new Error('Schema must contain at least one table');
|
|
30
|
-
}
|
|
31
|
-
const names = new Set();
|
|
32
|
-
for (const [tableIndex, table] of tables.entries()) {
|
|
33
|
-
if (!table || typeof table !== 'object') {
|
|
34
|
-
throw new Error(`schema.tables[${tableIndex}] must be an object`);
|
|
35
|
-
}
|
|
36
|
-
const candidate = table;
|
|
37
|
-
const name = requiredString(candidate.name, `schema.tables[${tableIndex}].name`);
|
|
38
|
-
if (names.has(name))
|
|
39
|
-
throw new Error(`Duplicate table name: ${name}`);
|
|
40
|
-
names.add(name);
|
|
41
|
-
if (!Array.isArray(candidate.fields) || candidate.fields.length === 0) {
|
|
42
|
-
throw new Error(`schema.tables[${tableIndex}].fields must not be empty`);
|
|
43
|
-
}
|
|
44
|
-
for (const [fieldIndex, field] of candidate.fields.entries()) {
|
|
45
|
-
if (!field || typeof field !== 'object') {
|
|
46
|
-
throw new Error(`schema.tables[${tableIndex}].fields[${fieldIndex}] must be an object`);
|
|
47
|
-
}
|
|
48
|
-
const candidateField = field;
|
|
49
|
-
requiredString(candidateField.name, `schema.tables[${tableIndex}].fields[${fieldIndex}].name`);
|
|
50
|
-
requiredString(candidateField.type, `schema.tables[${tableIndex}].fields[${fieldIndex}].type`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return parsed;
|
|
54
|
-
}
|
|
55
21
|
function tableId(response, label) {
|
|
56
22
|
const data = responseData(response);
|
|
57
23
|
const table = data.table;
|
|
@@ -76,6 +42,14 @@ function baseName(response) {
|
|
|
76
42
|
? value.trim()
|
|
77
43
|
: undefined;
|
|
78
44
|
}
|
|
45
|
+
function baseTimeZone(response) {
|
|
46
|
+
const data = responseData(response);
|
|
47
|
+
const base = data.base;
|
|
48
|
+
const value = base?.time_zone ?? base?.timeZone ?? data.time_zone ?? data.timeZone;
|
|
49
|
+
return typeof value === 'string' && value.trim() !== ''
|
|
50
|
+
? value.trim()
|
|
51
|
+
: undefined;
|
|
52
|
+
}
|
|
79
53
|
function existingTables(response) {
|
|
80
54
|
const data = responseData(response);
|
|
81
55
|
const value = data.tables ?? data.items;
|
|
@@ -185,29 +159,543 @@ function catalogBaseAppUrl(catalog) {
|
|
|
185
159
|
const value = catalog.baseAppUrl;
|
|
186
160
|
return typeof value === 'string' && value.trim() !== '' ? value : undefined;
|
|
187
161
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
162
|
+
function validateApplication(value, token, name, description, baseTokenValue, tables, validateMetadata, validateBinding, exactBindings) {
|
|
163
|
+
if (!isRecord(value) || !isRecord(value.project))
|
|
164
|
+
throw new Error('Application readback does not contain project metadata');
|
|
165
|
+
const project = value.project;
|
|
166
|
+
if (project.token !== token)
|
|
167
|
+
throw new Error('Application token changed after initialization started');
|
|
168
|
+
if (validateMetadata &&
|
|
169
|
+
(project.name !== name || String(project.description ?? '') !== description))
|
|
170
|
+
throw new Error('Application metadata changed after initialization started');
|
|
171
|
+
if (!validateBinding)
|
|
172
|
+
return;
|
|
173
|
+
if (!Array.isArray(project.baseBindings))
|
|
174
|
+
throw new Error('Application readback does not contain Base bindings');
|
|
175
|
+
const binding = project.baseBindings.find((item) => isRecord(item) && item.baseToken === baseTokenValue);
|
|
176
|
+
if (!isRecord(binding) || !Array.isArray(binding.tableIds))
|
|
177
|
+
throw new Error('Application no longer contains the expected Base binding');
|
|
178
|
+
const actual = binding.tableIds.map(String);
|
|
179
|
+
const expected = tables.map((table) => table.tableId);
|
|
180
|
+
if (actual.length < expected.length ||
|
|
181
|
+
expected.some((id) => !actual.includes(id)) ||
|
|
182
|
+
(exactBindings &&
|
|
183
|
+
(project.baseBindings.length !== 1 || actual.length !== expected.length)))
|
|
184
|
+
throw new Error('Application Base bindings changed after initialization started');
|
|
185
|
+
}
|
|
186
|
+
function validateExactCatalog(value, baseTokenValue, tables) {
|
|
187
|
+
const catalog = validateCatalog(value, baseTokenValue, tables);
|
|
188
|
+
const actual = Array.isArray(catalog.tables) ? catalog.tables : [];
|
|
189
|
+
const expected = new Set(tables.map((table) => `${baseTokenValue}:${table.tableId}`));
|
|
190
|
+
if (actual.length !== expected.size ||
|
|
191
|
+
actual.some((table) => !isRecord(table) ||
|
|
192
|
+
!expected.has(`${String(table.baseToken)}:${String(table.id)}`)))
|
|
193
|
+
throw new Error('Application Catalog contains unexpected table bindings');
|
|
194
|
+
}
|
|
195
|
+
function recoveryBlocked(journal, phase, message) {
|
|
196
|
+
throw new InitFailureError(message, {
|
|
197
|
+
ok: false,
|
|
198
|
+
status: 'blocked',
|
|
199
|
+
phase,
|
|
200
|
+
journal: journal.path,
|
|
201
|
+
receipt: journal.receipt,
|
|
202
|
+
error: { message },
|
|
203
|
+
nextAction: 'Preserve the receipt and reconcile the reported resource or unknown outcome. No recovery write was sent.',
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function existingFields(response) {
|
|
207
|
+
const data = responseData(response);
|
|
208
|
+
const value = data.fields ?? data.items;
|
|
209
|
+
if (!Array.isArray(value))
|
|
210
|
+
throw new Error('field-list response does not contain fields');
|
|
211
|
+
const fields = value.map((field, index) => {
|
|
212
|
+
if (!isRecord(field))
|
|
213
|
+
throw new Error(`field-list response fields[${index}] must be an object`);
|
|
214
|
+
return {
|
|
215
|
+
id: requiredString(field.id ?? field.field_id ?? field.fieldId, `field-list response fields[${index}] ID`),
|
|
216
|
+
name: requiredString(field.name, `field-list response fields[${index}] name`),
|
|
217
|
+
type: requiredString(field.type, `field-list response fields[${index}] type`),
|
|
218
|
+
raw: field,
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
fields,
|
|
223
|
+
total: typeof data.total === 'number' && Number.isInteger(data.total)
|
|
224
|
+
? data.total
|
|
225
|
+
: fields.length,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function listExistingFields(baseTokenValue, tableIdValue, env) {
|
|
229
|
+
const limit = 200;
|
|
230
|
+
const fields = [];
|
|
231
|
+
for (let offset = 0;; offset += limit) {
|
|
232
|
+
const page = existingFields(runLarkCli([
|
|
233
|
+
'base',
|
|
234
|
+
'+field-list',
|
|
235
|
+
'--base-token',
|
|
236
|
+
baseTokenValue,
|
|
237
|
+
'--table-id',
|
|
238
|
+
tableIdValue,
|
|
239
|
+
'--limit',
|
|
240
|
+
String(limit),
|
|
241
|
+
'--offset',
|
|
242
|
+
String(offset),
|
|
243
|
+
], env));
|
|
244
|
+
fields.push(...page.fields);
|
|
245
|
+
if (fields.length >= page.total)
|
|
246
|
+
return fields;
|
|
247
|
+
if (page.fields.length === 0)
|
|
248
|
+
throw new Error('field-list returned more fields without a page');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
function primaryFieldId(baseTokenValue, tableIdValue, env) {
|
|
252
|
+
const data = responseData(runLarkCli([
|
|
253
|
+
'base',
|
|
254
|
+
'+table-get',
|
|
255
|
+
'--base-token',
|
|
256
|
+
baseTokenValue,
|
|
257
|
+
'--table-id',
|
|
258
|
+
tableIdValue,
|
|
259
|
+
], env));
|
|
260
|
+
const table = isRecord(data.table) ? data.table : {};
|
|
261
|
+
return requiredString(table.primary_field ?? table.primaryField, `table-get response primary field for ${tableIdValue}`);
|
|
262
|
+
}
|
|
263
|
+
function receiptTable(receipt, name) {
|
|
264
|
+
return receipt.resources.tables.find((table) => table.name === name);
|
|
265
|
+
}
|
|
266
|
+
function plainObject(value) {
|
|
267
|
+
return isRecord(value) ? value : {};
|
|
268
|
+
}
|
|
269
|
+
function normalizeStyle(type, value) {
|
|
270
|
+
const source = plainObject(value);
|
|
271
|
+
if (type === 'text')
|
|
272
|
+
return { type: source.type ?? 'plain', ...source };
|
|
273
|
+
if (type === 'number') {
|
|
274
|
+
const styleType = String(source.type ?? 'plain');
|
|
275
|
+
const defaults = {
|
|
276
|
+
plain: { precision: 2, percentage: false, thousands_separator: false },
|
|
277
|
+
currency: { precision: 2 },
|
|
278
|
+
progress: { percentage: true },
|
|
279
|
+
rating: { icon: 'star', min: 1, max: 5 },
|
|
280
|
+
};
|
|
281
|
+
return { type: styleType, ...(defaults[styleType] ?? {}), ...source };
|
|
282
|
+
}
|
|
283
|
+
if (['datetime', 'created_at', 'updated_at'].includes(type))
|
|
284
|
+
return { format: 'yyyy/MM/dd', ...source };
|
|
285
|
+
return Object.keys(source).length > 0 ? source : undefined;
|
|
286
|
+
}
|
|
287
|
+
function normalizeFieldConfig(source, receipt, remoteByTable) {
|
|
288
|
+
const type = String(source.type ?? '');
|
|
289
|
+
const result = {
|
|
290
|
+
name: source.name,
|
|
291
|
+
type,
|
|
292
|
+
description: source.description ?? null,
|
|
293
|
+
};
|
|
294
|
+
const style = normalizeStyle(type, source.style);
|
|
295
|
+
if (style)
|
|
296
|
+
result.style = style;
|
|
297
|
+
const supportsDefault = [
|
|
298
|
+
'text',
|
|
299
|
+
'number',
|
|
300
|
+
'select',
|
|
301
|
+
'datetime',
|
|
302
|
+
'user',
|
|
303
|
+
].includes(type);
|
|
304
|
+
if (supportsDefault && source.dynamic_options_source === undefined)
|
|
305
|
+
result.default_value = source.default_value ?? null;
|
|
306
|
+
if (type === 'select') {
|
|
307
|
+
result.multiple = source.multiple ?? false;
|
|
308
|
+
if (Array.isArray(source.options))
|
|
309
|
+
result.options = source.options.map((value) => {
|
|
310
|
+
const option = plainObject(value);
|
|
311
|
+
return {
|
|
312
|
+
name: option.name,
|
|
313
|
+
hue: option.hue ?? 'Blue',
|
|
314
|
+
lightness: option.lightness ?? 'Lighter',
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
if (isRecord(source.dynamic_options_source)) {
|
|
318
|
+
const dynamic = source.dynamic_options_source;
|
|
319
|
+
const tableReference = String(dynamic.table_id ?? '');
|
|
320
|
+
const table = receiptTable(receipt, tableReference);
|
|
321
|
+
const tableIdValue = table?.tableId ?? tableReference;
|
|
322
|
+
const remote = remoteByTable.get(tableIdValue) ?? [];
|
|
323
|
+
const fieldReference = String(dynamic.field_id ?? '');
|
|
324
|
+
const fieldIdValue = remote.find((field) => field.id === fieldReference || field.name === fieldReference)?.id ?? fieldReference;
|
|
325
|
+
result.dynamic_options_source = {
|
|
326
|
+
table_id: tableIdValue,
|
|
327
|
+
field_id: fieldIdValue,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (type === 'user' || type === 'group_chat')
|
|
332
|
+
result.multiple = source.multiple ?? true;
|
|
333
|
+
if (type === 'link') {
|
|
334
|
+
const reference = String(source.link_table ?? '');
|
|
335
|
+
result.link_table = receiptTable(receipt, reference)?.tableId ?? reference;
|
|
336
|
+
result.bidirectional = source.bidirectional ?? false;
|
|
337
|
+
if (source.bidirectional_link_field_name !== undefined)
|
|
338
|
+
result.bidirectional_link_field_name =
|
|
339
|
+
source.bidirectional_link_field_name;
|
|
340
|
+
}
|
|
341
|
+
if (type === 'formula')
|
|
342
|
+
result.expression = source.expression;
|
|
343
|
+
if (type === 'lookup')
|
|
344
|
+
for (const key of ['from', 'select', 'where', 'aggregate'])
|
|
345
|
+
if (source[key] !== undefined)
|
|
346
|
+
result[key] = source[key];
|
|
347
|
+
if (type === 'auto_number' && source.style !== undefined)
|
|
348
|
+
result.style = source.style;
|
|
349
|
+
if (type === 'button')
|
|
350
|
+
result.button_config = source.button_config;
|
|
351
|
+
return result;
|
|
352
|
+
}
|
|
353
|
+
function configContains(actual, expected) {
|
|
354
|
+
if (Array.isArray(expected))
|
|
355
|
+
return (Array.isArray(actual) &&
|
|
356
|
+
actual.length === expected.length &&
|
|
357
|
+
expected.every((value, index) => configContains(actual[index], value)));
|
|
358
|
+
if (isRecord(expected))
|
|
359
|
+
return (isRecord(actual) &&
|
|
360
|
+
Object.entries(expected).every(([key, value]) => configContains(actual[key], value)));
|
|
361
|
+
return Object.is(actual, expected);
|
|
362
|
+
}
|
|
363
|
+
function stableJson(value) {
|
|
364
|
+
if (Array.isArray(value))
|
|
365
|
+
return value.map(stableJson);
|
|
366
|
+
if (isRecord(value))
|
|
367
|
+
return Object.fromEntries(Object.keys(value)
|
|
368
|
+
.sort()
|
|
369
|
+
.map((key) => [key, stableJson(value[key])]));
|
|
370
|
+
return value;
|
|
371
|
+
}
|
|
372
|
+
function assertRemoteField(journal, phase, remote, expected, remoteByTable, id) {
|
|
373
|
+
if (!id)
|
|
374
|
+
recoveryBlocked(journal, phase, `Receipt does not contain a confirmed field ID for ${String(expected.name)}`);
|
|
375
|
+
const actual = remote.find((field) => field.id === id);
|
|
376
|
+
const expectedConfig = normalizeFieldConfig(expected, journal.receipt, remoteByTable);
|
|
377
|
+
const actualConfig = actual
|
|
378
|
+
? normalizeFieldConfig(actual.raw, journal.receipt, remoteByTable)
|
|
379
|
+
: undefined;
|
|
380
|
+
if (!actual ||
|
|
381
|
+
actual.name !== expected.name ||
|
|
382
|
+
actual.type !== expected.type ||
|
|
383
|
+
!configContains(actualConfig, expectedConfig))
|
|
384
|
+
recoveryBlocked(journal, phase, `Remote field drift detected for ${String(expected.name)} (${id})`);
|
|
385
|
+
}
|
|
386
|
+
function validateFieldItems(journal, step, fields, remote, remoteByTable) {
|
|
387
|
+
const items = step.items;
|
|
388
|
+
if (!items || items.length !== fields.length)
|
|
389
|
+
recoveryBlocked(journal, step.id, `Receipt does not contain a complete field result map for ${step.id}`);
|
|
390
|
+
const indexes = new Set();
|
|
391
|
+
const retry = [];
|
|
392
|
+
for (const item of items) {
|
|
393
|
+
if (!Number.isInteger(item.index) ||
|
|
394
|
+
item.index < 0 ||
|
|
395
|
+
item.index >= fields.length ||
|
|
396
|
+
indexes.has(item.index))
|
|
397
|
+
recoveryBlocked(journal, step.id, `Invalid field result index in ${step.id}`);
|
|
398
|
+
indexes.add(item.index);
|
|
399
|
+
const expected = fields[item.index];
|
|
400
|
+
if (!expected)
|
|
401
|
+
recoveryBlocked(journal, step.id, `Missing field plan for ${step.id}`);
|
|
402
|
+
if (item.status === 'created') {
|
|
403
|
+
assertRemoteField(journal, step.id, remote, expected, remoteByTable, item.field?.id);
|
|
404
|
+
}
|
|
405
|
+
else if (item.status === 'failed' || item.status === 'not_attempted') {
|
|
406
|
+
if (remote.some((field) => field.name === expected.name))
|
|
407
|
+
recoveryBlocked(journal, step.id, `Remote field ${String(expected.name)} exists without a confirmed receipt ID`);
|
|
408
|
+
retry.push(item.index);
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
recoveryBlocked(journal, step.id, `Unsupported field result in ${step.id}`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return retry;
|
|
415
|
+
}
|
|
416
|
+
function buildInitPlan(options, schema, resources) {
|
|
417
|
+
const steps = [];
|
|
418
|
+
if (schema) {
|
|
419
|
+
schema.tables.forEach((table, i) => steps.push({
|
|
420
|
+
id: i === 0 ? 'create-base' : `create-table:${i}`,
|
|
421
|
+
kind: 'write',
|
|
422
|
+
target: { tableIndex: i, tableName: table.name },
|
|
423
|
+
}));
|
|
424
|
+
schema.tables.forEach((table, i) => {
|
|
425
|
+
if (table.fields.length > 1)
|
|
426
|
+
steps.push({
|
|
427
|
+
id: `create-fields:${i}`,
|
|
428
|
+
kind: 'write',
|
|
429
|
+
target: { tableIndex: i, tableName: table.name, fieldOffset: 1 },
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (options.appToken)
|
|
434
|
+
steps.push({ id: 'update-app', kind: 'write' }, { id: 'bind-base', kind: 'write' });
|
|
435
|
+
else
|
|
436
|
+
steps.push({ id: 'create-app', kind: 'write' });
|
|
437
|
+
steps.push({ id: 'read-catalog', kind: 'read' }, { id: 'scaffold', kind: 'local' });
|
|
438
|
+
const planHash = createHash('sha256')
|
|
439
|
+
.update(JSON.stringify({
|
|
440
|
+
name: options.name,
|
|
441
|
+
description: options.description,
|
|
442
|
+
timeZone: options.timeZone,
|
|
443
|
+
schema,
|
|
444
|
+
baseToken: options.baseToken,
|
|
445
|
+
appToken: options.appToken,
|
|
446
|
+
}))
|
|
447
|
+
.digest('hex');
|
|
448
|
+
return {
|
|
449
|
+
steps,
|
|
450
|
+
planHash,
|
|
451
|
+
resources: {
|
|
452
|
+
...resources,
|
|
453
|
+
baseSource: schema ? 'created' : 'existing',
|
|
454
|
+
appSource: options.appToken ? 'existing' : 'created',
|
|
455
|
+
...(options.appToken ? { appToken: options.appToken.trim() } : {}),
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function prepareResume(journal, options, schema, env) {
|
|
460
|
+
for (const step of journal.receipt.steps) {
|
|
461
|
+
if (step.kind === 'write' && ['running', 'unknown'].includes(step.status))
|
|
462
|
+
recoveryBlocked(journal, step.id, `Write outcome is unknown for ${step.id}; recovery cannot continue`);
|
|
463
|
+
if (step.id === 'create-app' && step.status === 'failed')
|
|
464
|
+
recoveryBlocked(journal, step.id, 'Application creation may have produced an application before returning an error');
|
|
465
|
+
if (step.status === 'partial' && !step.id.startsWith('create-fields:'))
|
|
466
|
+
recoveryBlocked(journal, step.id, `Unsupported partial step ${step.id}`);
|
|
467
|
+
if (step.kind === 'local' &&
|
|
468
|
+
!['not_attempted', 'succeeded'].includes(step.status))
|
|
469
|
+
recoveryBlocked(journal, step.id, 'Local scaffolding may be incomplete and will not be overwritten');
|
|
470
|
+
}
|
|
471
|
+
const receipt = journal.receipt;
|
|
472
|
+
const fieldRetries = new Map();
|
|
473
|
+
let remoteTables = [];
|
|
474
|
+
const needsBaseRead = receipt.resources.baseSource === 'existing' ||
|
|
475
|
+
receipt.steps.some((step) => (step.id === 'create-base' || step.id.startsWith('create-table:')) &&
|
|
476
|
+
step.status === 'succeeded');
|
|
477
|
+
if (needsBaseRead) {
|
|
478
|
+
const token = receipt.resources.baseToken;
|
|
479
|
+
if (!token)
|
|
480
|
+
recoveryBlocked(journal, 'resume-preflight', 'Receipt has no confirmed Base token');
|
|
481
|
+
const baseResponse = runLarkCli(['base', '+base-get', '--base-token', token], env);
|
|
482
|
+
if (receipt.resources.baseSource === 'created' &&
|
|
483
|
+
(baseName(baseResponse) !== requiredString(options.name, '--name') ||
|
|
484
|
+
(options.timeZone !== undefined &&
|
|
485
|
+
baseTimeZone(baseResponse) !== undefined &&
|
|
486
|
+
baseTimeZone(baseResponse) !== options.timeZone)))
|
|
487
|
+
recoveryBlocked(journal, 'resume-preflight', 'Created Base name or time zone changed after initialization started');
|
|
488
|
+
remoteTables = listExistingTables(token, env);
|
|
489
|
+
for (const table of receipt.resources.tables) {
|
|
490
|
+
const actual = remoteTables.find((item) => item.tableId === table.tableId);
|
|
491
|
+
if (!actual || actual.name !== table.name)
|
|
492
|
+
recoveryBlocked(journal, 'resume-preflight', `Remote table drift detected for ${table.name} (${table.tableId})`);
|
|
493
|
+
}
|
|
494
|
+
if (receipt.resources.baseSource === 'existing' &&
|
|
495
|
+
(remoteTables.length !== receipt.resources.tables.length ||
|
|
496
|
+
remoteTables.some((table) => !receipt.resources.tables.some((saved) => saved.tableId === table.tableId && saved.name === table.name))))
|
|
497
|
+
recoveryBlocked(journal, 'resume-preflight', 'Existing Base table set changed after initialization started');
|
|
498
|
+
}
|
|
499
|
+
const remoteByTable = new Map();
|
|
500
|
+
if (schema && receipt.resources.baseToken) {
|
|
501
|
+
for (const [tableIndex, table] of schema.tables.entries()) {
|
|
502
|
+
const step = journal.step(tableIndex === 0 ? 'create-base' : `create-table:${tableIndex}`);
|
|
503
|
+
const saved = receiptTable(receipt, table.name);
|
|
504
|
+
if (step.status === 'succeeded' && saved)
|
|
505
|
+
remoteByTable.set(saved.tableId, listExistingFields(receipt.resources.baseToken, saved.tableId, env));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (schema) {
|
|
509
|
+
for (const [tableIndex, table] of schema.tables.entries()) {
|
|
510
|
+
const createStep = journal.step(tableIndex === 0 ? 'create-base' : `create-table:${tableIndex}`);
|
|
511
|
+
const savedTable = receiptTable(receipt, table.name);
|
|
512
|
+
if (createStep.status === 'succeeded') {
|
|
513
|
+
if (!savedTable)
|
|
514
|
+
recoveryBlocked(journal, createStep.id, `Receipt has no confirmed table ID for ${table.name}`);
|
|
515
|
+
const fields = remoteByTable.get(savedTable.tableId);
|
|
516
|
+
if (!fields)
|
|
517
|
+
recoveryBlocked(journal, createStep.id, `Remote fields were not available for ${table.name}`);
|
|
518
|
+
const primary = table.fields[0];
|
|
519
|
+
if (!primary)
|
|
520
|
+
recoveryBlocked(journal, createStep.id, `Primary field drift detected for ${table.name}`);
|
|
521
|
+
assertRemoteField(journal, createStep.id, fields, primary, remoteByTable, primaryFieldId(requiredString(receipt.resources.baseToken, 'receipt Base token'), savedTable.tableId, env));
|
|
522
|
+
const fieldStep = receipt.steps.find((step) => step.id === `create-fields:${tableIndex}`);
|
|
523
|
+
if (fieldStep && fieldStep.status !== 'not_attempted') {
|
|
524
|
+
const planned = table.fields.slice(1);
|
|
525
|
+
if (fieldStep.status === 'failed' &&
|
|
526
|
+
fieldStep.error?.kind === 'api' &&
|
|
527
|
+
!fieldStep.items) {
|
|
528
|
+
if (planned.some((expected) => fields.some((field) => field.name === expected.name)))
|
|
529
|
+
recoveryBlocked(journal, fieldStep.id, 'A failed field step has matching remote fields without confirmed IDs');
|
|
530
|
+
fieldRetries.set(fieldStep.id, planned.map((_, index) => index));
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
const retry = validateFieldItems(journal, fieldStep, planned, fields, remoteByTable);
|
|
534
|
+
if (fieldStep.status === 'succeeded') {
|
|
535
|
+
if (retry.length > 0)
|
|
536
|
+
recoveryBlocked(journal, fieldStep.id, `Succeeded field step contains incomplete items: ${fieldStep.id}`);
|
|
537
|
+
}
|
|
538
|
+
else if (fieldStep.status === 'partial' ||
|
|
539
|
+
fieldStep.status === 'failed') {
|
|
540
|
+
if (retry.length === 0)
|
|
541
|
+
journal.reconcile(fieldStep.id);
|
|
542
|
+
else
|
|
543
|
+
fieldRetries.set(fieldStep.id, retry);
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
recoveryBlocked(journal, fieldStep.id, `Field recovery cannot continue from ${fieldStep.status}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
else if (createStep.status === 'partial') {
|
|
551
|
+
recoveryBlocked(journal, createStep.id, `Unsupported partial step ${createStep.id}`);
|
|
552
|
+
}
|
|
553
|
+
else if (createStep.status === 'failed') {
|
|
554
|
+
if (createStep.id === 'create-base' || createStep.error?.kind !== 'api')
|
|
555
|
+
recoveryBlocked(journal, createStep.id, `Table creation is not safely retryable for ${createStep.id}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
const createApp = receipt.steps.find((step) => step.id === 'create-app');
|
|
560
|
+
const updateApp = receipt.steps.find((step) => step.id === 'update-app');
|
|
561
|
+
const bindBase = receipt.steps.find((step) => step.id === 'bind-base');
|
|
562
|
+
if (createApp?.status === 'succeeded' ||
|
|
563
|
+
updateApp?.status === 'succeeded' ||
|
|
564
|
+
bindBase?.status === 'succeeded') {
|
|
565
|
+
const token = receipt.resources.appToken;
|
|
566
|
+
const baseTokenValue = receipt.resources.baseToken;
|
|
567
|
+
if (!token || !baseTokenValue)
|
|
568
|
+
recoveryBlocked(journal, 'resume-preflight', 'Receipt has no confirmed application or Base token');
|
|
569
|
+
const projectResponse = runLarkCli([
|
|
570
|
+
'api',
|
|
571
|
+
'GET',
|
|
572
|
+
`/open-apis/base/v3/sites/${encodeURIComponent(token)}/get`,
|
|
573
|
+
], env);
|
|
574
|
+
try {
|
|
575
|
+
validateApplication(projectResponse.data, token, requiredString(options.name, '--name'), options.description ?? '', baseTokenValue, receipt.resources.tables, createApp?.status === 'succeeded' || updateApp?.status === 'succeeded', createApp?.status === 'succeeded' || bindBase?.status === 'succeeded', createApp?.status === 'succeeded');
|
|
576
|
+
if (createApp?.status === 'succeeded' ||
|
|
577
|
+
bindBase?.status === 'succeeded') {
|
|
578
|
+
const catalog = runLarkCli([
|
|
579
|
+
'api',
|
|
580
|
+
'GET',
|
|
581
|
+
`/open-apis/base/v3/sites/${encodeURIComponent(token)}/catalog`,
|
|
582
|
+
], env);
|
|
583
|
+
if (createApp?.status === 'succeeded')
|
|
584
|
+
validateExactCatalog(catalog.data, baseTokenValue, receipt.resources.tables);
|
|
585
|
+
else
|
|
586
|
+
validateCatalog(catalog.data, baseTokenValue, receipt.resources.tables);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
recoveryBlocked(journal, 'resume-preflight', error instanceof Error ? error.message : 'Application drift detected');
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
for (const step of receipt.steps) {
|
|
594
|
+
if (step.kind === 'read') {
|
|
595
|
+
if (step.status === 'unknown')
|
|
596
|
+
recoveryBlocked(journal, step.id, `Read outcome is invalid for recovery: ${step.id}`);
|
|
597
|
+
if (step.status !== 'not_attempted')
|
|
598
|
+
journal.prepare(step.id);
|
|
599
|
+
}
|
|
600
|
+
else if (step.status === 'failed' &&
|
|
601
|
+
step.kind === 'write' &&
|
|
602
|
+
step.error?.kind === 'api' &&
|
|
603
|
+
!['create-base', 'create-app'].includes(step.id) &&
|
|
604
|
+
!step.id.startsWith('create-fields:'))
|
|
605
|
+
journal.prepare(step.id);
|
|
606
|
+
}
|
|
607
|
+
const scaffold = journal.step('scaffold');
|
|
608
|
+
const projectRoot = resolve(dirname(journal.path), '..', '..');
|
|
609
|
+
const scaffoldOutputs = [
|
|
193
610
|
CONFIG_FILE_NAME,
|
|
194
611
|
'catalog.json',
|
|
195
612
|
'page',
|
|
196
613
|
'server-functions',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
614
|
+
];
|
|
615
|
+
if (scaffold.status === 'succeeded') {
|
|
616
|
+
for (const output of scaffoldOutputs)
|
|
617
|
+
if (!existsSync(join(projectRoot, output)))
|
|
618
|
+
recoveryBlocked(journal, 'scaffold', `Completed scaffold output is missing: ${output}`);
|
|
619
|
+
let databaseFunctions;
|
|
620
|
+
try {
|
|
621
|
+
databaseFunctions = databaseFunctionsDirectory(projectRoot);
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
recoveryBlocked(journal, 'scaffold', error instanceof Error
|
|
625
|
+
? error.message
|
|
626
|
+
: 'Function source roots are invalid');
|
|
627
|
+
}
|
|
628
|
+
if (!databaseFunctions) {
|
|
629
|
+
recoveryBlocked(journal, 'scaffold', `Completed scaffold output is missing: ${DATABASE_FUNCTIONS_DIRECTORY}`);
|
|
630
|
+
}
|
|
631
|
+
const config = readConfig(projectRoot);
|
|
632
|
+
const bindings = isRecord(config.bindings) ? config.bindings : {};
|
|
633
|
+
const main = isRecord(bindings.MAIN_BASE) ? bindings.MAIN_BASE : {};
|
|
634
|
+
const configuredTables = Array.isArray(main.tableIds)
|
|
635
|
+
? main.tableIds.map(String)
|
|
636
|
+
: [];
|
|
637
|
+
const expectedTables = receipt.resources.tables.map((table) => table.tableId);
|
|
638
|
+
if (config.appToken !== receipt.resources.appToken ||
|
|
639
|
+
(config.larkProfile ?? undefined) !==
|
|
640
|
+
(receipt.larkProfile ?? undefined) ||
|
|
641
|
+
(config.runtimeProfile ?? ONLINE_RUNTIME_PROFILE.name) !==
|
|
642
|
+
receipt.environment ||
|
|
643
|
+
main.baseToken !== receipt.resources.baseToken ||
|
|
644
|
+
configuredTables.length !== expectedTables.length ||
|
|
645
|
+
configuredTables.some((id) => !expectedTables.includes(id)))
|
|
646
|
+
recoveryBlocked(journal, 'scaffold', 'Completed project configuration does not match the receipt');
|
|
647
|
+
}
|
|
648
|
+
else if (scaffold.status === 'not_attempted' &&
|
|
649
|
+
(scaffoldOutputs.some((output) => existsSync(join(projectRoot, output))) ||
|
|
650
|
+
existsSync(join(projectRoot, DATABASE_FUNCTIONS_DIRECTORY)) ||
|
|
651
|
+
existsSync(join(projectRoot, LEGACY_DATABASE_FUNCTIONS_DIRECTORY)))) {
|
|
652
|
+
recoveryBlocked(journal, 'scaffold', 'Project outputs exist before the recorded scaffold step');
|
|
653
|
+
}
|
|
654
|
+
return { fieldRetries };
|
|
655
|
+
}
|
|
656
|
+
export async function initializeProject(options) {
|
|
657
|
+
const runtimeProfile = resolveRuntimeProfile(options.environment, options.runtimeProfile ?? ONLINE_RUNTIME_PROFILE);
|
|
658
|
+
const workspace = resolve(options.directory);
|
|
659
|
+
const databaseName = requiredString(options.name, '--name');
|
|
660
|
+
if (!options.resume) {
|
|
661
|
+
for (const output of [
|
|
662
|
+
CONFIG_FILE_NAME,
|
|
663
|
+
'catalog.json',
|
|
664
|
+
'page',
|
|
665
|
+
'server-functions',
|
|
666
|
+
DATABASE_FUNCTIONS_DIRECTORY,
|
|
667
|
+
LEGACY_DATABASE_FUNCTIONS_DIRECTORY,
|
|
668
|
+
]) {
|
|
669
|
+
if (existsSync(join(workspace, output))) {
|
|
670
|
+
throw new Error(`Refusing to overwrite ${join(workspace, output)}`);
|
|
671
|
+
}
|
|
201
672
|
}
|
|
673
|
+
assertNoInitReceipt(workspace);
|
|
202
674
|
}
|
|
203
675
|
const schemaPath = options.schema?.trim();
|
|
204
676
|
const existingBaseToken = options.baseToken?.trim();
|
|
205
677
|
const existingAppToken = options.appToken?.trim();
|
|
678
|
+
if (options.appToken !== undefined && !existingAppToken)
|
|
679
|
+
throw new Error('--app-token must be a non-empty string');
|
|
206
680
|
if (Boolean(schemaPath) === Boolean(existingBaseToken)) {
|
|
207
681
|
throw new Error('Provide exactly one of --schema or --base-token');
|
|
208
682
|
}
|
|
683
|
+
let schema;
|
|
684
|
+
try {
|
|
685
|
+
schema = schemaPath ? readInitSchema(schemaPath) : undefined;
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
if (!(error instanceof InitSchemaError))
|
|
689
|
+
throw error;
|
|
690
|
+
throw new InitFailureError(error.message, {
|
|
691
|
+
ok: false,
|
|
692
|
+
status: 'failed',
|
|
693
|
+
phase: 'preflight',
|
|
694
|
+
error: { message: error.message, path: error.path },
|
|
695
|
+
nextAction: 'Correct the schema at the reported path. No remote resources were created.',
|
|
696
|
+
});
|
|
697
|
+
}
|
|
209
698
|
const { env, larkProfile } = resolveLarkProfileEnvironment(await runtimeProfile.commandEnvironment(), options.larkProfile, undefined, runtimeProfile);
|
|
210
|
-
const attempts = runtimeProfile.initAttempts;
|
|
211
699
|
let selectedBaseToken;
|
|
212
700
|
let selectedBaseUrl;
|
|
213
701
|
let selectedBaseName = databaseName;
|
|
@@ -215,227 +703,362 @@ export async function initializeProject(options) {
|
|
|
215
703
|
const baseSource = existingBaseToken
|
|
216
704
|
? 'existing'
|
|
217
705
|
: 'created';
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
const schema = parseSchema(requiredString(schemaPath, '--schema'));
|
|
228
|
-
const [first, ...remaining] = schema.tables;
|
|
229
|
-
if (!first)
|
|
230
|
-
throw new Error('Schema must contain at least one table');
|
|
231
|
-
process.stderr.write(`Creating Base ${databaseName}...\n`);
|
|
232
|
-
const createdBase = await retry('base-create', attempts, () => runLarkCli([
|
|
233
|
-
'base',
|
|
234
|
-
'+base-create',
|
|
235
|
-
'--name',
|
|
236
|
-
databaseName,
|
|
237
|
-
'--time-zone',
|
|
238
|
-
options.timeZone,
|
|
239
|
-
'--table-name',
|
|
240
|
-
first.name,
|
|
241
|
-
'--fields',
|
|
242
|
-
JSON.stringify(first.fields.slice(0, 1)),
|
|
243
|
-
], env));
|
|
244
|
-
selectedBaseToken = baseToken(createdBase);
|
|
245
|
-
selectedBaseUrl = baseUrl(createdBase);
|
|
246
|
-
tables = [
|
|
247
|
-
{ name: first.name, tableId: tableId(createdBase, 'base-create') },
|
|
248
|
-
];
|
|
249
|
-
for (const table of remaining) {
|
|
250
|
-
await pause();
|
|
251
|
-
process.stderr.write(`Creating table ${table.name}...\n`);
|
|
252
|
-
const response = await retry(`table-create ${table.name}`, attempts, () => runLarkCli([
|
|
253
|
-
'base',
|
|
254
|
-
'+table-create',
|
|
255
|
-
'--base-token',
|
|
256
|
-
selectedBaseToken,
|
|
257
|
-
'--name',
|
|
258
|
-
table.name,
|
|
259
|
-
'--fields',
|
|
260
|
-
JSON.stringify(table.fields.slice(0, 1)),
|
|
261
|
-
], env));
|
|
262
|
-
tables.push({
|
|
263
|
-
name: table.name,
|
|
264
|
-
tableId: tableId(response, 'table-create'),
|
|
706
|
+
let journal;
|
|
707
|
+
let resumePreparation;
|
|
708
|
+
try {
|
|
709
|
+
if (options.resume) {
|
|
710
|
+
const plan = buildInitPlan(options, schema, {
|
|
711
|
+
...(existingBaseToken ? { baseToken: existingBaseToken } : {}),
|
|
712
|
+
tables: [],
|
|
265
713
|
});
|
|
714
|
+
journal = InitJournal.resume(workspace, {
|
|
715
|
+
environment: options.environment,
|
|
716
|
+
...(larkProfile ? { larkProfile } : {}),
|
|
717
|
+
planHash: plan.planHash,
|
|
718
|
+
}, plan.steps, plan.resources, env);
|
|
719
|
+
resumePreparation = prepareResume(journal, options, schema, env);
|
|
266
720
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
721
|
+
if (existingBaseToken) {
|
|
722
|
+
selectedBaseToken = requiredString(existingBaseToken, '--base-token');
|
|
723
|
+
process.stderr.write('Reading existing Base tables...\n');
|
|
724
|
+
const existingBase = runLarkCli(['base', '+base-get', '--base-token', selectedBaseToken], env);
|
|
725
|
+
selectedBaseUrl = baseUrl(existingBase);
|
|
726
|
+
selectedBaseName = baseName(existingBase) ?? selectedBaseToken;
|
|
727
|
+
tables = listExistingTables(selectedBaseToken, env);
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
if (!schema)
|
|
731
|
+
throw new Error('Schema is required');
|
|
732
|
+
const [first, ...remaining] = schema.tables;
|
|
733
|
+
journal ??= startJournal(workspace, options, larkProfile, env, schema, {
|
|
734
|
+
tables: [],
|
|
735
|
+
});
|
|
736
|
+
if (!first)
|
|
737
|
+
throw new Error('Schema must contain at least one table');
|
|
738
|
+
const baseStep = journal.step('create-base');
|
|
739
|
+
if (baseStep.status === 'succeeded') {
|
|
740
|
+
selectedBaseToken = requiredString(journal.receipt.resources.baseToken, 'receipt Base token');
|
|
741
|
+
const saved = receiptTable(journal.receipt, first.name);
|
|
742
|
+
if (!saved)
|
|
743
|
+
recoveryBlocked(journal, baseStep.id, `Receipt has no confirmed table ID for ${first.name}`);
|
|
744
|
+
const currentBase = runLarkCli(['base', '+base-get', '--base-token', selectedBaseToken], env);
|
|
745
|
+
selectedBaseUrl = baseUrl(currentBase);
|
|
746
|
+
selectedBaseName = baseName(currentBase) ?? databaseName;
|
|
747
|
+
tables = [saved];
|
|
748
|
+
}
|
|
749
|
+
else {
|
|
750
|
+
process.stderr.write(`Creating Base ${databaseName}...\n`);
|
|
751
|
+
const createdBase = await journal.run('create-base', () => runLarkCli([
|
|
752
|
+
'base',
|
|
753
|
+
'+base-create',
|
|
754
|
+
'--name',
|
|
755
|
+
databaseName,
|
|
756
|
+
...(options.timeZone ? ['--time-zone', options.timeZone] : []),
|
|
757
|
+
'--table-name',
|
|
758
|
+
first.name,
|
|
759
|
+
'--fields',
|
|
760
|
+
JSON.stringify(first.fields.slice(0, 1)),
|
|
761
|
+
], env), (response) => {
|
|
762
|
+
baseToken(response);
|
|
763
|
+
tableId(response, 'base-create');
|
|
764
|
+
});
|
|
765
|
+
selectedBaseToken = baseToken(createdBase);
|
|
766
|
+
selectedBaseUrl = baseUrl(createdBase);
|
|
767
|
+
tables = [
|
|
768
|
+
{ name: first.name, tableId: tableId(createdBase, 'base-create') },
|
|
769
|
+
];
|
|
770
|
+
}
|
|
771
|
+
for (const [index, table] of remaining.entries()) {
|
|
772
|
+
const stepId = `create-table:${index + 1}`;
|
|
773
|
+
const step = journal.step(stepId);
|
|
774
|
+
if (step.status === 'succeeded') {
|
|
775
|
+
const saved = receiptTable(journal.receipt, table.name);
|
|
776
|
+
if (!saved)
|
|
777
|
+
recoveryBlocked(journal, stepId, `Receipt has no confirmed table ID for ${table.name}`);
|
|
778
|
+
tables.push(saved);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
await pause();
|
|
782
|
+
process.stderr.write(`Creating table ${table.name}...\n`);
|
|
783
|
+
const response = await journal.run(stepId, () => runLarkCli([
|
|
784
|
+
'base',
|
|
785
|
+
'+table-create',
|
|
786
|
+
'--base-token',
|
|
787
|
+
selectedBaseToken,
|
|
788
|
+
'--name',
|
|
789
|
+
table.name,
|
|
790
|
+
'--fields',
|
|
791
|
+
JSON.stringify(table.fields.slice(0, 1)),
|
|
792
|
+
], env), (response) => {
|
|
793
|
+
tableId(response, 'table-create');
|
|
794
|
+
});
|
|
795
|
+
tables.push({
|
|
796
|
+
name: table.name,
|
|
797
|
+
tableId: tableId(response, 'table-create'),
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
for (const [tableIndex, table] of schema.tables.entries()) {
|
|
801
|
+
const remainingFields = table.fields.slice(1);
|
|
802
|
+
if (remainingFields.length === 0)
|
|
803
|
+
continue;
|
|
804
|
+
const stepId = `create-fields:${tableIndex}`;
|
|
805
|
+
const step = journal.step(stepId);
|
|
806
|
+
if (step.status === 'succeeded')
|
|
807
|
+
continue;
|
|
808
|
+
const createdTable = tables[tableIndex];
|
|
809
|
+
if (!createdTable) {
|
|
810
|
+
throw new Error(`Missing created table for schema table ${table.name}`);
|
|
811
|
+
}
|
|
812
|
+
await pause();
|
|
813
|
+
process.stderr.write(`Creating remaining fields for ${table.name}\n`);
|
|
814
|
+
const retryIndexes = resumePreparation?.fieldRetries.get(stepId);
|
|
815
|
+
if (retryIndexes)
|
|
816
|
+
journal.prepare(stepId);
|
|
817
|
+
const fieldJournal = journal;
|
|
818
|
+
await fieldJournal.run(stepId, () => createFieldsWithRetry({
|
|
819
|
+
journal: fieldJournal,
|
|
820
|
+
stepId,
|
|
821
|
+
fields: remainingFields,
|
|
822
|
+
initialIndexes: retryIndexes ?? remainingFields.map((_, index) => index),
|
|
823
|
+
env,
|
|
824
|
+
execute: (indexes) => runLarkCli([
|
|
825
|
+
'base',
|
|
826
|
+
'+field-create',
|
|
827
|
+
'--base-token',
|
|
828
|
+
selectedBaseToken,
|
|
829
|
+
'--table-id',
|
|
830
|
+
createdTable.tableId,
|
|
831
|
+
'--json',
|
|
832
|
+
JSON.stringify(indexes.map((index) => remainingFields[index])),
|
|
833
|
+
], env),
|
|
834
|
+
}));
|
|
274
835
|
}
|
|
275
836
|
await pause();
|
|
276
|
-
process.stderr.write(`Creating remaining fields for ${table.name}\n`);
|
|
277
|
-
await retry(`field-create ${table.name}`, attempts, () => runLarkCli([
|
|
278
|
-
'base',
|
|
279
|
-
'+field-create',
|
|
280
|
-
'--base-token',
|
|
281
|
-
selectedBaseToken,
|
|
282
|
-
'--table-id',
|
|
283
|
-
createdTable.tableId,
|
|
284
|
-
'--json',
|
|
285
|
-
JSON.stringify(remainingFields),
|
|
286
|
-
], env));
|
|
287
837
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
baseToken: selectedBaseToken,
|
|
292
|
-
tableIds: tables.map((table) => table.tableId),
|
|
293
|
-
};
|
|
294
|
-
let selectedAppToken;
|
|
295
|
-
const databaseSource = existingAppToken
|
|
296
|
-
? 'existing'
|
|
297
|
-
: 'created';
|
|
298
|
-
if (existingAppToken) {
|
|
299
|
-
selectedAppToken = requiredString(existingAppToken, '--app-token');
|
|
300
|
-
process.stderr.write('Updating the existing application metadata...\n');
|
|
301
|
-
runLarkCli([
|
|
302
|
-
'api',
|
|
303
|
-
'PATCH',
|
|
304
|
-
`/open-apis/base/v3/sites/${encodeURIComponent(selectedAppToken)}/update`,
|
|
305
|
-
'--data',
|
|
306
|
-
JSON.stringify({
|
|
307
|
-
name: options.name,
|
|
308
|
-
description: options.description ?? '',
|
|
309
|
-
}),
|
|
310
|
-
], env);
|
|
311
|
-
process.stderr.write('Binding all tables to the existing application...\n');
|
|
312
|
-
runLarkCli([
|
|
313
|
-
'api',
|
|
314
|
-
'POST',
|
|
315
|
-
`/open-apis/base/v3/sites/${encodeURIComponent(selectedAppToken)}/bind_base`,
|
|
316
|
-
'--data',
|
|
317
|
-
JSON.stringify(binding),
|
|
318
|
-
], env);
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
process.stderr.write('Creating application and binding all tables...\n');
|
|
322
|
-
const createdDatabase = runLarkCli([
|
|
323
|
-
'api',
|
|
324
|
-
'POST',
|
|
325
|
-
'/open-apis/base/v3/sites/create',
|
|
326
|
-
'--data',
|
|
327
|
-
JSON.stringify({
|
|
328
|
-
name: databaseName,
|
|
329
|
-
description: options.description ?? '',
|
|
330
|
-
baseBindings: [binding],
|
|
331
|
-
}),
|
|
332
|
-
], env);
|
|
333
|
-
selectedAppToken = appToken(createdDatabase);
|
|
334
|
-
}
|
|
335
|
-
const encodedToken = encodeURIComponent(selectedAppToken);
|
|
336
|
-
const catalog = await retry('catalog', 5, () => {
|
|
337
|
-
const response = runLarkCli(['api', 'GET', `/open-apis/base/v3/sites/${encodedToken}/catalog`], env);
|
|
338
|
-
return validateCatalog(response.data, selectedBaseToken, tables);
|
|
339
|
-
});
|
|
340
|
-
mkdirSync(workspace, { recursive: true });
|
|
341
|
-
for (const directory of ['page', 'server-functions', 'sql']) {
|
|
342
|
-
cpSync(join(starterRoot, directory), join(workspace, directory), {
|
|
343
|
-
recursive: true,
|
|
838
|
+
journal ??= startJournal(workspace, options, larkProfile, env, schema, {
|
|
839
|
+
baseToken: selectedBaseToken,
|
|
840
|
+
tables,
|
|
344
841
|
});
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
tableIds: tables.map((table) => table.tableId),
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
|
-
});
|
|
371
|
-
const base = {
|
|
372
|
-
name: selectedBaseName,
|
|
373
|
-
token: selectedBaseToken,
|
|
374
|
-
...(selectedBaseUrl ? { url: selectedBaseUrl } : {}),
|
|
375
|
-
tables,
|
|
376
|
-
};
|
|
377
|
-
const baseAppUrl = catalogBaseAppUrl(catalog);
|
|
378
|
-
return {
|
|
379
|
-
ok: true,
|
|
380
|
-
environment: runtimeProfile.name,
|
|
381
|
-
workspace,
|
|
382
|
-
...(larkProfile ? { larkProfile } : {}),
|
|
383
|
-
config: configPath,
|
|
384
|
-
catalog: join(workspace, 'catalog.json'),
|
|
385
|
-
baseSource,
|
|
386
|
-
databaseSource,
|
|
387
|
-
baseToken: selectedBaseToken,
|
|
388
|
-
...(selectedBaseUrl ? { baseUrl: selectedBaseUrl } : {}),
|
|
389
|
-
tables,
|
|
390
|
-
appToken: selectedAppToken,
|
|
391
|
-
...(baseAppUrl ? { baseAppUrl } : {}),
|
|
392
|
-
...(baseSource === 'existing' || databaseSource === 'existing'
|
|
393
|
-
? {
|
|
394
|
-
reused: {
|
|
395
|
-
...(baseSource === 'existing' ? { base } : {}),
|
|
396
|
-
...(databaseSource === 'existing'
|
|
397
|
-
? {
|
|
398
|
-
database: {
|
|
399
|
-
name: databaseName,
|
|
400
|
-
token: selectedAppToken,
|
|
401
|
-
binding,
|
|
402
|
-
},
|
|
403
|
-
}
|
|
404
|
-
: {}),
|
|
405
|
-
},
|
|
842
|
+
const binding = {
|
|
843
|
+
baseToken: selectedBaseToken,
|
|
844
|
+
tableIds: tables.map((table) => table.tableId),
|
|
845
|
+
};
|
|
846
|
+
let selectedAppToken;
|
|
847
|
+
const databaseSource = existingAppToken
|
|
848
|
+
? 'existing'
|
|
849
|
+
: 'created';
|
|
850
|
+
if (existingAppToken) {
|
|
851
|
+
selectedAppToken = requiredString(existingAppToken, '--app-token');
|
|
852
|
+
if (journal.step('update-app').status !== 'succeeded') {
|
|
853
|
+
process.stderr.write('Updating the existing application metadata...\n');
|
|
854
|
+
await journal.run('update-app', () => runLarkCli([
|
|
855
|
+
'api',
|
|
856
|
+
'PATCH',
|
|
857
|
+
`/open-apis/base/v3/sites/${encodeURIComponent(selectedAppToken)}/update`,
|
|
858
|
+
'--data',
|
|
859
|
+
JSON.stringify({
|
|
860
|
+
name: databaseName,
|
|
861
|
+
description: options.description ?? '',
|
|
862
|
+
}),
|
|
863
|
+
], env));
|
|
406
864
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
865
|
+
if (journal.step('bind-base').status !== 'succeeded') {
|
|
866
|
+
process.stderr.write('Binding all tables to the existing application...\n');
|
|
867
|
+
await journal.run('bind-base', () => runLarkCli([
|
|
868
|
+
'api',
|
|
869
|
+
'POST',
|
|
870
|
+
`/open-apis/base/v3/sites/${encodeURIComponent(selectedAppToken)}/bind_base`,
|
|
871
|
+
'--data',
|
|
872
|
+
JSON.stringify(binding),
|
|
873
|
+
], env));
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
if (journal.step('create-app').status === 'succeeded') {
|
|
878
|
+
selectedAppToken = requiredString(journal.receipt.resources.appToken, 'receipt application token');
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
process.stderr.write('Creating application and binding all tables...\n');
|
|
882
|
+
const createdDatabase = await journal.run('create-app', () => runLarkCli([
|
|
883
|
+
'api',
|
|
884
|
+
'POST',
|
|
885
|
+
'/open-apis/base/v3/sites/create',
|
|
886
|
+
'--data',
|
|
887
|
+
JSON.stringify({
|
|
413
888
|
name: databaseName,
|
|
414
|
-
|
|
415
|
-
binding,
|
|
889
|
+
description: options.description ?? '',
|
|
890
|
+
baseBindings: [binding],
|
|
891
|
+
}),
|
|
892
|
+
], env), (response) => {
|
|
893
|
+
appToken(response);
|
|
894
|
+
});
|
|
895
|
+
selectedAppToken = appToken(createdDatabase);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const encodedToken = encodeURIComponent(selectedAppToken);
|
|
899
|
+
const catalog = await journal.run('read-catalog', () => retry('catalog', 5, () => {
|
|
900
|
+
const response = runLarkCli(['api', 'GET', `/open-apis/base/v3/sites/${encodedToken}/catalog`], env);
|
|
901
|
+
return validateCatalog(response.data, selectedBaseToken, tables);
|
|
902
|
+
}));
|
|
903
|
+
if (journal.step('scaffold').status === 'succeeded') {
|
|
904
|
+
let localCatalog;
|
|
905
|
+
try {
|
|
906
|
+
localCatalog = JSON.parse(readFileSync(join(workspace, 'catalog.json'), 'utf8'));
|
|
907
|
+
}
|
|
908
|
+
catch {
|
|
909
|
+
recoveryBlocked(journal, 'scaffold', 'Completed project Catalog is missing or invalid');
|
|
910
|
+
}
|
|
911
|
+
if (JSON.stringify(stableJson(localCatalog)) !==
|
|
912
|
+
JSON.stringify(stableJson(catalog)))
|
|
913
|
+
recoveryBlocked(journal, 'scaffold', 'Remote Catalog changed after local scaffolding completed');
|
|
914
|
+
}
|
|
915
|
+
const configPath = journal.step('scaffold').status === 'succeeded'
|
|
916
|
+
? join(workspace, CONFIG_FILE_NAME)
|
|
917
|
+
: await journal.run('scaffold', () => {
|
|
918
|
+
mkdirSync(workspace, { recursive: true });
|
|
919
|
+
for (const directory of [
|
|
920
|
+
'page',
|
|
921
|
+
'server-functions',
|
|
922
|
+
'mutations',
|
|
923
|
+
DATABASE_FUNCTIONS_DIRECTORY,
|
|
924
|
+
]) {
|
|
925
|
+
cpSync(join(starterRoot, directory), join(workspace, directory), {
|
|
926
|
+
recursive: true,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
writePageTitle(workspace, databaseName);
|
|
930
|
+
runtimeProfile.prepareWorkspace?.(workspace);
|
|
931
|
+
const readmePath = join(workspace, 'README.md');
|
|
932
|
+
if (!existsSync(readmePath)) {
|
|
933
|
+
cpSync(join(starterRoot, 'README.md'), readmePath);
|
|
934
|
+
}
|
|
935
|
+
mergeIgnoreFile(join(workspace, '.gitignore'), join(starterRoot, 'gitignore'));
|
|
936
|
+
mergeIgnoreFile(join(workspace, '.prettierignore'), join(starterRoot, 'prettierignore'));
|
|
937
|
+
writeFileSync(join(workspace, 'catalog.json'), `${JSON.stringify(catalog, null, 2)}\n`);
|
|
938
|
+
return writeConfig(workspace, {
|
|
939
|
+
configVersion: 1,
|
|
940
|
+
...(larkProfile ? { larkProfile } : {}),
|
|
941
|
+
name: basename(workspace),
|
|
942
|
+
...(runtimeProfile.name === ONLINE_RUNTIME_PROFILE.name
|
|
943
|
+
? {}
|
|
944
|
+
: { runtimeProfile: runtimeProfile.name }),
|
|
945
|
+
appToken: selectedAppToken,
|
|
946
|
+
bindings: {
|
|
947
|
+
MAIN_BASE: {
|
|
948
|
+
type: 'base',
|
|
949
|
+
baseToken: selectedBaseToken,
|
|
950
|
+
tableIds: tables.map((table) => table.tableId),
|
|
951
|
+
},
|
|
952
|
+
},
|
|
953
|
+
});
|
|
954
|
+
});
|
|
955
|
+
journal.complete();
|
|
956
|
+
const base = {
|
|
957
|
+
name: selectedBaseName,
|
|
958
|
+
token: selectedBaseToken,
|
|
959
|
+
...(selectedBaseUrl ? { url: selectedBaseUrl } : {}),
|
|
960
|
+
tables,
|
|
961
|
+
};
|
|
962
|
+
const baseAppUrl = catalogBaseAppUrl(catalog);
|
|
963
|
+
return {
|
|
964
|
+
ok: true,
|
|
965
|
+
initialization: {
|
|
966
|
+
journal: journal.path,
|
|
967
|
+
status: 'succeeded',
|
|
968
|
+
...(options.resume ? { resumed: true } : {}),
|
|
969
|
+
},
|
|
970
|
+
environment: runtimeProfile.name,
|
|
971
|
+
workspace,
|
|
972
|
+
...(larkProfile ? { larkProfile } : {}),
|
|
973
|
+
config: configPath,
|
|
974
|
+
catalog: join(workspace, 'catalog.json'),
|
|
975
|
+
baseSource,
|
|
976
|
+
databaseSource,
|
|
977
|
+
baseToken: selectedBaseToken,
|
|
978
|
+
...(selectedBaseUrl ? { baseUrl: selectedBaseUrl } : {}),
|
|
979
|
+
tables,
|
|
980
|
+
appToken: selectedAppToken,
|
|
981
|
+
...(baseAppUrl ? { baseAppUrl } : {}),
|
|
982
|
+
...(baseSource === 'existing' || databaseSource === 'existing'
|
|
983
|
+
? {
|
|
984
|
+
reused: {
|
|
985
|
+
...(baseSource === 'existing' ? { base } : {}),
|
|
986
|
+
...(databaseSource === 'existing'
|
|
987
|
+
? {
|
|
988
|
+
database: {
|
|
989
|
+
name: databaseName,
|
|
990
|
+
token: selectedAppToken,
|
|
991
|
+
binding,
|
|
992
|
+
},
|
|
993
|
+
}
|
|
994
|
+
: {}),
|
|
416
995
|
},
|
|
417
996
|
}
|
|
418
997
|
: {}),
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
998
|
+
created: {
|
|
999
|
+
...(baseSource === 'created' ? { base } : {}),
|
|
1000
|
+
...(databaseSource === 'created'
|
|
1001
|
+
? {
|
|
1002
|
+
database: {
|
|
1003
|
+
name: databaseName,
|
|
1004
|
+
token: selectedAppToken,
|
|
1005
|
+
binding,
|
|
1006
|
+
},
|
|
1007
|
+
}
|
|
1008
|
+
: {}),
|
|
1009
|
+
project: {
|
|
1010
|
+
directory: workspace,
|
|
1011
|
+
config: configPath,
|
|
1012
|
+
catalog: join(workspace, 'catalog.json'),
|
|
1013
|
+
source: workspace,
|
|
1014
|
+
},
|
|
431
1015
|
},
|
|
432
|
-
{
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
command: runtimeProfile.
|
|
436
|
-
|
|
1016
|
+
developmentReadiness: {
|
|
1017
|
+
status: 'not_checked',
|
|
1018
|
+
dependencies: 'not_prepared',
|
|
1019
|
+
command: runtimeProfile.checkCommand,
|
|
1020
|
+
scope: 'local',
|
|
1021
|
+
sqlValidation: 'not_run',
|
|
1022
|
+
description: 'Initialization scaffolds source files but does not prepare Page/FaaS dependencies or run the local project check.',
|
|
437
1023
|
},
|
|
438
|
-
|
|
439
|
-
|
|
1024
|
+
nextSteps: [
|
|
1025
|
+
{
|
|
1026
|
+
action: 'develop',
|
|
1027
|
+
workingDirectory: workspace,
|
|
1028
|
+
description: 'Read catalog.json, then implement the complete Page, Database Functions, Mutation Functions, and server-side FaaS Functions under page/, database-functions/, mutations/, and server-functions/ in one pass.',
|
|
1029
|
+
},
|
|
1030
|
+
{
|
|
1031
|
+
action: 'checkLocal',
|
|
1032
|
+
workingDirectory: workspace,
|
|
1033
|
+
command: runtimeProfile.checkCommand,
|
|
1034
|
+
description: 'Run the supported local check after implementation. It regenerates Page Function input types from local metadata, may install Page/FaaS dependencies, and writes build output; it does not validate server-side Database Function queries or runtime behavior and does not deploy.',
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
action: 'deployPreview',
|
|
1038
|
+
workingDirectory: workspace,
|
|
1039
|
+
command: runtimeProfile.deployCommand,
|
|
1040
|
+
description: 'Run this command after the local check passes; it repeats required build checks, validates server-side Database Function queries, uploads, publishes, and returns baseAppUrl.',
|
|
1041
|
+
},
|
|
1042
|
+
],
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
if (error instanceof InitFailureError)
|
|
1047
|
+
throw error;
|
|
1048
|
+
if (journal)
|
|
1049
|
+
throw journal.failure(error);
|
|
1050
|
+
throw error;
|
|
1051
|
+
}
|
|
1052
|
+
finally {
|
|
1053
|
+
journal?.close();
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
function startJournal(workspace, options, larkProfile, env, schema, resources) {
|
|
1057
|
+
const plan = buildInitPlan(options, schema, resources);
|
|
1058
|
+
return new InitJournal(workspace, {
|
|
1059
|
+
environment: options.environment,
|
|
1060
|
+
...(larkProfile ? { larkProfile } : {}),
|
|
1061
|
+
planHash: plan.planHash,
|
|
1062
|
+
}, plan.steps, plan.resources, env);
|
|
440
1063
|
}
|
|
441
1064
|
//# sourceMappingURL=init.js.map
|