@codemation/host 0.8.0 → 0.9.1
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 +59 -0
- package/dist/{ApiPaths-Dv1dcHu_.js → ApiPaths-DCvrlIjg.js} +12 -1
- package/dist/{ApiPaths-Dv1dcHu_.js.map → ApiPaths-DCvrlIjg.js.map} +1 -1
- package/dist/{AppConfigFactory-Cx4qQvRk.js → AppConfigFactory-D4LL1aOR.js} +77 -297
- package/dist/AppConfigFactory-D4LL1aOR.js.map +1 -0
- package/dist/{AppConfigFactory-BT0y0LVC.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
- package/dist/{AppContainerFactory-DRTjG7nG.js → AppContainerFactory-CHCXP2rn.js} +1735 -474
- package/dist/AppContainerFactory-CHCXP2rn.js.map +1 -0
- package/dist/{CodemationAppContext-CGFYVcSb.d.ts → CodemationAppContext-K51b7oXe.d.ts} +3 -3
- package/dist/{CodemationAuthoring.types-DiKKogum.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +4 -4
- package/dist/{CodemationConfigNormalizer-48f-T66P.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-_PIYqwVx.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +2 -2
- package/dist/{CodemationPluginListMerger-DP7djJ9S.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +24 -12
- package/dist/{persistenceServer-C-hH4z6l.js → CodemationPostgresPrismaClientFactory-C7156Fe-.js} +2 -2
- package/dist/CodemationPostgresPrismaClientFactory-C7156Fe-.js.map +1 -0
- package/dist/CodemationPostgresPrismaClientFactory-CTNTPnDr.d.ts +9 -0
- package/dist/{CredentialContractsRegistry-Bq2bq28t.d.ts → CredentialContractsRegistry-Dgu-rEXi.d.ts} +16 -3
- package/dist/{CredentialServices-BLloBztI.d.ts → CredentialServices-B3wPyp2y.d.ts} +4 -4
- package/dist/{CredentialServices-Dk8yypeL.js → CredentialServices-Bios0dM8.js} +10 -4
- package/dist/CredentialServices-Bios0dM8.js.map +1 -0
- package/dist/{InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts → InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts} +1 -1
- package/dist/{InternalPingRegistrar-DY3kSfxP.js → InternalPingRegistrar-BavAAnvk.js} +19 -16
- package/dist/InternalPingRegistrar-BavAAnvk.js.map +1 -0
- package/dist/{ItemsInputNormalizer-_RwIfRIQ.d.ts → ItemsInputNormalizer-CFkfNMLt.d.ts} +1434 -1225
- package/dist/PrismaMigrationDeployer-DdEcXXVi.d.ts +14 -0
- package/dist/{PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts → PublicFrontendBootstrapFactory-ClEjZP74.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts → PublicFrontendBootstrapJsonCodec-HNItQ7ol.d.ts} +6 -1
- package/dist/{TelemetryContracts-BtDx84Cp.d.ts → TelemetryContracts-DpZEODQM.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts → WorkflowPolicyUiPresentationFactory-BNn2fvR_.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js.map} +1 -1
- package/dist/authoring.d.ts +4 -4
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/consumer.d.ts +5 -5
- package/dist/credentials.d.ts +5 -5
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/dto.d.ts +5 -5
- package/dist/{index-DilAYwnH.d.ts → index-ChIfeWzk.d.ts} +71 -28
- package/dist/index.d.ts +17 -16
- package/dist/index.js +8 -8
- package/dist/infrastructure/persistence/PrismaMigrationOperations.d.ts +44 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js +302 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +15 -13
- package/dist/nextServer.js +6 -6
- package/dist/pairing.d.ts +28 -9
- package/dist/pairing.js +19 -3
- package/dist/pairing.js.map +1 -0
- package/dist/{pairing.types-snfZ_OzB.d.ts → pairing.types-D9Bjn98U.d.ts} +1 -1
- package/dist/persistenceServer.d.ts +31 -7
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-09PKasWR.d.ts → server-B5trn7y4.d.ts} +5 -5
- package/dist/{server-vtRCPgRJ.js → server-CNj_y0QO.js} +4 -4
- package/dist/{server-vtRCPgRJ.js.map → server-CNj_y0QO.js.map} +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +8 -8
- package/package.json +11 -10
- package/playwright.config.ts +8 -2
- package/playwright.scaffolded-dev.config.ts +8 -2
- package/prisma/migrations/20260526120000_credential_material_pointer/migration.sql +18 -0
- package/prisma/migrations/20260527120000_add_human_task/migration.sql +32 -0
- package/prisma/migrations/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations/20260527130000_add_hmac_nonce/migration.sql +12 -0
- package/prisma/migrations.sqlite/20260526120000_credential_material_pointer/migration.sql +13 -0
- package/prisma/migrations.sqlite/20260527120000_add_human_task/migration.sql +30 -0
- package/prisma/migrations.sqlite/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations.sqlite/20260527130000_add_hmac_nonce/migration.sql +9 -0
- package/prisma/schema.postgresql.prisma +48 -0
- package/prisma/schema.sqlite.prisma +48 -0
- package/prisma-generated/prisma-postgresql-client/edge.js +40 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +36 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +3179 -163
- package/prisma-generated/prisma-postgresql-client/index.js +40 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +48 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +40 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +36 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +3175 -163
- package/prisma-generated/prisma-sqlite-client/index.js +40 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +48 -0
- package/src/application/contracts/CredentialContractsRegistry.ts +15 -0
- package/src/application/credentials/AppGalleryProjector.ts +69 -0
- package/src/application/hitl/DecideHumanTaskCommandHandler.ts +149 -0
- package/src/application/hitl/DecisionSchemaValidator.ts +22 -0
- package/src/application/hitl/HitlCallbackHandler.ts +96 -0
- package/src/application/mapping/WorkflowDefinitionMapper.ts +1 -3
- package/src/application/queries/CredentialQueryHandlers.ts +2 -0
- package/src/application/queries/GetCredentialAppsQuery.ts +4 -0
- package/src/application/queries/GetCredentialAppsQueryHandler.ts +27 -0
- package/src/application/telemetry/ResumeTelemetryContextForRun.ts +53 -0
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +9 -8
- package/src/applicationTokens.ts +11 -1
- package/src/auth/managed/ManagedCorsMiddleware.ts +20 -5
- package/src/bootstrap/AppContainerFactory.ts +100 -0
- package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
- package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +4 -24
- package/src/credentials/ControlPlaneCredentialMaterialProvider.ts +79 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +2 -7
- package/src/credentials/InternalCredentialsBindingRegistrar.ts +83 -0
- package/src/credentials/LocalCredentialMaterialProvider.ts +92 -0
- package/src/domain/credentials/CredentialInstanceService.ts +5 -1
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +18 -4
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +7 -4
- package/src/dto.ts +2 -0
- package/src/hitl/ControlPlaneInboxChannel.ts +102 -0
- package/src/hitl/HitlResumeTokenSigner.ts +80 -0
- package/src/hitl/HitlTimeoutJobScheduler.ts +89 -0
- package/src/hitl/HitlTimeoutWorker.ts +143 -0
- package/src/hitl/InboxChannelResolver.ts +49 -0
- package/src/hitl/LocalInboxChannel.ts +37 -0
- package/src/infrastructure/persistence/PrismaCredentialStore.ts +10 -0
- package/src/infrastructure/persistence/PrismaHmacNonceStore.ts +29 -0
- package/src/infrastructure/persistence/PrismaHumanTaskStore.ts +156 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +53 -383
- package/src/infrastructure/persistence/PrismaMigrationOperations.ts +401 -0
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +39 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +5 -1
- package/src/pairing/HmacNonceStore.ts +14 -0
- package/src/pairing/HmacNonceStoreToken.ts +4 -0
- package/src/pairing/HmacRequestSigner.ts +10 -1
- package/src/pairing/InMemoryHmacNonceStore.ts +24 -0
- package/src/pairing/IncomingHmacVerifier.ts +28 -12
- package/src/pairing/InternalHmacAuthMiddleware.ts +1 -1
- package/src/pairing/index.ts +3 -0
- package/src/presentation/http/ApiPaths.ts +14 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +4 -0
- package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +1 -0
- package/src/presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar.ts +54 -0
- package/src/presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar.ts +33 -0
- package/src/presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar.ts +43 -0
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +9 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +1 -1
- package/src/server.ts +7 -2
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +9 -0
- package/tsconfig.json +1 -0
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +0 -1
- package/dist/AppContainerFactory-DRTjG7nG.js.map +0 -1
- package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
- package/dist/persistenceServer-B71RGvSj.d.ts +0 -30
- package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
- package/src/credentials/catalogTypes.ts +0 -4
|
@@ -1,400 +1,70 @@
|
|
|
1
|
-
import type { Client } from "@libsql/client";
|
|
2
1
|
import { injectable } from "@codemation/core";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { existsSync } from "node:fs";
|
|
5
|
-
import { mkdir } from "node:fs/promises";
|
|
6
|
-
import { createRequire } from "node:module";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { fileURLToPath } from "node:url";
|
|
9
2
|
import type { AppPersistenceConfig } from "../../presentation/config/AppConfig";
|
|
10
3
|
|
|
11
4
|
/**
|
|
12
5
|
* Runs `prisma migrate deploy` against TCP PostgreSQL or a SQLite database file.
|
|
6
|
+
*
|
|
7
|
+
* This class is deliberately kept as a thin wrapper with no direct imports from
|
|
8
|
+
* node:fs, node:path, node:module, or node:url. All heavy filesystem and dynamic-
|
|
9
|
+
* require operations live in PrismaMigrationOperations, which is loaded lazily via
|
|
10
|
+
* a runtime-computed `import(...)` path. The Turbopack / Next.js NFT module tracer
|
|
11
|
+
* only follows statically-analysable import paths, so computing the specifier at
|
|
12
|
+
* runtime prevents it from walking the whole project when tracing this file.
|
|
13
13
|
*/
|
|
14
|
+
|
|
15
|
+
// The import specifier is intentionally split so static analysers (NFT / Turbopack)
|
|
16
|
+
// cannot resolve it at build time and trace the heavy fs/createRequire ops inside.
|
|
17
|
+
// We disambiguate source vs dist at runtime via `import.meta.url`, then resolve to
|
|
18
|
+
// an absolute `file://` URL. The absolute URL bypasses tsx-loader's namespace
|
|
19
|
+
// decoration, which otherwise propagates a `?tsx-namespace=...` query to every
|
|
20
|
+
// transitive `node:*` builtin import in PrismaMigrationOperations and trips
|
|
21
|
+
// `ENOENT: open 'node:path?tsx-namespace=...'` under the packaged smoke harness.
|
|
22
|
+
// - source (Vitest, `pnpm dev`): the wrapper and operations are siblings under
|
|
23
|
+
// `src/infrastructure/persistence/`, so the relative path is `./PrismaMigrationOperations.js`.
|
|
24
|
+
// - dist (published tgz): tsdown inlines the wrapper into top-level chunks, while
|
|
25
|
+
// the operations module is emitted as its own entry at
|
|
26
|
+
// `dist/infrastructure/persistence/PrismaMigrationOperations.js`.
|
|
27
|
+
const implSpecifier = /* @__PURE__ */ (() => {
|
|
28
|
+
const suffix = "Operations.js";
|
|
29
|
+
const relativePath = import.meta.url.includes("/src/")
|
|
30
|
+
? "./PrismaMigration" + suffix
|
|
31
|
+
: "./infrastructure/persistence/PrismaMigration" + suffix;
|
|
32
|
+
// Absolute `file://` URL: keeps tsx-loader from propagating its
|
|
33
|
+
// `?tsx-namespace=...` query to transitive node:* imports inside the
|
|
34
|
+
// operations module, and keeps the Turbopack NFT tracer from following the
|
|
35
|
+
// path statically (which would walk the heavy fs/createRequire ops).
|
|
36
|
+
return new URL(relativePath, import.meta.url).href;
|
|
37
|
+
})();
|
|
38
|
+
|
|
14
39
|
@injectable()
|
|
15
40
|
export class PrismaMigrationDeployer {
|
|
16
|
-
private static readonly normalizedRuntimeMigrationName = "20260407140000_run_normalized_persistence";
|
|
17
|
-
private readonly require = createRequire(import.meta.url);
|
|
18
|
-
|
|
19
41
|
async deployPersistence(persistence: AppPersistenceConfig, env?: Readonly<NodeJS.ProcessEnv>): Promise<void> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
await this.deploySqlite({ databaseFilePath: persistence.databaseFilePath, env });
|
|
42
|
+
const { PrismaMigrationOperations } = await import(
|
|
43
|
+
/* webpackIgnore: true */
|
|
44
|
+
/* @vite-ignore */
|
|
45
|
+
implSpecifier
|
|
46
|
+
);
|
|
47
|
+
// eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
|
|
48
|
+
return new PrismaMigrationOperations().deployPersistence(persistence, env);
|
|
28
49
|
}
|
|
29
50
|
|
|
30
51
|
async deploy(args: Readonly<{ databaseUrl: string; env?: Readonly<NodeJS.ProcessEnv> }>): Promise<void> {
|
|
31
|
-
await
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
args: Readonly<{ databaseFilePath: string; env?: Readonly<NodeJS.ProcessEnv> }>,
|
|
36
|
-
): Promise<void> {
|
|
37
|
-
await this.ensureSqliteParentDirectoryExists(args.databaseFilePath);
|
|
38
|
-
const databaseUrl = this.sqliteFilePathToDatabaseUrl(args.databaseFilePath);
|
|
39
|
-
try {
|
|
40
|
-
await this.deployWithProvider({
|
|
41
|
-
provider: "sqlite",
|
|
42
|
-
databaseUrl,
|
|
43
|
-
env: args.env,
|
|
44
|
-
});
|
|
45
|
-
} catch (error) {
|
|
46
|
-
const recovered = await this.tryRecoverPartiallyAppliedNormalizedRuntimeMigration({
|
|
47
|
-
databaseFilePath: args.databaseFilePath,
|
|
48
|
-
databaseUrl,
|
|
49
|
-
env: args.env,
|
|
50
|
-
error,
|
|
51
|
-
});
|
|
52
|
-
if (!recovered) {
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
await this.cleanupNormalizedRuntimeLegacyArtifacts(args.databaseFilePath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private async deployPostgres(
|
|
60
|
-
args: Readonly<{ databaseUrl: string; env?: Readonly<NodeJS.ProcessEnv> }>,
|
|
61
|
-
): Promise<void> {
|
|
62
|
-
await this.deployWithProvider({
|
|
63
|
-
provider: "postgresql",
|
|
64
|
-
databaseUrl: args.databaseUrl,
|
|
65
|
-
env: args.env,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
private async deployWithProvider(
|
|
70
|
-
args: Readonly<{
|
|
71
|
-
provider: "postgresql" | "sqlite";
|
|
72
|
-
databaseUrl: string;
|
|
73
|
-
env?: Readonly<NodeJS.ProcessEnv>;
|
|
74
|
-
}>,
|
|
75
|
-
): Promise<void> {
|
|
76
|
-
await this.runPrismaCommand({
|
|
77
|
-
prismaArgs: ["migrate", "deploy"],
|
|
78
|
-
provider: args.provider,
|
|
79
|
-
databaseUrl: args.databaseUrl,
|
|
80
|
-
env: args.env,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
private async resolveAppliedMigration(
|
|
85
|
-
args: Readonly<{
|
|
86
|
-
provider: "postgresql" | "sqlite";
|
|
87
|
-
databaseUrl: string;
|
|
88
|
-
migrationName: string;
|
|
89
|
-
env?: Readonly<NodeJS.ProcessEnv>;
|
|
90
|
-
}>,
|
|
91
|
-
): Promise<void> {
|
|
92
|
-
await this.runPrismaCommand({
|
|
93
|
-
prismaArgs: ["migrate", "resolve", "--applied", args.migrationName],
|
|
94
|
-
provider: args.provider,
|
|
95
|
-
databaseUrl: args.databaseUrl,
|
|
96
|
-
env: args.env,
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
private async runPrismaCommand(
|
|
101
|
-
args: Readonly<{
|
|
102
|
-
prismaArgs: string[];
|
|
103
|
-
provider: "postgresql" | "sqlite";
|
|
104
|
-
databaseUrl: string;
|
|
105
|
-
env?: Readonly<NodeJS.ProcessEnv>;
|
|
106
|
-
}>,
|
|
107
|
-
): Promise<void> {
|
|
108
|
-
const resolverEnv = { ...process.env, ...(args.env ?? {}) };
|
|
109
|
-
const prismaConfigPath = this.resolveAbsolutePrismaConfigPath(resolverEnv);
|
|
110
|
-
await new Promise<void>((resolve, reject) => {
|
|
111
|
-
const command = spawn(
|
|
112
|
-
process.execPath,
|
|
113
|
-
[...[this.resolvePrismaCliPath(resolverEnv), ...args.prismaArgs], "--config", path.basename(prismaConfigPath)],
|
|
114
|
-
{
|
|
115
|
-
cwd: path.dirname(prismaConfigPath),
|
|
116
|
-
env: this.createProcessEnvironment(args.databaseUrl, args.provider, args.env),
|
|
117
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
118
|
-
},
|
|
119
|
-
);
|
|
120
|
-
let stdout = "";
|
|
121
|
-
let stderr = "";
|
|
122
|
-
command.stdout.on("data", (chunk: Buffer | string) => {
|
|
123
|
-
stdout += chunk.toString();
|
|
124
|
-
});
|
|
125
|
-
command.stderr.on("data", (chunk: Buffer | string) => {
|
|
126
|
-
stderr += chunk.toString();
|
|
127
|
-
});
|
|
128
|
-
command.once("error", (error) => {
|
|
129
|
-
reject(error);
|
|
130
|
-
});
|
|
131
|
-
command.once("close", (exitCode) => {
|
|
132
|
-
if (exitCode === 0) {
|
|
133
|
-
resolve();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
reject(this.createDeployError(exitCode, stdout, stderr));
|
|
137
|
-
});
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private async tryRecoverPartiallyAppliedNormalizedRuntimeMigration(
|
|
142
|
-
args: Readonly<{
|
|
143
|
-
databaseFilePath: string;
|
|
144
|
-
databaseUrl: string;
|
|
145
|
-
env?: Readonly<NodeJS.ProcessEnv>;
|
|
146
|
-
error: unknown;
|
|
147
|
-
}>,
|
|
148
|
-
): Promise<boolean> {
|
|
149
|
-
if (!this.isRecoverableNormalizedRuntimeMigrationError(args.error)) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
const repaired = await this.repairPartiallyAppliedNormalizedRuntimeSqliteDatabase(args.databaseFilePath);
|
|
153
|
-
if (!repaired) {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
await this.resolveAppliedMigration({
|
|
157
|
-
provider: "sqlite",
|
|
158
|
-
databaseUrl: args.databaseUrl,
|
|
159
|
-
migrationName: PrismaMigrationDeployer.normalizedRuntimeMigrationName,
|
|
160
|
-
env: args.env,
|
|
161
|
-
});
|
|
162
|
-
await this.deployWithProvider({
|
|
163
|
-
provider: "sqlite",
|
|
164
|
-
databaseUrl: args.databaseUrl,
|
|
165
|
-
env: args.env,
|
|
166
|
-
});
|
|
167
|
-
return true;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private isRecoverableNormalizedRuntimeMigrationError(error: unknown): boolean {
|
|
171
|
-
if (!(error instanceof Error)) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
return (
|
|
175
|
-
error.message.includes("Error: P3009") &&
|
|
176
|
-
error.message.includes(PrismaMigrationDeployer.normalizedRuntimeMigrationName)
|
|
52
|
+
const { PrismaMigrationOperations } = await import(
|
|
53
|
+
/* webpackIgnore: true */
|
|
54
|
+
/* @vite-ignore */
|
|
55
|
+
implSpecifier
|
|
177
56
|
);
|
|
57
|
+
// eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
|
|
58
|
+
return new PrismaMigrationOperations().deploy(args);
|
|
178
59
|
}
|
|
179
60
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
client,
|
|
189
|
-
PrismaMigrationDeployer.normalizedRuntimeMigrationName,
|
|
190
|
-
);
|
|
191
|
-
if (!failedMigration) {
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
const runColumns = await this.readSqliteTableColumns(client, "Run");
|
|
195
|
-
const hasNormalizedRunShape =
|
|
196
|
-
runColumns.has("finished_at") &&
|
|
197
|
-
runColumns.has("revision") &&
|
|
198
|
-
runColumns.has("outputs_by_node_json") &&
|
|
199
|
-
!runColumns.has("state_json");
|
|
200
|
-
if (!hasNormalizedRunShape) {
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
await this.ensureNormalizedRuntimeRepairArtifacts(client);
|
|
204
|
-
return true;
|
|
205
|
-
} finally {
|
|
206
|
-
client.close();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
private async hasActiveFailedMigrationRecord(client: Client, migrationName: string): Promise<boolean> {
|
|
211
|
-
const result = await client.execute({
|
|
212
|
-
sql: [
|
|
213
|
-
'SELECT 1 AS "has_failed"',
|
|
214
|
-
'FROM "_prisma_migrations"',
|
|
215
|
-
'WHERE "migration_name" = ?',
|
|
216
|
-
' AND "finished_at" IS NULL',
|
|
217
|
-
' AND "rolled_back_at" IS NULL',
|
|
218
|
-
"LIMIT 1",
|
|
219
|
-
].join(" "),
|
|
220
|
-
args: [migrationName],
|
|
221
|
-
});
|
|
222
|
-
return result.rows.length > 0;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
private async readSqliteTableColumns(client: Client, tableName: string): Promise<Set<string>> {
|
|
226
|
-
const result = await client.execute(`PRAGMA table_info("${tableName}")`);
|
|
227
|
-
return new Set(result.rows.map((row) => String(row.name)));
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
private async ensureNormalizedRuntimeRepairArtifacts(client: Client): Promise<void> {
|
|
231
|
-
await client.execute(`
|
|
232
|
-
CREATE TABLE IF NOT EXISTS "RunWorkItem" (
|
|
233
|
-
"work_item_id" TEXT NOT NULL PRIMARY KEY,
|
|
234
|
-
"run_id" TEXT NOT NULL,
|
|
235
|
-
"workflow_id" TEXT NOT NULL,
|
|
236
|
-
"status" TEXT NOT NULL,
|
|
237
|
-
"target_node_id" TEXT NOT NULL,
|
|
238
|
-
"batch_id" TEXT NOT NULL,
|
|
239
|
-
"queue_name" TEXT,
|
|
240
|
-
"claim_token" TEXT,
|
|
241
|
-
"claimed_by" TEXT,
|
|
242
|
-
"claimed_at" TEXT,
|
|
243
|
-
"available_at" TEXT NOT NULL,
|
|
244
|
-
"enqueued_at" TEXT NOT NULL,
|
|
245
|
-
"completed_at" TEXT,
|
|
246
|
-
"failed_at" TEXT,
|
|
247
|
-
"source_instance_id" TEXT,
|
|
248
|
-
"parent_instance_id" TEXT,
|
|
249
|
-
"items_in" INTEGER NOT NULL,
|
|
250
|
-
"inputs_by_port_json" TEXT NOT NULL,
|
|
251
|
-
"error_json" TEXT,
|
|
252
|
-
CONSTRAINT "RunWorkItem_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
253
|
-
)
|
|
254
|
-
`);
|
|
255
|
-
await client.execute(`
|
|
256
|
-
CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_status_available_at_idx"
|
|
257
|
-
ON "RunWorkItem"("run_id", "status", "available_at")
|
|
258
|
-
`);
|
|
259
|
-
await client.execute(`
|
|
260
|
-
CREATE INDEX IF NOT EXISTS "RunWorkItem_run_id_target_node_id_batch_id_idx"
|
|
261
|
-
ON "RunWorkItem"("run_id", "target_node_id", "batch_id")
|
|
262
|
-
`);
|
|
263
|
-
await client.execute(`
|
|
264
|
-
CREATE TABLE IF NOT EXISTS "RunSlotProjection" (
|
|
265
|
-
"run_id" TEXT NOT NULL PRIMARY KEY,
|
|
266
|
-
"workflow_id" TEXT NOT NULL,
|
|
267
|
-
"revision" INTEGER NOT NULL,
|
|
268
|
-
"updated_at" TEXT NOT NULL,
|
|
269
|
-
"slot_states_json" TEXT NOT NULL,
|
|
270
|
-
CONSTRAINT "RunSlotProjection_run_id_fkey" FOREIGN KEY ("run_id") REFERENCES "Run"("run_id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
271
|
-
)
|
|
272
|
-
`);
|
|
273
|
-
await client.execute(`
|
|
274
|
-
CREATE INDEX IF NOT EXISTS "RunSlotProjection_workflow_id_updated_at_idx"
|
|
275
|
-
ON "RunSlotProjection"("workflow_id", "updated_at")
|
|
276
|
-
`);
|
|
277
|
-
await client.execute(`
|
|
278
|
-
INSERT OR IGNORE INTO "RunSlotProjection" (
|
|
279
|
-
"run_id",
|
|
280
|
-
"workflow_id",
|
|
281
|
-
"revision",
|
|
282
|
-
"updated_at",
|
|
283
|
-
"slot_states_json"
|
|
284
|
-
)
|
|
285
|
-
SELECT
|
|
286
|
-
"run_id",
|
|
287
|
-
"workflow_id",
|
|
288
|
-
"revision",
|
|
289
|
-
"updated_at",
|
|
290
|
-
json_object('slotStatesByNodeId', json('{}'))
|
|
291
|
-
FROM "Run"
|
|
292
|
-
`);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
private async cleanupNormalizedRuntimeLegacyArtifacts(databaseFilePath: string): Promise<void> {
|
|
296
|
-
const { createClient } = await import("@libsql/client");
|
|
297
|
-
const client = createClient({ url: this.sqliteFilePathToDatabaseUrl(databaseFilePath) });
|
|
298
|
-
try {
|
|
299
|
-
const runColumns = await this.readSqliteTableColumns(client, "Run");
|
|
300
|
-
const hasNormalizedRunShape =
|
|
301
|
-
runColumns.has("finished_at") &&
|
|
302
|
-
runColumns.has("revision") &&
|
|
303
|
-
runColumns.has("outputs_by_node_json") &&
|
|
304
|
-
!runColumns.has("state_json");
|
|
305
|
-
if (!hasNormalizedRunShape) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const runSlotProjectionColumns = await this.readSqliteTableColumns(client, "RunSlotProjection");
|
|
309
|
-
await client.execute('DROP TABLE IF EXISTS "Run_legacy"');
|
|
310
|
-
if (runSlotProjectionColumns.size > 0) {
|
|
311
|
-
await client.execute('DROP TABLE IF EXISTS "RunProjection"');
|
|
312
|
-
}
|
|
313
|
-
} finally {
|
|
314
|
-
client.close();
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
private sqliteFilePathToDatabaseUrl(databaseFilePath: string): string {
|
|
319
|
-
return `file:${path.resolve(databaseFilePath)}`;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
private createProcessEnvironment(
|
|
323
|
-
databaseUrl: string,
|
|
324
|
-
provider: "postgresql" | "sqlite",
|
|
325
|
-
env?: Readonly<NodeJS.ProcessEnv>,
|
|
326
|
-
): NodeJS.ProcessEnv {
|
|
327
|
-
return {
|
|
328
|
-
...process.env,
|
|
329
|
-
...(env ?? {}),
|
|
330
|
-
DATABASE_URL: databaseUrl,
|
|
331
|
-
CODEMATION_PRISMA_PROVIDER: provider,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private resolvePrismaCliPath(env: Readonly<NodeJS.ProcessEnv>): string {
|
|
336
|
-
const configuredPath = env.CODEMATION_PRISMA_CLI_PATH;
|
|
337
|
-
if (configuredPath && existsSync(configuredPath)) {
|
|
338
|
-
return configuredPath;
|
|
339
|
-
}
|
|
340
|
-
const packageRoot = this.resolvePackageRoot(env);
|
|
341
|
-
const packageManagerCandidates = [
|
|
342
|
-
path.resolve(process.cwd(), "node_modules", "prisma", "build", "index.js"),
|
|
343
|
-
path.resolve(packageRoot, "node_modules", "prisma", "build", "index.js"),
|
|
344
|
-
];
|
|
345
|
-
for (const candidate of packageManagerCandidates) {
|
|
346
|
-
if (existsSync(candidate)) {
|
|
347
|
-
return candidate;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
try {
|
|
351
|
-
return this.require.resolve("prisma/build/index.js", {
|
|
352
|
-
paths: [process.cwd(), packageRoot],
|
|
353
|
-
});
|
|
354
|
-
} catch {
|
|
355
|
-
throw new Error(
|
|
356
|
-
"Unable to resolve the Prisma CLI required for startup migrations. Ensure `prisma` is installed.",
|
|
357
|
-
);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
private resolveAbsolutePrismaConfigPath(env: Readonly<NodeJS.ProcessEnv>): string {
|
|
362
|
-
const configuredPath = env.CODEMATION_PRISMA_CONFIG_PATH;
|
|
363
|
-
const packageRoot = this.resolvePackageRoot(env);
|
|
364
|
-
if (configuredPath) {
|
|
365
|
-
return path.isAbsolute(configuredPath) ? configuredPath : path.resolve(packageRoot, configuredPath);
|
|
366
|
-
}
|
|
367
|
-
return path.resolve(packageRoot, "prisma.config.ts");
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): string {
|
|
371
|
-
const configuredRoot = env.CODEMATION_HOST_PACKAGE_ROOT;
|
|
372
|
-
if (configuredRoot) {
|
|
373
|
-
return configuredRoot;
|
|
374
|
-
}
|
|
375
|
-
let currentDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
376
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
377
|
-
if (existsSync(path.join(currentDirectory, "prisma", "schema.postgresql.prisma"))) {
|
|
378
|
-
return currentDirectory;
|
|
379
|
-
}
|
|
380
|
-
const parentDirectory = path.dirname(currentDirectory);
|
|
381
|
-
if (parentDirectory === currentDirectory) {
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
currentDirectory = parentDirectory;
|
|
385
|
-
}
|
|
386
|
-
throw new Error(`Could not locate prisma/schema.postgresql.prisma near ${fileURLToPath(import.meta.url)}.`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
private async ensureSqliteParentDirectoryExists(databaseFilePath: string): Promise<void> {
|
|
390
|
-
await mkdir(path.dirname(databaseFilePath), { recursive: true });
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
private createDeployError(exitCode: number | null, stdout: string, stderr: string): Error {
|
|
394
|
-
const output = stderr.trim() || stdout.trim();
|
|
395
|
-
if (!output) {
|
|
396
|
-
return new Error(`Prisma migrate deploy failed during startup with exit code ${exitCode ?? "unknown"}.`);
|
|
397
|
-
}
|
|
398
|
-
return new Error(`Prisma migrate deploy failed during startup with exit code ${exitCode ?? "unknown"}.\n${output}`);
|
|
61
|
+
async resolvePackageRoot(env: Readonly<NodeJS.ProcessEnv> = process.env): Promise<string> {
|
|
62
|
+
const { PrismaMigrationOperations } = await import(
|
|
63
|
+
/* webpackIgnore: true */
|
|
64
|
+
/* @vite-ignore */
|
|
65
|
+
implSpecifier
|
|
66
|
+
);
|
|
67
|
+
// eslint-disable-next-line codemation/no-manual-di-new -- lazy-loaded impl; cannot be registered in DI without breaking the bootstrap chain
|
|
68
|
+
return new PrismaMigrationOperations().resolvePackageRoot(env);
|
|
399
69
|
}
|
|
400
70
|
}
|