@tiangong-lca/cli 0.1.7 → 0.1.9
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 +30 -11
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +74 -18
- package/dist/src/cli.js.map +1 -1
- package/dist/src/lib/dataset-command.d.ts +2 -0
- package/dist/src/lib/dataset-command.js +20 -0
- package/dist/src/lib/dataset-command.js.map +1 -1
- package/dist/src/lib/dataset-support-cache.d.ts +48 -0
- package/dist/src/lib/dataset-support-cache.js +265 -0
- package/dist/src/lib/dataset-support-cache.js.map +1 -0
- package/dist/src/lib/env.d.ts +9 -0
- package/dist/src/lib/env.js +72 -5
- package/dist/src/lib/env.js.map +1 -1
- package/dist/src/lib/lifecyclemodel-resulting-process.js +3 -1
- package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -1
- package/dist/src/lib/process-save-draft.d.ts +1 -0
- package/dist/src/lib/process-save-draft.js +8 -0
- package/dist/src/lib/process-save-draft.js.map +1 -1
- package/dist/src/lib/publish.d.ts +7 -0
- package/dist/src/lib/publish.js +111 -7
- package/dist/src/lib/publish.js.map +1 -1
- package/dist/src/lib/supabase-client.d.ts +3 -1
- package/dist/src/lib/supabase-client.js +11 -1
- package/dist/src/lib/supabase-client.js.map +1 -1
- package/dist/src/lib/supabase-data-api-contract.d.ts +22 -14
- package/dist/src/lib/supabase-data-api-contract.js +19 -9
- package/dist/src/lib/supabase-data-api-contract.js.map +1 -1
- package/dist/src/lib/supabase-json-ordered-write.js +3 -1
- package/dist/src/lib/supabase-json-ordered-write.js.map +1 -1
- package/package.json +4 -2
|
@@ -11,6 +11,7 @@ export type DatasetCommandTransport = {
|
|
|
11
11
|
};
|
|
12
12
|
declare function readOptionalRuleVerification(extraData?: JsonObject): boolean | null | undefined;
|
|
13
13
|
declare function readOptionalModelId(extraData: JsonObject | undefined, allowNull: boolean): string | null | undefined;
|
|
14
|
+
declare function readOptionalModelVersion(extraData: JsonObject | undefined): string | null | undefined;
|
|
14
15
|
export declare function deriveSupabaseFunctionsBaseUrl(apiBaseUrl: string): string;
|
|
15
16
|
export declare function buildDatasetCommandTransport(options: {
|
|
16
17
|
runtime: SupabaseDataRuntime;
|
|
@@ -48,6 +49,7 @@ export declare function deleteDatasetRecord(options: {
|
|
|
48
49
|
export declare const __testInternals: {
|
|
49
50
|
deriveSupabaseFunctionsBaseUrl: typeof deriveSupabaseFunctionsBaseUrl;
|
|
50
51
|
readOptionalModelId: typeof readOptionalModelId;
|
|
52
|
+
readOptionalModelVersion: typeof readOptionalModelVersion;
|
|
51
53
|
readOptionalRuleVerification: typeof readOptionalRuleVerification;
|
|
52
54
|
};
|
|
53
55
|
export {};
|
|
@@ -45,6 +45,17 @@ function readOptionalModelId(extraData, allowNull) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
+
function readOptionalModelVersion(extraData) {
|
|
49
|
+
if (!extraData) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const value = extraData.modelVersion ?? extraData.model_version;
|
|
53
|
+
const trimmed = trimToken(value);
|
|
54
|
+
if (trimmed) {
|
|
55
|
+
return trimmed;
|
|
56
|
+
}
|
|
57
|
+
return value === null ? null : undefined;
|
|
58
|
+
}
|
|
48
59
|
function requireCommandSuccessPayload(payload, url) {
|
|
49
60
|
const normalized = requireRemoteOkPayload(payload, url);
|
|
50
61
|
if (!isRecord(normalized) || normalized.ok !== true) {
|
|
@@ -111,6 +122,10 @@ export async function createDatasetRecord(options) {
|
|
|
111
122
|
if (modelId !== undefined) {
|
|
112
123
|
body.modelId = modelId;
|
|
113
124
|
}
|
|
125
|
+
const modelVersion = readOptionalModelVersion(options.extraData);
|
|
126
|
+
if (modelVersion !== undefined) {
|
|
127
|
+
body.modelVersion = modelVersion;
|
|
128
|
+
}
|
|
114
129
|
const ruleVerification = readOptionalRuleVerification(options.extraData);
|
|
115
130
|
if (ruleVerification !== undefined) {
|
|
116
131
|
body.ruleVerification = ruleVerification;
|
|
@@ -133,6 +148,10 @@ export async function saveDraftDatasetRecord(options) {
|
|
|
133
148
|
if (modelId !== undefined) {
|
|
134
149
|
body.modelId = modelId;
|
|
135
150
|
}
|
|
151
|
+
const modelVersion = readOptionalModelVersion(options.extraData);
|
|
152
|
+
if (modelVersion !== undefined) {
|
|
153
|
+
body.modelVersion = modelVersion;
|
|
154
|
+
}
|
|
136
155
|
const ruleVerification = readOptionalRuleVerification(options.extraData);
|
|
137
156
|
if (ruleVerification !== undefined) {
|
|
138
157
|
body.ruleVerification = ruleVerification;
|
|
@@ -158,6 +177,7 @@ export async function deleteDatasetRecord(options) {
|
|
|
158
177
|
export const __testInternals = {
|
|
159
178
|
deriveSupabaseFunctionsBaseUrl,
|
|
160
179
|
readOptionalModelId,
|
|
180
|
+
readOptionalModelVersion,
|
|
161
181
|
readOptionalRuleVerification,
|
|
162
182
|
};
|
|
163
183
|
//# sourceMappingURL=dataset-command.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-command.js","sourceRoot":"","sources":["../../../src/lib/dataset-command.ts"],"names":[],"mappings":"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","sourcesContent":["import { CliError } from './errors.js';\nimport type { FetchLike } from './http.js';\nimport { postJson, requireRemoteOkPayload } from './http.js';\nimport {\n buildSupabaseAuthHeaders,\n deriveSupabaseProjectBaseUrl,\n requireSupabaseRestRuntime,\n type SupabaseDataRuntime,\n} from './supabase-client.js';\nimport { createSupabaseDataRuntime } from './supabase-session.js';\n\ntype JsonObject = Record<string, unknown>;\n\nexport type DatasetCommandTable =\n | 'contacts'\n | 'sources'\n | 'unitgroups'\n | 'flowproperties'\n | 'flows'\n | 'processes'\n | 'lifecyclemodels';\n\nexport type DatasetCommandTransport = {\n functionsBaseUrl: string;\n publishableKey: string;\n accessToken: string;\n fetchImpl: FetchLike;\n timeoutMs: number;\n};\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction trimToken(value: unknown): string | null {\n if (typeof value !== 'string') {\n return null;\n }\n\n const trimmed = value.trim();\n return trimmed ? trimmed : null;\n}\n\nfunction readOptionalRuleVerification(extraData?: JsonObject): boolean | null | undefined {\n if (!extraData) {\n return undefined;\n }\n\n if ('ruleVerification' in extraData) {\n const value = extraData.ruleVerification;\n if (typeof value === 'boolean' || value === null) {\n return value;\n }\n return undefined;\n }\n\n if ('rule_verification' in extraData) {\n const value = extraData.rule_verification;\n if (typeof value === 'boolean' || value === null) {\n return value;\n }\n }\n\n return undefined;\n}\n\nfunction readOptionalModelId(\n extraData: JsonObject | undefined,\n allowNull: boolean,\n): string | null | undefined {\n if (!extraData) {\n return undefined;\n }\n\n const value = extraData.modelId ?? extraData.model_id;\n const trimmed = trimToken(value);\n if (trimmed) {\n return trimmed;\n }\n\n if (allowNull && value === null) {\n return null;\n }\n\n return undefined;\n}\n\nfunction requireCommandSuccessPayload(payload: unknown, url: string): JsonObject {\n const normalized = requireRemoteOkPayload(payload, url);\n if (!isRecord(normalized) || normalized.ok !== true) {\n throw new CliError(`Dataset command returned an unexpected payload for ${url}`, {\n code: 'REMOTE_RESPONSE_INVALID',\n exitCode: 1,\n details: normalized,\n });\n }\n\n return normalized;\n}\n\nasync function invokeDatasetCommand(options: {\n transport: DatasetCommandTransport;\n commandName: 'app_dataset_create' | 'app_dataset_save_draft' | 'app_dataset_delete';\n body: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const url = `${options.transport.functionsBaseUrl}/${options.commandName}`;\n try {\n options.beforeDispatch?.();\n } catch {\n throw new CliError('Dataset command was blocked before request dispatch.', {\n code: 'DATASET_COMMAND_BEFORE_DISPATCH_FAILED',\n exitCode: 1,\n });\n }\n return requireCommandSuccessPayload(\n await postJson({\n url,\n headers: {\n ...buildSupabaseAuthHeaders(\n options.transport.publishableKey,\n options.transport.accessToken,\n ),\n 'Content-Type': 'application/json',\n },\n body: options.body,\n timeoutMs: options.transport.timeoutMs,\n fetchImpl: options.transport.fetchImpl,\n }),\n url,\n );\n}\n\nexport function deriveSupabaseFunctionsBaseUrl(apiBaseUrl: string): string {\n return `${deriveSupabaseProjectBaseUrl(apiBaseUrl)}/functions/v1`;\n}\n\nexport async function buildDatasetCommandTransport(options: {\n runtime: SupabaseDataRuntime;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<DatasetCommandTransport> {\n return {\n functionsBaseUrl: deriveSupabaseFunctionsBaseUrl(options.runtime.apiBaseUrl),\n publishableKey: options.runtime.publishableKey,\n accessToken: await options.runtime.getAccessToken(),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n };\n}\n\nexport async function resolveDatasetCommandTransport(options: {\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<DatasetCommandTransport> {\n return buildDatasetCommandTransport({\n runtime: createSupabaseDataRuntime({\n runtime: requireSupabaseRestRuntime(options.env),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n }),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n });\n}\n\nexport async function createDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n payload: JsonObject;\n extraData?: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const body: JsonObject = {\n table: options.table,\n id: options.id,\n jsonOrdered: options.payload,\n };\n const modelId = readOptionalModelId(options.extraData, true);\n if (modelId !== undefined) {\n body.modelId = modelId;\n }\n const ruleVerification = readOptionalRuleVerification(options.extraData);\n if (ruleVerification !== undefined) {\n body.ruleVerification = ruleVerification;\n }\n\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_create',\n body,\n beforeDispatch: options.beforeDispatch,\n });\n}\n\nexport async function saveDraftDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n version: string;\n payload: JsonObject;\n extraData?: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const body: JsonObject = {\n table: options.table,\n id: options.id,\n version: options.version,\n jsonOrdered: options.payload,\n };\n const modelId = readOptionalModelId(options.extraData, false);\n if (modelId !== undefined) {\n body.modelId = modelId;\n }\n const ruleVerification = readOptionalRuleVerification(options.extraData);\n if (ruleVerification !== undefined) {\n body.ruleVerification = ruleVerification;\n }\n\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_save_draft',\n body,\n beforeDispatch: options.beforeDispatch,\n });\n}\n\nexport async function deleteDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n version: string;\n}): Promise<JsonObject> {\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_delete',\n body: {\n table: options.table,\n id: options.id,\n version: options.version,\n },\n });\n}\n\nexport const __testInternals = {\n deriveSupabaseFunctionsBaseUrl,\n readOptionalModelId,\n readOptionalRuleVerification,\n};\n"]}
|
|
1
|
+
{"version":3,"file":"dataset-command.js","sourceRoot":"","sources":["../../../src/lib/dataset-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EACL,wBAAwB,EACxB,4BAA4B,EAC5B,0BAA0B,GAE3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAqBlE,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,SAAS,4BAA4B,CAAC,SAAsB;IAC1D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,kBAAkB,IAAI,SAAS,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,SAAS,CAAC,gBAAgB,CAAC;QACzC,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjD,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,mBAAmB,IAAI,SAAS,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,SAAS,CAAC,iBAAiB,CAAC;QAC1C,IAAI,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,mBAAmB,CAC1B,SAAiC,EACjC,SAAkB;IAElB,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,IAAI,SAAS,CAAC,QAAQ,CAAC;IACtD,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,wBAAwB,CAAC,SAAiC;IACjE,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,KAAK,GAAG,SAAS,CAAC,YAAY,IAAI,SAAS,CAAC,aAAa,CAAC;IAChE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IACjC,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC3C,CAAC;AAED,SAAS,4BAA4B,CAAC,OAAgB,EAAE,GAAW;IACjE,MAAM,UAAU,GAAG,sBAAsB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IACxD,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,UAAU,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,IAAI,QAAQ,CAAC,sDAAsD,GAAG,EAAE,EAAE;YAC9E,IAAI,EAAE,yBAAyB;YAC/B,QAAQ,EAAE,CAAC;YACX,OAAO,EAAE,UAAU;SACpB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,KAAK,UAAU,oBAAoB,CAAC,OAKnC;IACC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC,gBAAgB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAC3E,IAAI,CAAC;QACH,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,QAAQ,CAAC,sDAAsD,EAAE;YACzE,IAAI,EAAE,wCAAwC;YAC9C,QAAQ,EAAE,CAAC;SACZ,CAAC,CAAC;IACL,CAAC;IACD,OAAO,4BAA4B,CACjC,MAAM,QAAQ,CAAC;QACb,GAAG;QACH,OAAO,EAAE;YACP,GAAG,wBAAwB,CACzB,OAAO,CAAC,SAAS,CAAC,cAAc,EAChC,OAAO,CAAC,SAAS,CAAC,WAAW,CAC9B;YACD,cAAc,EAAE,kBAAkB;SACnC;QACD,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,SAAS;QACtC,SAAS,EAAE,OAAO,CAAC,SAAS,CAAC,SAAS;KACvC,CAAC,EACF,GAAG,CACJ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,8BAA8B,CAAC,UAAkB;IAC/D,OAAO,GAAG,4BAA4B,CAAC,UAAU,CAAC,eAAe,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAAC,OAIlD;IACC,OAAO;QACL,gBAAgB,EAAE,8BAA8B,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC;QAC5E,cAAc,EAAE,OAAO,CAAC,OAAO,CAAC,cAAc;QAC9C,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,EAAE;QACnD,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAAC,OAIpD;IACC,OAAO,4BAA4B,CAAC;QAClC,OAAO,EAAE,yBAAyB,CAAC;YACjC,OAAO,EAAE,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC;YAChD,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;SAC7B,CAAC;QACF,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;KAC7B,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAOzC;IACC,MAAM,IAAI,GAAe;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,WAAW,EAAE,OAAO,CAAC,OAAO;KAC7B,CAAC;IACF,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC7D,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IACD,MAAM,YAAY,GAAG,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACjE,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;IACD,MAAM,gBAAgB,GAAG,4BAA4B,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACzE,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC3C,CAAC;IAED,OAAO,oBAAoB,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,oBAAoB;QACjC,IAAI;QACJ,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,OAQ5C;IACC,MAAM,IAAI,GAAe;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,OAAO,EAAE,OAAO,CAAC,OAAO;QACxB,WAAW,EAAE,OAAO,CAAC,OAAO;KAC7B,CAAC;IACF,MAAM,OAAO,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC9D,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IACD,MAAM,YAAY,GAAG,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACjE,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;IACnC,CAAC;IACD,MAAM,gBAAgB,GAAG,4BAA4B,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACzE,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACnC,IAAI,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;IAC3C,CAAC;IAED,OAAO,oBAAoB,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,wBAAwB;QACrC,IAAI;QACJ,cAAc,EAAE,OAAO,CAAC,cAAc;KACvC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAKzC;IACC,OAAO,oBAAoB,CAAC;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,WAAW,EAAE,oBAAoB;QACjC,IAAI,EAAE;YACJ,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,8BAA8B;IAC9B,mBAAmB;IACnB,wBAAwB;IACxB,4BAA4B;CAC7B,CAAC","sourcesContent":["import { CliError } from './errors.js';\nimport type { FetchLike } from './http.js';\nimport { postJson, requireRemoteOkPayload } from './http.js';\nimport {\n buildSupabaseAuthHeaders,\n deriveSupabaseProjectBaseUrl,\n requireSupabaseRestRuntime,\n type SupabaseDataRuntime,\n} from './supabase-client.js';\nimport { createSupabaseDataRuntime } from './supabase-session.js';\n\ntype JsonObject = Record<string, unknown>;\n\nexport type DatasetCommandTable =\n | 'contacts'\n | 'sources'\n | 'unitgroups'\n | 'flowproperties'\n | 'flows'\n | 'processes'\n | 'lifecyclemodels';\n\nexport type DatasetCommandTransport = {\n functionsBaseUrl: string;\n publishableKey: string;\n accessToken: string;\n fetchImpl: FetchLike;\n timeoutMs: number;\n};\n\nfunction isRecord(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction trimToken(value: unknown): string | null {\n if (typeof value !== 'string') {\n return null;\n }\n\n const trimmed = value.trim();\n return trimmed ? trimmed : null;\n}\n\nfunction readOptionalRuleVerification(extraData?: JsonObject): boolean | null | undefined {\n if (!extraData) {\n return undefined;\n }\n\n if ('ruleVerification' in extraData) {\n const value = extraData.ruleVerification;\n if (typeof value === 'boolean' || value === null) {\n return value;\n }\n return undefined;\n }\n\n if ('rule_verification' in extraData) {\n const value = extraData.rule_verification;\n if (typeof value === 'boolean' || value === null) {\n return value;\n }\n }\n\n return undefined;\n}\n\nfunction readOptionalModelId(\n extraData: JsonObject | undefined,\n allowNull: boolean,\n): string | null | undefined {\n if (!extraData) {\n return undefined;\n }\n\n const value = extraData.modelId ?? extraData.model_id;\n const trimmed = trimToken(value);\n if (trimmed) {\n return trimmed;\n }\n\n if (allowNull && value === null) {\n return null;\n }\n\n return undefined;\n}\n\nfunction readOptionalModelVersion(extraData: JsonObject | undefined): string | null | undefined {\n if (!extraData) {\n return undefined;\n }\n\n const value = extraData.modelVersion ?? extraData.model_version;\n const trimmed = trimToken(value);\n if (trimmed) {\n return trimmed;\n }\n\n return value === null ? null : undefined;\n}\n\nfunction requireCommandSuccessPayload(payload: unknown, url: string): JsonObject {\n const normalized = requireRemoteOkPayload(payload, url);\n if (!isRecord(normalized) || normalized.ok !== true) {\n throw new CliError(`Dataset command returned an unexpected payload for ${url}`, {\n code: 'REMOTE_RESPONSE_INVALID',\n exitCode: 1,\n details: normalized,\n });\n }\n\n return normalized;\n}\n\nasync function invokeDatasetCommand(options: {\n transport: DatasetCommandTransport;\n commandName: 'app_dataset_create' | 'app_dataset_save_draft' | 'app_dataset_delete';\n body: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const url = `${options.transport.functionsBaseUrl}/${options.commandName}`;\n try {\n options.beforeDispatch?.();\n } catch {\n throw new CliError('Dataset command was blocked before request dispatch.', {\n code: 'DATASET_COMMAND_BEFORE_DISPATCH_FAILED',\n exitCode: 1,\n });\n }\n return requireCommandSuccessPayload(\n await postJson({\n url,\n headers: {\n ...buildSupabaseAuthHeaders(\n options.transport.publishableKey,\n options.transport.accessToken,\n ),\n 'Content-Type': 'application/json',\n },\n body: options.body,\n timeoutMs: options.transport.timeoutMs,\n fetchImpl: options.transport.fetchImpl,\n }),\n url,\n );\n}\n\nexport function deriveSupabaseFunctionsBaseUrl(apiBaseUrl: string): string {\n return `${deriveSupabaseProjectBaseUrl(apiBaseUrl)}/functions/v1`;\n}\n\nexport async function buildDatasetCommandTransport(options: {\n runtime: SupabaseDataRuntime;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<DatasetCommandTransport> {\n return {\n functionsBaseUrl: deriveSupabaseFunctionsBaseUrl(options.runtime.apiBaseUrl),\n publishableKey: options.runtime.publishableKey,\n accessToken: await options.runtime.getAccessToken(),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n };\n}\n\nexport async function resolveDatasetCommandTransport(options: {\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n timeoutMs: number;\n}): Promise<DatasetCommandTransport> {\n return buildDatasetCommandTransport({\n runtime: createSupabaseDataRuntime({\n runtime: requireSupabaseRestRuntime(options.env),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n }),\n fetchImpl: options.fetchImpl,\n timeoutMs: options.timeoutMs,\n });\n}\n\nexport async function createDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n payload: JsonObject;\n extraData?: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const body: JsonObject = {\n table: options.table,\n id: options.id,\n jsonOrdered: options.payload,\n };\n const modelId = readOptionalModelId(options.extraData, true);\n if (modelId !== undefined) {\n body.modelId = modelId;\n }\n const modelVersion = readOptionalModelVersion(options.extraData);\n if (modelVersion !== undefined) {\n body.modelVersion = modelVersion;\n }\n const ruleVerification = readOptionalRuleVerification(options.extraData);\n if (ruleVerification !== undefined) {\n body.ruleVerification = ruleVerification;\n }\n\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_create',\n body,\n beforeDispatch: options.beforeDispatch,\n });\n}\n\nexport async function saveDraftDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n version: string;\n payload: JsonObject;\n extraData?: JsonObject;\n beforeDispatch?: () => void;\n}): Promise<JsonObject> {\n const body: JsonObject = {\n table: options.table,\n id: options.id,\n version: options.version,\n jsonOrdered: options.payload,\n };\n const modelId = readOptionalModelId(options.extraData, false);\n if (modelId !== undefined) {\n body.modelId = modelId;\n }\n const modelVersion = readOptionalModelVersion(options.extraData);\n if (modelVersion !== undefined) {\n body.modelVersion = modelVersion;\n }\n const ruleVerification = readOptionalRuleVerification(options.extraData);\n if (ruleVerification !== undefined) {\n body.ruleVerification = ruleVerification;\n }\n\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_save_draft',\n body,\n beforeDispatch: options.beforeDispatch,\n });\n}\n\nexport async function deleteDatasetRecord(options: {\n transport: DatasetCommandTransport;\n table: DatasetCommandTable;\n id: string;\n version: string;\n}): Promise<JsonObject> {\n return invokeDatasetCommand({\n transport: options.transport,\n commandName: 'app_dataset_delete',\n body: {\n table: options.table,\n id: options.id,\n version: options.version,\n },\n });\n}\n\nexport const __testInternals = {\n deriveSupabaseFunctionsBaseUrl,\n readOptionalModelId,\n readOptionalModelVersion,\n readOptionalRuleVerification,\n};\n"]}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type ResolveAuthIdentitySession } from './auth-identity-receipt.js';
|
|
2
|
+
import type { FetchLike } from './http.js';
|
|
3
|
+
import { type DatasetMaintenanceSnapshotCompleteness } from './dataset-maintenance-pagination.js';
|
|
4
|
+
declare const TABLES: readonly ['flowproperties', 'unitgroups'];
|
|
5
|
+
type Table = (typeof TABLES)[number];
|
|
6
|
+
export type RunDatasetSupportCacheExportOptions = {
|
|
7
|
+
outDir: string;
|
|
8
|
+
env: NodeJS.ProcessEnv;
|
|
9
|
+
fetchImpl: FetchLike;
|
|
10
|
+
cliVersion: string;
|
|
11
|
+
stateCodes?: number[];
|
|
12
|
+
pageSize?: number;
|
|
13
|
+
timeoutMs?: number;
|
|
14
|
+
expectedProjectRef?: string;
|
|
15
|
+
expectedUserId?: string;
|
|
16
|
+
now?: Date;
|
|
17
|
+
resolveSessionImpl?: ResolveAuthIdentitySession;
|
|
18
|
+
monotonicNow?: () => number;
|
|
19
|
+
};
|
|
20
|
+
export type DatasetSupportCacheExportReport = {
|
|
21
|
+
schema_version: 1;
|
|
22
|
+
command: 'dataset support-cache export';
|
|
23
|
+
status: 'completed';
|
|
24
|
+
remote_write_mode: 'read-only';
|
|
25
|
+
captured_at_utc: string;
|
|
26
|
+
project_ref: string;
|
|
27
|
+
account: {
|
|
28
|
+
user_id: string;
|
|
29
|
+
session_source: string;
|
|
30
|
+
};
|
|
31
|
+
filters: {
|
|
32
|
+
state_codes: number[];
|
|
33
|
+
requested_page_size: number;
|
|
34
|
+
};
|
|
35
|
+
snapshot: {
|
|
36
|
+
status: 'observed-stable';
|
|
37
|
+
transactional_snapshot: false;
|
|
38
|
+
observations: 2;
|
|
39
|
+
};
|
|
40
|
+
completeness: DatasetMaintenanceSnapshotCompleteness<Table>[];
|
|
41
|
+
tables: Record<Table, {
|
|
42
|
+
rows: number;
|
|
43
|
+
sha256: string;
|
|
44
|
+
}>;
|
|
45
|
+
artifacts: Record<Table | 'identity' | 'report', string>;
|
|
46
|
+
};
|
|
47
|
+
export declare function runDatasetSupportCacheExport(options: RunDatasetSupportCacheExportOptions): Promise<DatasetSupportCacheExportReport>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// data-api-relations: flowproperties, unitgroups
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { runAuthIdentityReceipt, } from './auth-identity-receipt.js';
|
|
6
|
+
import { CliError } from './errors.js';
|
|
7
|
+
import { buildSnapshotCompleteness, fetchCompletePostgrestPages, parseExactContentRange, } from './dataset-maintenance-pagination.js';
|
|
8
|
+
import { buildSupabaseAuthHeaders, deriveSupabaseRestBaseUrl, requireSupabaseRestRuntime, } from './supabase-client.js';
|
|
9
|
+
import { applyDataApiProfileHeaders, buildDataApiUrl, resolveDataApiCapabilityFromUrl, } from './supabase-data-api-contract.js';
|
|
10
|
+
import { resolveSupabaseUserSession, } from './supabase-session.js';
|
|
11
|
+
const TABLES = ['flowproperties', 'unitgroups'];
|
|
12
|
+
const MAX_ROWS = 100_000;
|
|
13
|
+
const MAX_PAGES = 1_000;
|
|
14
|
+
const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
15
|
+
const MAX_TOTAL_BYTES = 64 * 1024 * 1024;
|
|
16
|
+
const MAX_DURATION_MS = 120_000;
|
|
17
|
+
function fail(code, message, exitCode = 1) {
|
|
18
|
+
throw new CliError(message, { code: `DATASET_SUPPORT_CACHE_${code}`, exitCode });
|
|
19
|
+
}
|
|
20
|
+
function integer(value, minimum, maximum, label) {
|
|
21
|
+
if (!Number.isSafeInteger(value) || value < minimum || value > maximum)
|
|
22
|
+
fail('OPTION_INVALID', `${label} is outside the supported integer range.`, 2);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
function object(value) {
|
|
26
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function rowsFrom(value, states) {
|
|
29
|
+
if (!Array.isArray(value))
|
|
30
|
+
fail('ROWS_INVALID', 'Support response must be an array.');
|
|
31
|
+
return value.map((row) => {
|
|
32
|
+
if (!object(row) ||
|
|
33
|
+
typeof row.id !== 'string' ||
|
|
34
|
+
!row.id.trim() ||
|
|
35
|
+
typeof row.version !== 'string' ||
|
|
36
|
+
!row.version.trim() ||
|
|
37
|
+
!Number.isSafeInteger(row.state_code) ||
|
|
38
|
+
!object(row.json))
|
|
39
|
+
fail('ROWS_INVALID', 'Support response contains an invalid row.');
|
|
40
|
+
if (!states.includes(row.state_code))
|
|
41
|
+
fail('STATE_FENCE_VIOLATION', 'Support response escaped the requested state filters.');
|
|
42
|
+
return {
|
|
43
|
+
id: row.id,
|
|
44
|
+
version: row.version,
|
|
45
|
+
state_code: row.state_code,
|
|
46
|
+
json: row.json,
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function readBounded(response) {
|
|
51
|
+
if (Number(response.headers.get('content-length')) > MAX_RESPONSE_BYTES)
|
|
52
|
+
fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');
|
|
53
|
+
if (!response.body) {
|
|
54
|
+
const text = await response.text();
|
|
55
|
+
if (Buffer.byteLength(text) > MAX_RESPONSE_BYTES)
|
|
56
|
+
fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');
|
|
57
|
+
return text;
|
|
58
|
+
}
|
|
59
|
+
const reader = response.body.getReader();
|
|
60
|
+
const chunks = [];
|
|
61
|
+
let bytes = 0;
|
|
62
|
+
try {
|
|
63
|
+
for (;;) {
|
|
64
|
+
const chunk = await reader.read();
|
|
65
|
+
if (chunk.done)
|
|
66
|
+
break;
|
|
67
|
+
bytes += chunk.value.byteLength;
|
|
68
|
+
if (bytes > MAX_RESPONSE_BYTES) {
|
|
69
|
+
await reader.cancel();
|
|
70
|
+
fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');
|
|
71
|
+
}
|
|
72
|
+
chunks.push(chunk.value);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
reader.releaseLock();
|
|
77
|
+
}
|
|
78
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
79
|
+
}
|
|
80
|
+
function rowBytes(rows) {
|
|
81
|
+
return rows.map((row) => JSON.stringify(row) + '\n').join('');
|
|
82
|
+
}
|
|
83
|
+
function digest(bytes) {
|
|
84
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
85
|
+
}
|
|
86
|
+
export async function runDatasetSupportCacheExport(options) {
|
|
87
|
+
if (!options.outDir.trim())
|
|
88
|
+
fail('OUT_DIR_REQUIRED', 'A fresh --out-dir is required.', 2);
|
|
89
|
+
const outDir = path.resolve(options.outDir);
|
|
90
|
+
if (lstatSync(outDir, { throwIfNoEntry: false }))
|
|
91
|
+
fail('OUTPUT_EXISTS', 'Support export output already exists.', 2);
|
|
92
|
+
const pageSize = integer(options.pageSize ?? 1000, 1, 5000, 'Page size');
|
|
93
|
+
const timeoutMs = integer(options.timeoutMs ?? 10000, 1, MAX_DURATION_MS, 'Timeout');
|
|
94
|
+
const states = [...new Set(options.stateCodes ?? [100])].sort((a, b) => a - b);
|
|
95
|
+
if (!states.length)
|
|
96
|
+
fail('OPTION_INVALID', 'At least one state code is required.', 2);
|
|
97
|
+
states.forEach((state) => integer(state, -2147483648, 2147483647, 'State code'));
|
|
98
|
+
const runtime = requireSupabaseRestRuntime(options.env);
|
|
99
|
+
if (runtime.authMode !== 'oauth')
|
|
100
|
+
fail('OAUTH_REQUIRED', 'Support export requires a CLI OAuth session.', 2);
|
|
101
|
+
const clock = options.monotonicNow ?? (() => performance.now());
|
|
102
|
+
const started = clock();
|
|
103
|
+
const overall = AbortSignal.timeout(MAX_DURATION_MS);
|
|
104
|
+
const fetchImpl = (url, init = {}) => options.fetchImpl(url, {
|
|
105
|
+
...init,
|
|
106
|
+
signal: AbortSignal.any([overall, init.signal ?? AbortSignal.timeout(timeoutMs)]),
|
|
107
|
+
});
|
|
108
|
+
const resolver = options.resolveSessionImpl ?? resolveSupabaseUserSession;
|
|
109
|
+
const holder = {};
|
|
110
|
+
const identity = await runAuthIdentityReceipt({
|
|
111
|
+
env: options.env,
|
|
112
|
+
fetchImpl,
|
|
113
|
+
cliVersion: options.cliVersion,
|
|
114
|
+
expectedProjectRef: options.expectedProjectRef,
|
|
115
|
+
expectedUserId: options.expectedUserId,
|
|
116
|
+
timeoutMs,
|
|
117
|
+
now: options.now,
|
|
118
|
+
resolveSessionImpl: async (request) => {
|
|
119
|
+
holder.session = await resolver(request);
|
|
120
|
+
return holder.session;
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
// The canonical identity runner resolves a session before it can return this receipt.
|
|
124
|
+
const session = holder.session;
|
|
125
|
+
const restBase = deriveSupabaseRestBaseUrl(runtime.apiBaseUrl);
|
|
126
|
+
let totalBytes = 0;
|
|
127
|
+
const observations = [];
|
|
128
|
+
for (let observation = 0; observation < 2; observation += 1) {
|
|
129
|
+
const collected = {};
|
|
130
|
+
const results = [];
|
|
131
|
+
for (const table of TABLES) {
|
|
132
|
+
let pages = 0;
|
|
133
|
+
try {
|
|
134
|
+
const result = await fetchCompletePostgrestPages({
|
|
135
|
+
table,
|
|
136
|
+
requestedPageSize: pageSize,
|
|
137
|
+
rowIdentity: (row) => `${row.id}@${row.version}`,
|
|
138
|
+
fetchPage: async (offset) => {
|
|
139
|
+
if (++pages > MAX_PAGES)
|
|
140
|
+
fail('PAGE_LIMIT', 'Support export exceeded the page limit.');
|
|
141
|
+
if (clock() - started >= MAX_DURATION_MS)
|
|
142
|
+
fail('TIME_LIMIT', 'Support export exceeded the operation deadline.');
|
|
143
|
+
const url = new URL(buildDataApiUrl(restBase, { kind: 'relation', name: table }));
|
|
144
|
+
url.searchParams.set('select', 'id,version,state_code,json');
|
|
145
|
+
url.searchParams.set('state_code', `in.(${states.join(',')})`);
|
|
146
|
+
url.searchParams.set('order', 'id.asc,version.asc');
|
|
147
|
+
url.searchParams.set('limit', String(pageSize));
|
|
148
|
+
url.searchParams.set('offset', String(offset));
|
|
149
|
+
const headers = applyDataApiProfileHeaders({
|
|
150
|
+
...buildSupabaseAuthHeaders(runtime.publishableKey, session.accessToken),
|
|
151
|
+
Prefer: 'count=exact',
|
|
152
|
+
}, resolveDataApiCapabilityFromUrl({ url: url.toString(), method: 'GET' }), 'GET');
|
|
153
|
+
const response = await fetchImpl(url.toString(), {
|
|
154
|
+
method: 'GET',
|
|
155
|
+
headers,
|
|
156
|
+
redirect: 'error',
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok)
|
|
159
|
+
fail('READ_FAILED', 'Support export request was rejected.');
|
|
160
|
+
const range = response.headers.get('content-range');
|
|
161
|
+
if (parseExactContentRange(range).total > MAX_ROWS)
|
|
162
|
+
fail('ROW_LIMIT', 'Support export exceeded the row limit.');
|
|
163
|
+
const text = await readBounded(response);
|
|
164
|
+
totalBytes += Buffer.byteLength(text);
|
|
165
|
+
if (totalBytes > MAX_TOTAL_BYTES)
|
|
166
|
+
fail('BYTE_LIMIT', 'Support export exceeded the total byte limit.');
|
|
167
|
+
let value;
|
|
168
|
+
try {
|
|
169
|
+
value = JSON.parse(text);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
fail('INVALID_JSON', 'Support response is not valid JSON.');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
rows: rowsFrom(value, states),
|
|
176
|
+
source_url: url.toString(),
|
|
177
|
+
content_range: range,
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
collected[table] = result.rows;
|
|
182
|
+
results.push({ table, completeness: result.completeness });
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
if (error instanceof CliError && error.code.startsWith('DATASET_SUPPORT_CACHE_'))
|
|
186
|
+
throw error;
|
|
187
|
+
if (error instanceof CliError && error.code === 'DATASET_MAINTENANCE_SNAPSHOT_INCOMPLETE')
|
|
188
|
+
fail('SNAPSHOT_INCOMPLETE', 'Support pagination completeness could not be proven.');
|
|
189
|
+
fail('READ_FAILED', 'Support export request failed.');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
observations.push({
|
|
193
|
+
rows: collected,
|
|
194
|
+
completeness: buildSnapshotCompleteness({
|
|
195
|
+
tables: TABLES,
|
|
196
|
+
requestedPageSize: pageSize,
|
|
197
|
+
results,
|
|
198
|
+
}),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const first = observations[0];
|
|
202
|
+
const second = observations[1];
|
|
203
|
+
const bytes = {
|
|
204
|
+
flowproperties: rowBytes(first.rows.flowproperties),
|
|
205
|
+
unitgroups: rowBytes(first.rows.unitgroups),
|
|
206
|
+
};
|
|
207
|
+
for (const table of TABLES) {
|
|
208
|
+
if (digest(bytes[table]) !== digest(rowBytes(second.rows[table])))
|
|
209
|
+
fail('CONTENT_DRIFT', 'Support rows changed between observations.');
|
|
210
|
+
}
|
|
211
|
+
const artifacts = {
|
|
212
|
+
flowproperties: path.join(outDir, 'flowproperties.jsonl'),
|
|
213
|
+
unitgroups: path.join(outDir, 'unitgroups.jsonl'),
|
|
214
|
+
identity: path.join(outDir, 'identity-receipt.json'),
|
|
215
|
+
report: path.join(outDir, 'export-report.json'),
|
|
216
|
+
};
|
|
217
|
+
const report = {
|
|
218
|
+
schema_version: 1,
|
|
219
|
+
command: 'dataset support-cache export',
|
|
220
|
+
status: 'completed',
|
|
221
|
+
remote_write_mode: 'read-only',
|
|
222
|
+
captured_at_utc: identity.captured_at_utc,
|
|
223
|
+
project_ref: identity.project.project_ref,
|
|
224
|
+
account: { user_id: identity.identity.user_id, session_source: identity.session.source },
|
|
225
|
+
filters: { state_codes: states, requested_page_size: pageSize },
|
|
226
|
+
snapshot: { status: 'observed-stable', transactional_snapshot: false, observations: 2 },
|
|
227
|
+
completeness: observations.map((item) => item.completeness),
|
|
228
|
+
tables: {
|
|
229
|
+
flowproperties: {
|
|
230
|
+
rows: first.rows.flowproperties.length,
|
|
231
|
+
sha256: digest(bytes.flowproperties),
|
|
232
|
+
},
|
|
233
|
+
unitgroups: { rows: first.rows.unitgroups.length, sha256: digest(bytes.unitgroups) },
|
|
234
|
+
},
|
|
235
|
+
artifacts,
|
|
236
|
+
};
|
|
237
|
+
if (clock() - started >= MAX_DURATION_MS)
|
|
238
|
+
fail('TIME_LIMIT', 'Support export exceeded the operation deadline.');
|
|
239
|
+
try {
|
|
240
|
+
mkdirSync(path.dirname(outDir), { recursive: true, mode: 0o700 });
|
|
241
|
+
mkdirSync(outDir, { mode: 0o700 });
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
// Reservation failure must never clean another writer's output.
|
|
245
|
+
if (error.code === 'EEXIST')
|
|
246
|
+
fail('OUTPUT_EXISTS', 'Support export output already exists.', 2);
|
|
247
|
+
fail('ARTIFACT_WRITE_FAILED', 'Support export could not reserve its output directory.');
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
for (const table of TABLES)
|
|
251
|
+
writeFileSync(artifacts[table], bytes[table], { flag: 'wx', mode: 0o600 });
|
|
252
|
+
writeFileSync(artifacts.identity, JSON.stringify(identity) + '\n', { flag: 'wx', mode: 0o600 });
|
|
253
|
+
writeFileSync(artifacts.report + '.tmp', JSON.stringify(report) + '\n', {
|
|
254
|
+
flag: 'wx',
|
|
255
|
+
mode: 0o600,
|
|
256
|
+
});
|
|
257
|
+
renameSync(artifacts.report + '.tmp', artifacts.report);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
261
|
+
fail('ARTIFACT_WRITE_FAILED', 'Support export could not publish its completion report.');
|
|
262
|
+
}
|
|
263
|
+
return report;
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=dataset-support-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-support-cache.js","sourceRoot":"","sources":["../../../src/lib/dataset-support-cache.ts"],"names":[],"mappings":"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","sourcesContent":["// data-api-relations: flowproperties, unitgroups\nimport { createHash } from 'node:crypto';\nimport { lstatSync, mkdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport {\n runAuthIdentityReceipt,\n type ResolveAuthIdentitySession,\n} from './auth-identity-receipt.js';\nimport { CliError } from './errors.js';\nimport type { FetchLike, ResponseLike } from './http.js';\nimport {\n buildSnapshotCompleteness,\n fetchCompletePostgrestPages,\n parseExactContentRange,\n type DatasetMaintenanceSnapshotCompleteness,\n} from './dataset-maintenance-pagination.js';\nimport {\n buildSupabaseAuthHeaders,\n deriveSupabaseRestBaseUrl,\n requireSupabaseRestRuntime,\n} from './supabase-client.js';\nimport {\n applyDataApiProfileHeaders,\n buildDataApiUrl,\n resolveDataApiCapabilityFromUrl,\n} from './supabase-data-api-contract.js';\nimport {\n resolveSupabaseUserSession,\n type ResolvedSupabaseUserSession,\n} from './supabase-session.js';\n\nconst TABLES = ['flowproperties', 'unitgroups'] as const;\ntype Table = (typeof TABLES)[number];\ntype Row = { id: string; version: string; state_code: number; json: Record<string, unknown> };\nconst MAX_ROWS = 100_000;\nconst MAX_PAGES = 1_000;\nconst MAX_RESPONSE_BYTES = 8 * 1024 * 1024;\nconst MAX_TOTAL_BYTES = 64 * 1024 * 1024;\nconst MAX_DURATION_MS = 120_000;\n\nexport type RunDatasetSupportCacheExportOptions = {\n outDir: string;\n env: NodeJS.ProcessEnv;\n fetchImpl: FetchLike;\n cliVersion: string;\n stateCodes?: number[];\n pageSize?: number;\n timeoutMs?: number;\n expectedProjectRef?: string;\n expectedUserId?: string;\n now?: Date;\n resolveSessionImpl?: ResolveAuthIdentitySession;\n monotonicNow?: () => number;\n};\n\nexport type DatasetSupportCacheExportReport = {\n schema_version: 1;\n command: 'dataset support-cache export';\n status: 'completed';\n remote_write_mode: 'read-only';\n captured_at_utc: string;\n project_ref: string;\n account: { user_id: string; session_source: string };\n filters: { state_codes: number[]; requested_page_size: number };\n snapshot: { status: 'observed-stable'; transactional_snapshot: false; observations: 2 };\n completeness: DatasetMaintenanceSnapshotCompleteness<Table>[];\n tables: Record<Table, { rows: number; sha256: string }>;\n artifacts: Record<Table | 'identity' | 'report', string>;\n};\n\nfunction fail(code: string, message: string, exitCode = 1): never {\n throw new CliError(message, { code: `DATASET_SUPPORT_CACHE_${code}`, exitCode });\n}\n\nfunction integer(value: number, minimum: number, maximum: number, label: string): number {\n if (!Number.isSafeInteger(value) || value < minimum || value > maximum)\n fail('OPTION_INVALID', `${label} is outside the supported integer range.`, 2);\n return value;\n}\n\nfunction object(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction rowsFrom(value: unknown, states: readonly number[]): Row[] {\n if (!Array.isArray(value)) fail('ROWS_INVALID', 'Support response must be an array.');\n return value.map((row: unknown) => {\n if (\n !object(row) ||\n typeof row.id !== 'string' ||\n !row.id.trim() ||\n typeof row.version !== 'string' ||\n !row.version.trim() ||\n !Number.isSafeInteger(row.state_code) ||\n !object(row.json)\n )\n fail('ROWS_INVALID', 'Support response contains an invalid row.');\n if (!states.includes(row.state_code as number))\n fail('STATE_FENCE_VIOLATION', 'Support response escaped the requested state filters.');\n return {\n id: row.id,\n version: row.version,\n state_code: row.state_code as number,\n json: row.json,\n };\n });\n}\n\nasync function readBounded(response: ResponseLike): Promise<string> {\n if (Number(response.headers.get('content-length')) > MAX_RESPONSE_BYTES)\n fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');\n if (!response.body) {\n const text = await response.text();\n if (Buffer.byteLength(text) > MAX_RESPONSE_BYTES)\n fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');\n return text;\n }\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let bytes = 0;\n try {\n for (;;) {\n const chunk = await reader.read();\n if (chunk.done) break;\n bytes += chunk.value.byteLength;\n if (bytes > MAX_RESPONSE_BYTES) {\n await reader.cancel();\n fail('BYTE_LIMIT', 'Support response exceeded the byte limit.');\n }\n chunks.push(chunk.value);\n }\n } finally {\n reader.releaseLock();\n }\n return Buffer.concat(chunks).toString('utf8');\n}\n\nfunction rowBytes(rows: Row[]): string {\n return rows.map((row) => JSON.stringify(row) + '\\n').join('');\n}\n\nfunction digest(bytes: string): string {\n return createHash('sha256').update(bytes).digest('hex');\n}\n\nexport async function runDatasetSupportCacheExport(\n options: RunDatasetSupportCacheExportOptions,\n): Promise<DatasetSupportCacheExportReport> {\n if (!options.outDir.trim()) fail('OUT_DIR_REQUIRED', 'A fresh --out-dir is required.', 2);\n const outDir = path.resolve(options.outDir);\n if (lstatSync(outDir, { throwIfNoEntry: false }))\n fail('OUTPUT_EXISTS', 'Support export output already exists.', 2);\n const pageSize = integer(options.pageSize ?? 1000, 1, 5000, 'Page size');\n const timeoutMs = integer(options.timeoutMs ?? 10000, 1, MAX_DURATION_MS, 'Timeout');\n const states = [...new Set(options.stateCodes ?? [100])].sort((a, b) => a - b);\n if (!states.length) fail('OPTION_INVALID', 'At least one state code is required.', 2);\n states.forEach((state) => integer(state, -2147483648, 2147483647, 'State code'));\n const runtime = requireSupabaseRestRuntime(options.env);\n if (runtime.authMode !== 'oauth')\n fail('OAUTH_REQUIRED', 'Support export requires a CLI OAuth session.', 2);\n const clock = options.monotonicNow ?? (() => performance.now());\n const started = clock();\n const overall = AbortSignal.timeout(MAX_DURATION_MS);\n const fetchImpl: FetchLike = (url, init = {}) =>\n options.fetchImpl(url, {\n ...init,\n signal: AbortSignal.any([overall, init.signal ?? AbortSignal.timeout(timeoutMs)]),\n });\n const resolver = options.resolveSessionImpl ?? resolveSupabaseUserSession;\n const holder: { session?: ResolvedSupabaseUserSession } = {};\n const identity = await runAuthIdentityReceipt({\n env: options.env,\n fetchImpl,\n cliVersion: options.cliVersion,\n expectedProjectRef: options.expectedProjectRef,\n expectedUserId: options.expectedUserId,\n timeoutMs,\n now: options.now,\n resolveSessionImpl: async (request) => {\n holder.session = await resolver(request);\n return holder.session;\n },\n });\n // The canonical identity runner resolves a session before it can return this receipt.\n const session = holder.session!;\n const restBase = deriveSupabaseRestBaseUrl(runtime.apiBaseUrl);\n let totalBytes = 0;\n const observations: Array<{\n rows: Record<Table, Row[]>;\n completeness: DatasetMaintenanceSnapshotCompleteness<Table>;\n }> = [];\n for (let observation = 0; observation < 2; observation += 1) {\n const collected = {} as Record<Table, Row[]>;\n const results = [];\n for (const table of TABLES) {\n let pages = 0;\n try {\n const result = await fetchCompletePostgrestPages({\n table,\n requestedPageSize: pageSize,\n rowIdentity: (row: Row) => `${row.id}@${row.version}`,\n fetchPage: async (offset) => {\n if (++pages > MAX_PAGES) fail('PAGE_LIMIT', 'Support export exceeded the page limit.');\n if (clock() - started >= MAX_DURATION_MS)\n fail('TIME_LIMIT', 'Support export exceeded the operation deadline.');\n const url = new URL(buildDataApiUrl(restBase, { kind: 'relation', name: table }));\n url.searchParams.set('select', 'id,version,state_code,json');\n url.searchParams.set('state_code', `in.(${states.join(',')})`);\n url.searchParams.set('order', 'id.asc,version.asc');\n url.searchParams.set('limit', String(pageSize));\n url.searchParams.set('offset', String(offset));\n const headers = applyDataApiProfileHeaders(\n {\n ...buildSupabaseAuthHeaders(runtime.publishableKey, session.accessToken),\n Prefer: 'count=exact',\n },\n resolveDataApiCapabilityFromUrl({ url: url.toString(), method: 'GET' }),\n 'GET',\n );\n const response = await fetchImpl(url.toString(), {\n method: 'GET',\n headers,\n redirect: 'error',\n });\n if (!response.ok) fail('READ_FAILED', 'Support export request was rejected.');\n const range = response.headers.get('content-range');\n if (parseExactContentRange(range).total > MAX_ROWS)\n fail('ROW_LIMIT', 'Support export exceeded the row limit.');\n const text = await readBounded(response);\n totalBytes += Buffer.byteLength(text);\n if (totalBytes > MAX_TOTAL_BYTES)\n fail('BYTE_LIMIT', 'Support export exceeded the total byte limit.');\n let value: unknown;\n try {\n value = JSON.parse(text);\n } catch {\n fail('INVALID_JSON', 'Support response is not valid JSON.');\n }\n return {\n rows: rowsFrom(value, states),\n source_url: url.toString(),\n content_range: range,\n };\n },\n });\n collected[table] = result.rows;\n results.push({ table, completeness: result.completeness });\n } catch (error) {\n if (error instanceof CliError && error.code.startsWith('DATASET_SUPPORT_CACHE_'))\n throw error;\n if (error instanceof CliError && error.code === 'DATASET_MAINTENANCE_SNAPSHOT_INCOMPLETE')\n fail('SNAPSHOT_INCOMPLETE', 'Support pagination completeness could not be proven.');\n fail('READ_FAILED', 'Support export request failed.');\n }\n }\n observations.push({\n rows: collected,\n completeness: buildSnapshotCompleteness({\n tables: TABLES,\n requestedPageSize: pageSize,\n results,\n }),\n });\n }\n const first = observations[0]!;\n const second = observations[1]!;\n const bytes = {\n flowproperties: rowBytes(first.rows.flowproperties),\n unitgroups: rowBytes(first.rows.unitgroups),\n };\n for (const table of TABLES) {\n if (digest(bytes[table]) !== digest(rowBytes(second.rows[table])))\n fail('CONTENT_DRIFT', 'Support rows changed between observations.');\n }\n const artifacts = {\n flowproperties: path.join(outDir, 'flowproperties.jsonl'),\n unitgroups: path.join(outDir, 'unitgroups.jsonl'),\n identity: path.join(outDir, 'identity-receipt.json'),\n report: path.join(outDir, 'export-report.json'),\n };\n const report: DatasetSupportCacheExportReport = {\n schema_version: 1,\n command: 'dataset support-cache export',\n status: 'completed',\n remote_write_mode: 'read-only',\n captured_at_utc: identity.captured_at_utc,\n project_ref: identity.project.project_ref,\n account: { user_id: identity.identity.user_id, session_source: identity.session.source },\n filters: { state_codes: states, requested_page_size: pageSize },\n snapshot: { status: 'observed-stable', transactional_snapshot: false, observations: 2 },\n completeness: observations.map((item) => item.completeness),\n tables: {\n flowproperties: {\n rows: first.rows.flowproperties.length,\n sha256: digest(bytes.flowproperties),\n },\n unitgroups: { rows: first.rows.unitgroups.length, sha256: digest(bytes.unitgroups) },\n },\n artifacts,\n };\n if (clock() - started >= MAX_DURATION_MS)\n fail('TIME_LIMIT', 'Support export exceeded the operation deadline.');\n try {\n mkdirSync(path.dirname(outDir), { recursive: true, mode: 0o700 });\n mkdirSync(outDir, { mode: 0o700 });\n } catch (error) {\n // Reservation failure must never clean another writer's output.\n if ((error as NodeJS.ErrnoException).code === 'EEXIST')\n fail('OUTPUT_EXISTS', 'Support export output already exists.', 2);\n fail('ARTIFACT_WRITE_FAILED', 'Support export could not reserve its output directory.');\n }\n try {\n for (const table of TABLES)\n writeFileSync(artifacts[table], bytes[table], { flag: 'wx', mode: 0o600 });\n writeFileSync(artifacts.identity, JSON.stringify(identity) + '\\n', { flag: 'wx', mode: 0o600 });\n writeFileSync(artifacts.report + '.tmp', JSON.stringify(report) + '\\n', {\n flag: 'wx',\n mode: 0o600,\n });\n renameSync(artifacts.report + '.tmp', artifacts.report);\n } catch {\n rmSync(outDir, { recursive: true, force: true });\n fail('ARTIFACT_WRITE_FAILED', 'Support export could not publish its completion report.');\n }\n return report;\n}\n"]}
|
package/dist/src/lib/env.d.ts
CHANGED
|
@@ -22,6 +22,15 @@ export declare const ENV_KEYS: {
|
|
|
22
22
|
readonly disableSessionCache: 'TIANGONG_LCA_DISABLE_SESSION_CACHE';
|
|
23
23
|
readonly forceReauth: 'TIANGONG_LCA_FORCE_REAUTH';
|
|
24
24
|
};
|
|
25
|
+
export declare const OFFICIAL_PRODUCTION_PROFILE: Readonly<{
|
|
26
|
+
apiBaseUrl: "https://qgzvkongdjqiiamzbbts.supabase.co/functions/v1";
|
|
27
|
+
supabasePublishableKey: "sb_publishable_EFWH4E61tpAtf82WQ37xTA_Fxa5OPyg";
|
|
28
|
+
oauthClientId: "1837c6d3-3c9d-48e0-bbf7-b532b78f9f76";
|
|
29
|
+
oauthRedirectUri: "http://127.0.0.1:49191/oauth/callback";
|
|
30
|
+
region: "us-east-1";
|
|
31
|
+
}>;
|
|
32
|
+
export declare function hasProductionProfileMismatch(env: NodeJS.ProcessEnv): boolean;
|
|
33
|
+
export declare function resolveProductionRuntimeEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
25
34
|
export declare const ENV_SPECS: EnvSpec[];
|
|
26
35
|
export type DoctorCheck = {
|
|
27
36
|
key: string;
|
package/dist/src/lib/env.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEFAULT_OAUTH_REDIRECT_URI } from './oauth-loopback.js';
|
|
1
2
|
export const ENV_KEYS = {
|
|
2
3
|
apiBaseUrl: 'TIANGONG_LCA_API_BASE_URL',
|
|
3
4
|
authMode: 'TIANGONG_LCA_AUTH_MODE',
|
|
@@ -10,6 +11,69 @@ export const ENV_KEYS = {
|
|
|
10
11
|
disableSessionCache: 'TIANGONG_LCA_DISABLE_SESSION_CACHE',
|
|
11
12
|
forceReauth: 'TIANGONG_LCA_FORCE_REAUTH',
|
|
12
13
|
};
|
|
14
|
+
const PRODUCTION_PROJECT_URL = 'https://qgzvkongdjqiiamzbbts.supabase.co';
|
|
15
|
+
// Public application configuration, not user credentials. Keep this as the one
|
|
16
|
+
// source used by auth, remote adapters, and doctor; Skills must not copy it.
|
|
17
|
+
export const OFFICIAL_PRODUCTION_PROFILE = Object.freeze({
|
|
18
|
+
apiBaseUrl: `${PRODUCTION_PROJECT_URL}/functions/v1`,
|
|
19
|
+
supabasePublishableKey: 'sb_publishable_EFWH4E61tpAtf82WQ37xTA_Fxa5OPyg',
|
|
20
|
+
oauthClientId: '1837c6d3-3c9d-48e0-bbf7-b532b78f9f76',
|
|
21
|
+
oauthRedirectUri: DEFAULT_OAUTH_REDIRECT_URI,
|
|
22
|
+
region: 'us-east-1',
|
|
23
|
+
});
|
|
24
|
+
const PRODUCTION_ENV_DEFAULTS = Object.freeze({
|
|
25
|
+
[ENV_KEYS.apiBaseUrl]: OFFICIAL_PRODUCTION_PROFILE.apiBaseUrl,
|
|
26
|
+
[ENV_KEYS.supabasePublishableKey]: OFFICIAL_PRODUCTION_PROFILE.supabasePublishableKey,
|
|
27
|
+
[ENV_KEYS.oauthClientId]: OFFICIAL_PRODUCTION_PROFILE.oauthClientId,
|
|
28
|
+
[ENV_KEYS.oauthRedirectUri]: OFFICIAL_PRODUCTION_PROFILE.oauthRedirectUri,
|
|
29
|
+
});
|
|
30
|
+
function runtimeToken(value) {
|
|
31
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
32
|
+
}
|
|
33
|
+
function isProductionBase(value) {
|
|
34
|
+
return [
|
|
35
|
+
PRODUCTION_PROJECT_URL,
|
|
36
|
+
`${PRODUCTION_PROJECT_URL}/functions/v1`,
|
|
37
|
+
`${PRODUCTION_PROJECT_URL}/rest/v1`,
|
|
38
|
+
].includes(value.replace(/\/+$/u, ''));
|
|
39
|
+
}
|
|
40
|
+
export function hasProductionProfileMismatch(env) {
|
|
41
|
+
const base = runtimeToken(env[ENV_KEYS.apiBaseUrl]);
|
|
42
|
+
return Boolean(base &&
|
|
43
|
+
!isProductionBase(base) &&
|
|
44
|
+
(runtimeToken(env[ENV_KEYS.oauthClientId]).toLowerCase() ===
|
|
45
|
+
OFFICIAL_PRODUCTION_PROFILE.oauthClientId ||
|
|
46
|
+
runtimeToken(env[ENV_KEYS.supabasePublishableKey]) ===
|
|
47
|
+
OFFICIAL_PRODUCTION_PROFILE.supabasePublishableKey));
|
|
48
|
+
}
|
|
49
|
+
function productionDefaults(env) {
|
|
50
|
+
const mode = runtimeToken(env[ENV_KEYS.authMode]).toLowerCase();
|
|
51
|
+
// A caller-supplied bearer must always have an explicit destination and key.
|
|
52
|
+
if ((mode && mode !== 'oauth') || runtimeToken(env[ENV_KEYS.accessToken]))
|
|
53
|
+
return {};
|
|
54
|
+
const base = runtimeToken(env[ENV_KEYS.apiBaseUrl]);
|
|
55
|
+
if (base && !isProductionBase(base))
|
|
56
|
+
return {};
|
|
57
|
+
for (const key of [
|
|
58
|
+
ENV_KEYS.supabasePublishableKey,
|
|
59
|
+
ENV_KEYS.oauthClientId,
|
|
60
|
+
ENV_KEYS.oauthRedirectUri,
|
|
61
|
+
]) {
|
|
62
|
+
const configured = runtimeToken(env[key]);
|
|
63
|
+
const value = key === ENV_KEYS.oauthClientId ? configured.toLowerCase() : configured;
|
|
64
|
+
if (value && value !== PRODUCTION_ENV_DEFAULTS[key])
|
|
65
|
+
return {};
|
|
66
|
+
}
|
|
67
|
+
return PRODUCTION_ENV_DEFAULTS;
|
|
68
|
+
}
|
|
69
|
+
export function resolveProductionRuntimeEnv(env) {
|
|
70
|
+
const resolved = { ...env };
|
|
71
|
+
for (const [key, value] of Object.entries(productionDefaults(env))) {
|
|
72
|
+
if (!runtimeToken(env[key]))
|
|
73
|
+
resolved[key] = value;
|
|
74
|
+
}
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
13
77
|
export const ENV_SPECS = [
|
|
14
78
|
{
|
|
15
79
|
key: ENV_KEYS.apiBaseUrl,
|
|
@@ -20,7 +84,7 @@ export const ENV_SPECS = [
|
|
|
20
84
|
key: ENV_KEYS.region,
|
|
21
85
|
required: false,
|
|
22
86
|
description: 'Target TianGong LCA API region',
|
|
23
|
-
defaultValue:
|
|
87
|
+
defaultValue: OFFICIAL_PRODUCTION_PROFILE.region,
|
|
24
88
|
},
|
|
25
89
|
{
|
|
26
90
|
key: ENV_KEYS.supabasePublishableKey,
|
|
@@ -81,7 +145,7 @@ function parseBooleanEnv(value) {
|
|
|
81
145
|
}
|
|
82
146
|
export function resolveEnv(spec, env) {
|
|
83
147
|
const envValue = env[spec.key];
|
|
84
|
-
if (envValue) {
|
|
148
|
+
if (envValue && (!Object.hasOwn(PRODUCTION_ENV_DEFAULTS, spec.key) || runtimeToken(envValue))) {
|
|
85
149
|
return {
|
|
86
150
|
key: spec.key,
|
|
87
151
|
source: 'env',
|
|
@@ -89,11 +153,13 @@ export function resolveEnv(spec, env) {
|
|
|
89
153
|
present: true,
|
|
90
154
|
};
|
|
91
155
|
}
|
|
92
|
-
|
|
156
|
+
const defaults = productionDefaults(env);
|
|
157
|
+
const defaultValue = spec.defaultValue ?? (Object.hasOwn(defaults, spec.key) ? defaults[spec.key] : undefined);
|
|
158
|
+
if (defaultValue !== undefined) {
|
|
93
159
|
return {
|
|
94
160
|
key: spec.key,
|
|
95
161
|
source: 'default',
|
|
96
|
-
value:
|
|
162
|
+
value: defaultValue,
|
|
97
163
|
present: true,
|
|
98
164
|
};
|
|
99
165
|
}
|
|
@@ -150,7 +216,8 @@ export function buildDoctorReport(env, dotEnvStatus) {
|
|
|
150
216
|
};
|
|
151
217
|
});
|
|
152
218
|
return {
|
|
153
|
-
ok:
|
|
219
|
+
ok: !hasProductionProfileMismatch(env) &&
|
|
220
|
+
checks.every((check) => !check.required || check.present) &&
|
|
154
221
|
[ENV_KEYS.oauthClientId, ENV_KEYS.accessToken].some((key) => checks.find((check) => check.key === key)?.present),
|
|
155
222
|
loadedDotEnv: dotEnvStatus.loaded,
|
|
156
223
|
dotEnvPath: dotEnvStatus.path,
|