houdini 2.0.0-go.2 → 2.0.0-go.20
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/LICENSE +21 -0
- package/build/cmd/generate.d.ts +3 -0
- package/build/cmd/generate.js +31 -3
- package/build/cmd/index.js +7 -1
- package/build/cmd/init.js +29 -14
- package/build/lib/ast.d.ts +1 -1
- package/build/lib/codegen.d.ts +3 -3
- package/build/lib/codegen.js +240 -48
- package/build/lib/config.d.ts +17 -4
- package/build/lib/database.d.ts +3 -2
- package/build/lib/database.js +85 -45
- package/build/lib/error.js +3 -2
- package/build/lib/fs.d.ts +2 -1
- package/build/lib/fs.js +9 -2
- package/build/lib/index.d.ts +1 -0
- package/build/lib/index.js +1 -0
- package/build/lib/logger.d.ts +12 -0
- package/build/lib/logger.js +45 -0
- package/build/lib/parse.js +17 -13
- package/build/lib/plugins.js +26 -3
- package/build/lib/types.d.ts +7 -0
- package/build/lib/types.js +7 -0
- package/build/node/index.d.ts +37 -0
- package/build/node/index.js +270 -0
- package/build/node/package.json +1 -0
- package/build/package.json +75 -56
- package/build/router/conventions.d.ts +6 -4
- package/build/router/conventions.js +4 -0
- package/build/router/match.d.ts +1 -1
- package/build/router/server.d.ts +2 -2
- package/build/router/server.js +3 -1
- package/build/runtime/cache/index.d.ts +10 -10
- package/build/runtime/cache/index.js +14 -14
- package/build/runtime/cache/lists.d.ts +7 -7
- package/build/runtime/cache/lists.js +2 -2
- package/build/runtime/cache/storage.d.ts +1 -1
- package/build/runtime/cache/storage.js +1 -1
- package/build/runtime/cache/subscription.d.ts +2 -2
- package/build/runtime/cache/subscription.js +3 -3
- package/build/runtime/client.d.ts +2 -2
- package/build/runtime/client.js +0 -1
- package/build/runtime/config.d.ts +1 -1
- package/build/runtime/documentStore.d.ts +3 -3
- package/build/runtime/documentStore.js +4 -4
- package/build/runtime/flatten.d.ts +1 -1
- package/build/runtime/index.d.ts +12 -12
- package/build/runtime/index.js +12 -12
- package/build/runtime/pageInfo.d.ts +1 -1
- package/build/runtime/pageInfo.js +1 -1
- package/build/runtime/pagination.d.ts +2 -2
- package/build/runtime/pagination.js +3 -3
- package/build/runtime/scalars.d.ts +1 -1
- package/build/runtime/scalars.js +3 -3
- package/build/runtime/selection.d.ts +1 -1
- package/build/runtime/types.d.ts +12 -5
- package/build/runtime/types.js +1 -1
- package/build/vite/hmr.js +20 -17
- package/build/vite/houdini.js +67 -59
- package/build/vite/index.js +6 -8
- package/build/vite/schema.js +3 -2
- package/package.json +191 -180
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Alec Aivazis
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/build/cmd/generate.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PipelineHook } from '../lib/index.js';
|
|
1
2
|
export declare function generate(args?: {
|
|
2
3
|
pullSchema: boolean;
|
|
3
4
|
persistOutput?: string;
|
|
@@ -7,4 +8,6 @@ export declare function generate(args?: {
|
|
|
7
8
|
verbose: boolean;
|
|
8
9
|
mode?: string;
|
|
9
10
|
preserveDatabase: boolean;
|
|
11
|
+
afterPhase?: PipelineHook;
|
|
12
|
+
beforePhase?: PipelineHook;
|
|
10
13
|
}): Promise<void>;
|
package/build/cmd/generate.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { format_error } from "../lib/error.js";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
codegen_setup,
|
|
6
|
+
init_db,
|
|
7
|
+
run_pipeline,
|
|
8
|
+
PIPELINE_HOOKS
|
|
9
|
+
} from "../lib/index.js";
|
|
5
10
|
import { get_config } from "../lib/project.js";
|
|
6
11
|
import pull_schema from "./pullSchema.js";
|
|
7
12
|
async function generate(args = {
|
|
@@ -30,10 +35,33 @@ async function generate(args = {
|
|
|
30
35
|
};
|
|
31
36
|
process.on("SIGINT", on_close);
|
|
32
37
|
process.on("SIGTERM", on_close);
|
|
33
|
-
|
|
38
|
+
if (args.afterPhase && !PIPELINE_HOOKS.includes(args.afterPhase)) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Invalid --after-phase: ${args.afterPhase}. Valid phases are: ${PIPELINE_HOOKS.join(
|
|
41
|
+
", "
|
|
42
|
+
)}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (args.beforePhase && !PIPELINE_HOOKS.includes(args.beforePhase)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Invalid --before-phase: ${args.beforePhase}. Valid phases are: ${PIPELINE_HOOKS.join(", ")}`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const pipelineOptions = {
|
|
51
|
+
after: "Schema"
|
|
52
|
+
};
|
|
53
|
+
if (args.afterPhase) {
|
|
54
|
+
pipelineOptions.after = args.afterPhase;
|
|
55
|
+
}
|
|
56
|
+
if (args.beforePhase) {
|
|
57
|
+
pipelineOptions.through = args.beforePhase;
|
|
58
|
+
}
|
|
59
|
+
const results = await run_pipeline(trigger_hook, pipelineOptions);
|
|
60
|
+
const docCount = Object.values(results.GenerateDocuments ?? {}).flat().length;
|
|
61
|
+
console.log(`\u{1F3A9} Generated ${docCount} ${docCount === 1 ? "document" : "documents"}`);
|
|
34
62
|
await on_close();
|
|
35
63
|
} catch (e) {
|
|
36
|
-
format_error(e,
|
|
64
|
+
format_error(e, (error) => {
|
|
37
65
|
console.error(error.stack?.split("\n").slice(1).join("\n"));
|
|
38
66
|
});
|
|
39
67
|
try {
|
package/build/cmd/index.js
CHANGED
|
@@ -8,7 +8,13 @@ const program = new Command();
|
|
|
8
8
|
program.command("generate").description("generate the application runtime").option("-p, --pull-schema", "pull the latest schema before generating").option("-r, --preserve-database", "preserve any existing generated logic").option("-o, --output [outputPath]", "persist queries to a queryMap file").option(
|
|
9
9
|
"-h, --headers <headers...>",
|
|
10
10
|
"headers to use when pulling your schema. Should be passed as KEY=VALUE"
|
|
11
|
-
).option("-v, --verbose", "verbose error messages").
|
|
11
|
+
).option("-v, --verbose", "verbose error messages").option(
|
|
12
|
+
"--after-phase <phase>",
|
|
13
|
+
"start the pipeline after the specified phase (Config, AfterLoad, Schema, ExtractDocuments, AfterExtract, BeforeValidate, Validate, AfterValidate, BeforeGenerate, GenerateDocuments, GenerateRuntime, AfterGenerate)"
|
|
14
|
+
).option(
|
|
15
|
+
"--before-phase <phase>",
|
|
16
|
+
"run the pipeline up to and including the specified phase (Config, AfterLoad, Schema, ExtractDocuments, AfterExtract, BeforeValidate, Validate, AfterValidate, BeforeGenerate, GenerateDocuments, GenerateRuntime, AfterGenerate)"
|
|
17
|
+
).action(generate);
|
|
12
18
|
program.command("init").arguments("[path]").usage("[path] [options]").description("initialize a new houdini project").option(
|
|
13
19
|
"-h, --headers <headers...>",
|
|
14
20
|
"header to use when pulling your schema. Should be passed as KEY=VALUE"
|
package/build/cmd/init.js
CHANGED
|
@@ -164,12 +164,14 @@ async function init(_path, args) {
|
|
|
164
164
|
const configPath = path.join(targetPath, "houdini.config.js");
|
|
165
165
|
const s = p.spinner();
|
|
166
166
|
s.start(`\u{1F6A7} Generating houdini's files...`);
|
|
167
|
+
const runtimeDir = ".houdini";
|
|
167
168
|
await houdiniConfig(
|
|
168
169
|
configPath,
|
|
169
170
|
schemaPath,
|
|
170
171
|
module,
|
|
171
172
|
frameworkInfo,
|
|
172
|
-
is_remote_endpoint ? url : null
|
|
173
|
+
is_remote_endpoint ? url : null,
|
|
174
|
+
runtimeDir
|
|
173
175
|
);
|
|
174
176
|
await houdiniClient(sourceDir, typescript, frameworkInfo, url);
|
|
175
177
|
if (frameworkInfo.framework === "svelte") {
|
|
@@ -177,8 +179,11 @@ async function init(_path, args) {
|
|
|
177
179
|
} else if (frameworkInfo.framework === "kit") {
|
|
178
180
|
await svelteConfig(targetPath, typescript);
|
|
179
181
|
}
|
|
180
|
-
await gitIgnore(
|
|
181
|
-
|
|
182
|
+
await gitIgnore({
|
|
183
|
+
targetPath,
|
|
184
|
+
schemaPath: is_remote_endpoint ? schemaPath : void 0
|
|
185
|
+
});
|
|
186
|
+
await graphqlRC(targetPath, runtimeDir);
|
|
182
187
|
await viteConfig(targetPath, frameworkInfo, typescript);
|
|
183
188
|
await tjsConfig(targetPath, frameworkInfo);
|
|
184
189
|
await packageJSON(targetPath, frameworkInfo);
|
|
@@ -211,14 +216,14 @@ function finale_logs(package_manager) {
|
|
|
211
216
|
)
|
|
212
217
|
);
|
|
213
218
|
}
|
|
214
|
-
async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url) {
|
|
219
|
+
async function houdiniConfig(configPath, schemaPath, module, frameworkInfo, url, runtimeDir) {
|
|
215
220
|
const config = {};
|
|
216
221
|
if (url !== null) {
|
|
217
222
|
config.watchSchema = {
|
|
218
223
|
url
|
|
219
224
|
};
|
|
220
225
|
}
|
|
221
|
-
config.runtimeDir =
|
|
226
|
+
config.runtimeDir = runtimeDir;
|
|
222
227
|
if (schemaPath !== "./schema.graphql") {
|
|
223
228
|
config.schemaPath = schemaPath;
|
|
224
229
|
}
|
|
@@ -244,7 +249,7 @@ const config = ${configObj}`;
|
|
|
244
249
|
const content = module === "esm" ? `${content_base}
|
|
245
250
|
|
|
246
251
|
export default config
|
|
247
|
-
` : `${content_base}
|
|
252
|
+
` : `${content_base}
|
|
248
253
|
|
|
249
254
|
module.exports = config
|
|
250
255
|
`;
|
|
@@ -264,7 +269,7 @@ export default new HoudiniClient({
|
|
|
264
269
|
// fetchParams({ session }) {
|
|
265
270
|
// return {
|
|
266
271
|
// headers: {
|
|
267
|
-
//
|
|
272
|
+
// Authorization: \`Bearer \${session.token}\`,
|
|
268
273
|
// }
|
|
269
274
|
// }
|
|
270
275
|
// }
|
|
@@ -323,24 +328,34 @@ export default config;
|
|
|
323
328
|
`;
|
|
324
329
|
await fs.writeFile(svelteConfigPath, typescript ? newContentTs : newContentJs);
|
|
325
330
|
}
|
|
326
|
-
async function gitIgnore(targetPath) {
|
|
331
|
+
async function gitIgnore({ targetPath, schemaPath }) {
|
|
327
332
|
const filepath = path.join(targetPath, ".gitignore");
|
|
328
333
|
const existing = await fs.readFile(filepath) || "";
|
|
334
|
+
let newIgnores = "";
|
|
329
335
|
if (!existing.includes("\n.houdini\n")) {
|
|
330
|
-
|
|
336
|
+
newIgnores += ".houdini\n";
|
|
337
|
+
}
|
|
338
|
+
if (schemaPath && !existing.includes(`
|
|
339
|
+
${schemaPath}
|
|
340
|
+
`)) {
|
|
341
|
+
newIgnores += `${schemaPath}
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
if (newIgnores) {
|
|
345
|
+
await fs.writeFile(filepath, existing + "\n" + newIgnores);
|
|
331
346
|
}
|
|
332
347
|
}
|
|
333
|
-
async function graphqlRC(targetPath) {
|
|
348
|
+
async function graphqlRC(targetPath, runtimeDir) {
|
|
334
349
|
const target = path.join(targetPath, ".graphqlrc.yaml");
|
|
335
350
|
const content = `projects:
|
|
336
351
|
default:
|
|
337
352
|
schema:
|
|
338
353
|
- ./schema.graphql
|
|
339
|
-
-
|
|
354
|
+
- ./${runtimeDir}/graphql/schema.graphql
|
|
340
355
|
documents:
|
|
341
356
|
- '**/*.gql'
|
|
342
357
|
- '**/*.svelte'
|
|
343
|
-
-
|
|
358
|
+
- ./${runtimeDir}/graphql/documents.gql
|
|
344
359
|
`;
|
|
345
360
|
await fs.writeFile(target, content);
|
|
346
361
|
}
|
|
@@ -420,12 +435,12 @@ async function packageJSON(targetPath, frameworkInfo) {
|
|
|
420
435
|
}
|
|
421
436
|
packageJSON2.devDependencies = {
|
|
422
437
|
...packageJSON2.devDependencies,
|
|
423
|
-
houdini: "^2.0.0-go.
|
|
438
|
+
houdini: "^2.0.0-go.20"
|
|
424
439
|
};
|
|
425
440
|
if (frameworkInfo.framework === "svelte" || frameworkInfo.framework === "kit") {
|
|
426
441
|
packageJSON2.devDependencies = {
|
|
427
442
|
...packageJSON2.devDependencies,
|
|
428
|
-
"houdini-svelte": "^
|
|
443
|
+
"houdini-svelte": "^3.0.0-go.21"
|
|
429
444
|
};
|
|
430
445
|
} else {
|
|
431
446
|
throw new Error(`Unmanaged framework: "${JSON.stringify(frameworkInfo)}"`);
|
package/build/lib/ast.d.ts
CHANGED
|
@@ -12,5 +12,5 @@ export declare function find_exported_fn(body: Statement[], name: string): {
|
|
|
12
12
|
declaration: FunctionDeclaration | FunctionExpression | ArrowFunctionExpression | null | Identifier | CallExpression;
|
|
13
13
|
export: ExportNamedDeclaration;
|
|
14
14
|
} | null;
|
|
15
|
-
export declare function find_exported_id(program: Program, name: string): recast.types.namedTypes.ExportNamedDeclaration
|
|
15
|
+
export declare function find_exported_id(program: Program, name: string): recast.types.namedTypes.ExportNamedDeclaration;
|
|
16
16
|
export {};
|
package/build/lib/codegen.d.ts
CHANGED
|
@@ -42,9 +42,10 @@ export type CompilerProxy = {
|
|
|
42
42
|
database_path: string;
|
|
43
43
|
run_pipeline: (options: RunPipelineOptions) => Promise<Record<PipelineHook, Record<string, any>>>;
|
|
44
44
|
};
|
|
45
|
+
export declare function plugin_db_key(name: string): string;
|
|
45
46
|
export declare function codegen_setup(config: Config, mode: string, db: DatabaseSync, db_file: string): Promise<CompilerProxy>;
|
|
46
|
-
declare const PIPELINE_HOOKS: readonly ["Config", "AfterLoad", "Schema", "ExtractDocuments", "AfterExtract", "BeforeValidate", "Validate", "AfterValidate", "BeforeGenerate", "GenerateDocuments", "GenerateRuntime", "AfterGenerate"];
|
|
47
|
-
type PipelineHook = (typeof PIPELINE_HOOKS)[number];
|
|
47
|
+
export declare const PIPELINE_HOOKS: readonly ["Config", "AfterLoad", "Schema", "ExtractDocuments", "AfterExtract", "BeforeValidate", "Validate", "AfterValidate", "BeforeGenerate", "GenerateDocuments", "GenerateRuntime", "AfterGenerate"];
|
|
48
|
+
export type PipelineHook = (typeof PIPELINE_HOOKS)[number];
|
|
48
49
|
export type RunPipelineOptions = {
|
|
49
50
|
task_id?: string;
|
|
50
51
|
after?: PipelineHook;
|
|
@@ -52,4 +53,3 @@ export type RunPipelineOptions = {
|
|
|
52
53
|
through?: PipelineHook;
|
|
53
54
|
};
|
|
54
55
|
export declare function run_pipeline(trigger_hook: CompilerProxy['trigger_hook'], options?: RunPipelineOptions): Promise<Record<PipelineHook, Record<string, any>>>;
|
|
55
|
-
export {};
|
package/build/lib/codegen.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline";
|
|
3
4
|
import sqlite from "node:sqlite";
|
|
4
5
|
import { WebSocket } from "ws";
|
|
5
6
|
import * as conventions from "../router/conventions.js";
|
|
6
7
|
import { create_schema, write_config } from "./database.js";
|
|
7
8
|
import { format_hook_error } from "./error.js";
|
|
8
9
|
import * as fs from "./fs.js";
|
|
10
|
+
import { Logger } from "./logger.js";
|
|
11
|
+
import { LogLevel } from "./types.js";
|
|
9
12
|
function connect_db(config) {
|
|
10
13
|
const filepath = conventions.db_path(config);
|
|
11
14
|
const db = new sqlite.DatabaseSync(filepath);
|
|
@@ -37,29 +40,43 @@ async function init_db(config, preserve) {
|
|
|
37
40
|
}
|
|
38
41
|
return [connect_db(config)[0], db_file];
|
|
39
42
|
}
|
|
43
|
+
function plugin_db_key(name) {
|
|
44
|
+
if (!name.startsWith("./") && !name.startsWith("../") && !path.isAbsolute(name)) {
|
|
45
|
+
return name;
|
|
46
|
+
}
|
|
47
|
+
return name.replace(/\./g, "_").replace(/\//g, "__");
|
|
48
|
+
}
|
|
40
49
|
async function codegen_setup(config, mode, db, db_file) {
|
|
50
|
+
const logger = new Logger(config.config_file.logLevel ?? LogLevel.Summary);
|
|
41
51
|
await fs.mkdirpSync(conventions.houdini_root(config));
|
|
52
|
+
const rawTransport = config.config_file.pluginTransport ?? "websocket";
|
|
53
|
+
const resolvedTransport = rawTransport.startsWith("env:") ? process.env[rawTransport.slice("env:".length)] ?? "websocket" : rawTransport;
|
|
54
|
+
const useStdio = resolvedTransport === "stdio";
|
|
42
55
|
const plugins = {};
|
|
43
56
|
const plugin_specs = [];
|
|
44
57
|
const spec_results = {};
|
|
45
|
-
const
|
|
58
|
+
const stdioStdin = /* @__PURE__ */ new Map();
|
|
59
|
+
const triggerHookRef = { fn: null };
|
|
60
|
+
const wait_for_plugin_db = (configKey, dbKey) => new Promise((resolve, reject) => {
|
|
46
61
|
const find_plugin = db.prepare("SELECT * FROM plugins WHERE name = ?");
|
|
47
62
|
const interval = setInterval(() => {
|
|
48
|
-
const row = find_plugin.get(
|
|
63
|
+
const row = find_plugin.get(dbKey);
|
|
49
64
|
if (row) {
|
|
50
65
|
clearInterval(interval);
|
|
51
66
|
db.prepare("UPDATE plugins set config = ? where name = ?").run(
|
|
52
|
-
JSON.stringify(
|
|
53
|
-
|
|
67
|
+
JSON.stringify(
|
|
68
|
+
config.plugins.find((p) => p.name === configKey)?.config ?? {}
|
|
69
|
+
),
|
|
70
|
+
dbKey
|
|
54
71
|
);
|
|
55
72
|
const spec = {
|
|
56
73
|
name: row.name,
|
|
57
74
|
port: row.port,
|
|
58
75
|
hooks: new Set(JSON.parse(row.hooks)),
|
|
59
76
|
order: row.plugin_order,
|
|
60
|
-
directory: config.plugins.find((p) => p.name ===
|
|
77
|
+
directory: config.plugins.find((p) => p.name === configKey)?.directory || ""
|
|
61
78
|
};
|
|
62
|
-
spec_results[
|
|
79
|
+
spec_results[configKey] = spec;
|
|
63
80
|
if (row.config_module) {
|
|
64
81
|
import(row.config_module).then((module) => {
|
|
65
82
|
if (module && typeof module.default === "function") {
|
|
@@ -70,20 +87,142 @@ async function codegen_setup(config, mode, db, db_file) {
|
|
|
70
87
|
} else {
|
|
71
88
|
resolver(spec);
|
|
72
89
|
}
|
|
73
|
-
resolver(spec);
|
|
74
90
|
}
|
|
75
91
|
}, 10);
|
|
76
92
|
const timeout = setTimeout(() => {
|
|
77
93
|
clearInterval(interval);
|
|
78
|
-
reject(new Error(`Timeout waiting for plugin ${
|
|
94
|
+
reject(new Error(`Timeout waiting for plugin ${configKey} to register`));
|
|
79
95
|
}, 1e4);
|
|
80
96
|
const resolver = (spec) => {
|
|
81
97
|
clearTimeout(timeout);
|
|
82
98
|
resolve(spec);
|
|
83
99
|
};
|
|
84
100
|
});
|
|
101
|
+
const wait_for_plugin_stdio = (name, child) => new Promise((resolve, reject) => {
|
|
102
|
+
const timeout = setTimeout(() => {
|
|
103
|
+
reject(new Error(`Timeout waiting for plugin ${name} to register`));
|
|
104
|
+
}, 1e4);
|
|
105
|
+
const rl = createInterface({ input: child.stdout });
|
|
106
|
+
let registered = false;
|
|
107
|
+
rl.on("line", (line) => {
|
|
108
|
+
try {
|
|
109
|
+
const msg = JSON.parse(line);
|
|
110
|
+
if (!registered) {
|
|
111
|
+
if (msg.type !== "register") {
|
|
112
|
+
clearTimeout(timeout);
|
|
113
|
+
reject(new Error(`Plugin ${name} sent ${msg.type} before registering`));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
registered = true;
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
const spec = {
|
|
119
|
+
name: msg.name ?? plugin_db_key(name),
|
|
120
|
+
port: msg.port ?? 0,
|
|
121
|
+
hooks: new Set(msg.hooks ?? []),
|
|
122
|
+
order: msg.order,
|
|
123
|
+
directory: config.plugins.find((p) => p.name === name)?.directory || ""
|
|
124
|
+
};
|
|
125
|
+
spec_results[name] = spec;
|
|
126
|
+
db.prepare(
|
|
127
|
+
`INSERT OR IGNORE INTO plugins (name, hooks, port, plugin_order, include_runtime, config_module, client_plugins)
|
|
128
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
129
|
+
).run(
|
|
130
|
+
spec.name,
|
|
131
|
+
JSON.stringify([...spec.hooks]),
|
|
132
|
+
spec.port,
|
|
133
|
+
spec.order,
|
|
134
|
+
msg.includeRuntime ?? null,
|
|
135
|
+
msg.configModule ?? null,
|
|
136
|
+
msg.clientPlugins ?? null
|
|
137
|
+
);
|
|
138
|
+
db.prepare("UPDATE plugins SET config = ? WHERE name = ?").run(
|
|
139
|
+
JSON.stringify(
|
|
140
|
+
config.plugins.find((p) => p.name === name)?.config ?? {}
|
|
141
|
+
),
|
|
142
|
+
spec.name
|
|
143
|
+
);
|
|
144
|
+
if (msg.configModule) {
|
|
145
|
+
import(msg.configModule).then((module) => {
|
|
146
|
+
if (module && typeof module.default === "function") {
|
|
147
|
+
config.config_file = module.default(config.config_file);
|
|
148
|
+
}
|
|
149
|
+
resolve(spec);
|
|
150
|
+
}).catch((err) => {
|
|
151
|
+
reject(
|
|
152
|
+
new Error(
|
|
153
|
+
`Failed to load configModule for ${name}: ${err.message}`
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
resolve(spec);
|
|
159
|
+
}
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (msg.type === "response") {
|
|
163
|
+
const pending = pendingRequests.get(msg.id);
|
|
164
|
+
if (!pending)
|
|
165
|
+
return;
|
|
166
|
+
clearTimeout(pending.timeout);
|
|
167
|
+
pendingRequests.delete(msg.id);
|
|
168
|
+
if (msg.error) {
|
|
169
|
+
const errors = Array.isArray(msg.error) ? msg.error : [msg.error];
|
|
170
|
+
errors.forEach(
|
|
171
|
+
(error) => format_hook_error(config.root_dir, error, name, pending.hook)
|
|
172
|
+
);
|
|
173
|
+
pending.reject(new Error(`Failed to call ${name}`));
|
|
174
|
+
} else {
|
|
175
|
+
pending.resolve(msg.result);
|
|
176
|
+
}
|
|
177
|
+
} else if (msg.type === "invoke") {
|
|
178
|
+
if (!triggerHookRef.fn) {
|
|
179
|
+
const reply = JSON.stringify({
|
|
180
|
+
id: msg.id,
|
|
181
|
+
type: "invoke_result",
|
|
182
|
+
error: { message: "orchestrator not ready" }
|
|
183
|
+
}) + "\n";
|
|
184
|
+
child.stdin?.write(reply);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
triggerHookRef.fn(msg.hook, {
|
|
188
|
+
parallel_safe: msg.parallel,
|
|
189
|
+
payload: msg.payload,
|
|
190
|
+
task_id: msg.taskId
|
|
191
|
+
}).then((result) => {
|
|
192
|
+
const reply = JSON.stringify({ id: msg.id, type: "invoke_result", result }) + "\n";
|
|
193
|
+
child.stdin?.write(reply);
|
|
194
|
+
}).catch((err) => {
|
|
195
|
+
const reply = JSON.stringify({
|
|
196
|
+
id: msg.id,
|
|
197
|
+
type: "invoke_result",
|
|
198
|
+
error: { message: err.message }
|
|
199
|
+
}) + "\n";
|
|
200
|
+
child.stdin?.write(reply);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
} catch (err) {
|
|
204
|
+
if (!registered) {
|
|
205
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
rl.on("close", () => {
|
|
210
|
+
if (!registered) {
|
|
211
|
+
clearTimeout(timeout);
|
|
212
|
+
reject(new Error(`Plugin ${name} stdout closed before registering`));
|
|
213
|
+
} else {
|
|
214
|
+
for (const [id, pending] of pendingRequests.entries()) {
|
|
215
|
+
if (pending.plugin !== name)
|
|
216
|
+
continue;
|
|
217
|
+
clearTimeout(pending.timeout);
|
|
218
|
+
pendingRequests.delete(id);
|
|
219
|
+
pending.reject(new Error(`Plugin ${name} closed unexpectedly`));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
});
|
|
85
224
|
db.prepare("DELETE FROM plugins").run();
|
|
86
|
-
|
|
225
|
+
logger.time("Start Plugins");
|
|
87
226
|
await Promise.all(
|
|
88
227
|
config.plugins.map(async (plugin) => {
|
|
89
228
|
let executable = plugin.executable;
|
|
@@ -93,21 +232,35 @@ async function codegen_setup(config, mode, db, db_file) {
|
|
|
93
232
|
executable = "node";
|
|
94
233
|
args.unshift(plugin.executable);
|
|
95
234
|
}
|
|
96
|
-
|
|
235
|
+
const dbKey = plugin_db_key(plugin.name);
|
|
236
|
+
args.push("--plugin-key", dbKey);
|
|
237
|
+
if (useStdio) {
|
|
238
|
+
args.push("--transport", "stdio");
|
|
239
|
+
}
|
|
240
|
+
logger.time(`Spawn ${plugin.name}`);
|
|
241
|
+
const child = spawn(executable, args, {
|
|
242
|
+
stdio: useStdio ? ["pipe", "pipe", "inherit"] : ["inherit", "inherit", "inherit"],
|
|
243
|
+
detached: process.platform !== "win32"
|
|
244
|
+
});
|
|
245
|
+
if (useStdio) {
|
|
246
|
+
stdioStdin.set(dbKey, child.stdin);
|
|
247
|
+
child.stdin.on("error", (err) => {
|
|
248
|
+
if (err.code !== "EPIPE") {
|
|
249
|
+
console.error(`[${plugin.name}] stdin error:`, err.message);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
97
253
|
plugins[plugin.name] = {
|
|
98
|
-
process:
|
|
99
|
-
|
|
100
|
-
detached: process.platform !== "win32"
|
|
101
|
-
}),
|
|
102
|
-
...await wait_for_plugin(plugin.name)
|
|
254
|
+
process: child,
|
|
255
|
+
...await (useStdio ? wait_for_plugin_stdio(plugin.name, child) : wait_for_plugin_db(plugin.name, dbKey))
|
|
103
256
|
};
|
|
104
|
-
|
|
257
|
+
logger.timeEnd(`Spawn ${plugin.name}`, LogLevel.Verbose);
|
|
105
258
|
})
|
|
106
259
|
);
|
|
107
260
|
for (const plugin of config.plugins) {
|
|
108
261
|
plugin_specs.push(spec_results[plugin.name]);
|
|
109
262
|
}
|
|
110
|
-
|
|
263
|
+
logger.timeEnd("Start Plugins", LogLevel.Summary);
|
|
111
264
|
const wsConnections = /* @__PURE__ */ new Map();
|
|
112
265
|
let messageCounter = 0;
|
|
113
266
|
const pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -171,24 +324,39 @@ async function codegen_setup(config, mode, db, db_file) {
|
|
|
171
324
|
throw new Error(`unknown plugin: ${name}`);
|
|
172
325
|
}
|
|
173
326
|
const { port, directory } = plugin;
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
327
|
+
const messageId = String(++messageCounter);
|
|
328
|
+
const message = {
|
|
329
|
+
id: messageId,
|
|
330
|
+
type: "request",
|
|
331
|
+
hook,
|
|
332
|
+
payload,
|
|
333
|
+
taskId: task_id,
|
|
334
|
+
pluginDirectory: directory
|
|
335
|
+
};
|
|
336
|
+
if (useStdio) {
|
|
337
|
+
const stdin = stdioStdin.get(name);
|
|
338
|
+
if (!stdin) {
|
|
339
|
+
throw new Error(`No stdio channel for plugin ${name}`);
|
|
340
|
+
}
|
|
341
|
+
return new Promise((resolve, reject) => {
|
|
342
|
+
const timeout = setTimeout(() => {
|
|
343
|
+
pendingRequests.delete(messageId);
|
|
344
|
+
reject(new Error(`Request timeout for ${name}/${hook}`));
|
|
345
|
+
}, 3e4);
|
|
346
|
+
pendingRequests.set(messageId, { resolve, reject, timeout, hook, plugin: name });
|
|
347
|
+
stdin.write(JSON.stringify(message) + "\n");
|
|
348
|
+
});
|
|
349
|
+
} else {
|
|
350
|
+
const ws = await getOrCreateWS(name, port);
|
|
351
|
+
return new Promise((resolve, reject) => {
|
|
352
|
+
const timeout = setTimeout(() => {
|
|
353
|
+
pendingRequests.delete(messageId);
|
|
354
|
+
reject(new Error(`WebSocket request timeout for ${name}/${hook}`));
|
|
355
|
+
}, 3e4);
|
|
356
|
+
pendingRequests.set(messageId, { resolve, reject, timeout, hook, plugin: name });
|
|
357
|
+
ws.send(JSON.stringify(message));
|
|
358
|
+
});
|
|
359
|
+
}
|
|
192
360
|
};
|
|
193
361
|
const trigger_hook = async (hook, {
|
|
194
362
|
parallel_safe,
|
|
@@ -196,24 +364,28 @@ async function codegen_setup(config, mode, db, db_file) {
|
|
|
196
364
|
task_id
|
|
197
365
|
} = {}) => {
|
|
198
366
|
const timeName = hook + (task_id ? ` (${task_id})` : "");
|
|
199
|
-
|
|
367
|
+
logger.time(timeName);
|
|
200
368
|
const plugins2 = plugin_specs.filter(({ hooks }) => hooks.has(hook));
|
|
201
369
|
const result = {};
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
370
|
+
try {
|
|
371
|
+
if (parallel_safe) {
|
|
372
|
+
await Promise.all(
|
|
373
|
+
plugins2.map(async (plugin) => {
|
|
374
|
+
result[plugin.name] = await invoke_hook(plugin.name, hook, payload, task_id);
|
|
375
|
+
})
|
|
376
|
+
);
|
|
377
|
+
} else {
|
|
378
|
+
for (const { name } of plugins2) {
|
|
379
|
+
result[name] = await invoke_hook(name, hook, payload, task_id);
|
|
380
|
+
}
|
|
211
381
|
}
|
|
382
|
+
} finally {
|
|
383
|
+
logger.timeEnd(timeName, task_id ? LogLevel.Verbose : LogLevel.Summary);
|
|
212
384
|
}
|
|
213
|
-
console.timeEnd(timeName);
|
|
214
385
|
return result;
|
|
215
386
|
};
|
|
216
|
-
|
|
387
|
+
triggerHookRef.fn = trigger_hook;
|
|
388
|
+
await write_config(db, config, invoke_hook, plugin_specs, mode, logger);
|
|
217
389
|
await trigger_hook("Config");
|
|
218
390
|
await trigger_hook("AfterLoad");
|
|
219
391
|
await trigger_hook("Schema");
|
|
@@ -232,8 +404,16 @@ async function codegen_setup(config, mode, db, db_file) {
|
|
|
232
404
|
}
|
|
233
405
|
}
|
|
234
406
|
wsConnections.clear();
|
|
235
|
-
for (const [,
|
|
407
|
+
for (const [, stdin] of stdioStdin.entries()) {
|
|
408
|
+
try {
|
|
409
|
+
stdin.end();
|
|
410
|
+
} catch {
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
stdioStdin.clear();
|
|
414
|
+
for (const [, { timeout, reject }] of pendingRequests.entries()) {
|
|
236
415
|
clearTimeout(timeout);
|
|
416
|
+
reject(new Error("codegen closed"));
|
|
237
417
|
}
|
|
238
418
|
pendingRequests.clear();
|
|
239
419
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
@@ -315,13 +495,25 @@ async function run_pipeline(trigger_hook, options = {}) {
|
|
|
315
495
|
if (hook === "Validate" || hook === "GenerateDocuments") {
|
|
316
496
|
opts.parallel_safe = true;
|
|
317
497
|
}
|
|
498
|
+
if (hook === "GenerateDocuments" && i + 1 <= endIndex && PIPELINE_HOOKS[i + 1] === "GenerateRuntime") {
|
|
499
|
+
const [gdResult, grResult] = await Promise.all([
|
|
500
|
+
trigger_hook("GenerateDocuments", { task_id, parallel_safe: true }),
|
|
501
|
+
trigger_hook("GenerateRuntime", { task_id })
|
|
502
|
+
]);
|
|
503
|
+
results["GenerateDocuments"] = gdResult;
|
|
504
|
+
results["GenerateRuntime"] = grResult;
|
|
505
|
+
i++;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
318
508
|
results[hook] = await trigger_hook(hook, opts);
|
|
319
509
|
}
|
|
320
510
|
return results;
|
|
321
511
|
}
|
|
322
512
|
export {
|
|
513
|
+
PIPELINE_HOOKS,
|
|
323
514
|
codegen_setup,
|
|
324
515
|
connect_db,
|
|
325
516
|
init_db,
|
|
517
|
+
plugin_db_key,
|
|
326
518
|
run_pipeline
|
|
327
519
|
};
|