arcway 0.1.29 → 0.1.30
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/package.json +8 -1
- package/server/boot/index.js +0 -1
- package/server/db/index.js +5 -17
- package/server/plugins/discovery.js +29 -8
- package/server/plugins/manager.js +2 -11
- package/server/plugins/manifest.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "arcway",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.30",
|
|
4
4
|
"description": "A convention-based framework for building modular monoliths with strict domain boundaries.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -137,5 +137,12 @@
|
|
|
137
137
|
"react-dom": "^19.2.4",
|
|
138
138
|
"storybook": "^10.2.8",
|
|
139
139
|
"vitest": "^4.0.18"
|
|
140
|
+
},
|
|
141
|
+
"pnpm": {
|
|
142
|
+
"onlyBuiltDependencies": [
|
|
143
|
+
"better-sqlite3",
|
|
144
|
+
"esbuild",
|
|
145
|
+
"sharp"
|
|
146
|
+
]
|
|
140
147
|
}
|
|
141
148
|
}
|
package/server/boot/index.js
CHANGED
|
@@ -29,7 +29,6 @@ async function boot(options) {
|
|
|
29
29
|
const config = await makeConfig(rootDir, { overrides: options.configOverrides, mode });
|
|
30
30
|
|
|
31
31
|
const plugins = await createPluginManager(config.plugins, { runLifecycle: false });
|
|
32
|
-
config.database.pluginMigrations = plugins.migrations();
|
|
33
32
|
|
|
34
33
|
const infrastructure = await createInfrastructure(config, { runMigrations: true });
|
|
35
34
|
const { db, redis, queue, cache, files, mail, events, log } = infrastructure;
|
package/server/db/index.js
CHANGED
|
@@ -6,9 +6,8 @@ import { loadModule } from '../module-loader.js';
|
|
|
6
6
|
import { introspectSchema, generateSchemaMarkdown } from './schema/index.js';
|
|
7
7
|
|
|
8
8
|
class MigrationSource {
|
|
9
|
-
constructor(directory
|
|
9
|
+
constructor(directory) {
|
|
10
10
|
this.directory = directory;
|
|
11
|
-
this.pluginMigrations = pluginMigrations;
|
|
12
11
|
this.migrationPaths = new Map();
|
|
13
12
|
}
|
|
14
13
|
async getMigrations() {
|
|
@@ -23,15 +22,6 @@ class MigrationSource {
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
for (const plugin of this.pluginMigrations ?? []) {
|
|
27
|
-
const files = await discoverFiles(plugin.dir, { skipUnderscore: false });
|
|
28
|
-
for (const file of files) {
|
|
29
|
-
const name = `plugins/${plugin.pluginId}/${file.name}`;
|
|
30
|
-
migrations.push(name);
|
|
31
|
-
this.migrationPaths.set(name, file.filePath);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
25
|
return migrations;
|
|
36
26
|
}
|
|
37
27
|
getMigrationName(migration) {
|
|
@@ -98,10 +88,9 @@ async function createDB(config, { log } = {}) {
|
|
|
98
88
|
|
|
99
89
|
db.runMigrations = async () => {
|
|
100
90
|
const migrationsDir = config.dir;
|
|
101
|
-
|
|
102
|
-
if (!migrationsDir && pluginMigrations.length === 0) return;
|
|
91
|
+
if (!migrationsDir) return;
|
|
103
92
|
const [batch, migrations] = await db.migrate.latest({
|
|
104
|
-
migrationSource: new MigrationSource(migrationsDir
|
|
93
|
+
migrationSource: new MigrationSource(migrationsDir),
|
|
105
94
|
});
|
|
106
95
|
if (migrations.length > 0) {
|
|
107
96
|
log?.info(`Migrations (batch ${batch}): ${migrations.join(', ')}`);
|
|
@@ -112,12 +101,11 @@ async function createDB(config, { log } = {}) {
|
|
|
112
101
|
|
|
113
102
|
db.runRollback = async () => {
|
|
114
103
|
const migrationsDir = config.dir;
|
|
115
|
-
|
|
116
|
-
if (!migrationsDir && pluginMigrations.length === 0) {
|
|
104
|
+
if (!migrationsDir) {
|
|
117
105
|
throw new Error('No migrations dir configured');
|
|
118
106
|
}
|
|
119
107
|
const [batch, entries] = await db.migrate.rollback({
|
|
120
|
-
migrationSource: new MigrationSource(migrationsDir
|
|
108
|
+
migrationSource: new MigrationSource(migrationsDir),
|
|
121
109
|
});
|
|
122
110
|
return { batch, log: entries };
|
|
123
111
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { discoverPackageDirectories } from '../discovery.js';
|
|
3
|
+
import { discoverModules, discoverPackageDirectories } from '../discovery.js';
|
|
4
4
|
import { loadModule } from '../module-loader.js';
|
|
5
5
|
import { validateManifest } from './manifest.js';
|
|
6
6
|
|
|
@@ -30,6 +30,19 @@ function getManifest(module, manifestPath) {
|
|
|
30
30
|
return manifest;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// Load every `<dir>/<name>.js` and key the default export by file basename.
|
|
34
|
+
// Used for the capabilities/ and hooks/ folder conventions.
|
|
35
|
+
async function loadConventionModules(dir, label) {
|
|
36
|
+
if (!(await exists(dir))) return {};
|
|
37
|
+
const modules = await discoverModules(dir, { label: `plugin ${label}` });
|
|
38
|
+
const map = {};
|
|
39
|
+
for (const entry of modules) {
|
|
40
|
+
const name = path.basename(entry.name, path.extname(entry.name));
|
|
41
|
+
map[name] = entry.module.default ?? entry.module;
|
|
42
|
+
}
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
async function loadPluginPackage(packageDir) {
|
|
34
47
|
const packageJsonPath = path.join(packageDir, 'package.json');
|
|
35
48
|
if (!(await exists(packageJsonPath))) {
|
|
@@ -50,14 +63,24 @@ async function loadPluginPackage(packageDir) {
|
|
|
50
63
|
} else if (await exists(pluginJson)) {
|
|
51
64
|
manifestPath = pluginJson;
|
|
52
65
|
rawManifest = await readJson(pluginJson, 'plugin.json');
|
|
66
|
+
} else if (packageJson.arcway && typeof packageJson.arcway === 'object') {
|
|
67
|
+
// Metadata-only plugin: the manifest lives in package.json's "arcway" block,
|
|
68
|
+
// and code is discovered by folder convention (routes/, jobs/, capabilities/, hooks/).
|
|
69
|
+
manifestPath = packageJsonPath;
|
|
70
|
+
rawManifest = {};
|
|
53
71
|
} else {
|
|
54
|
-
|
|
72
|
+
// A package without plugin.js/plugin.json and without an "arcway" block is
|
|
73
|
+
// not a plugin — skip it rather than failing discovery.
|
|
74
|
+
return null;
|
|
55
75
|
}
|
|
56
76
|
|
|
77
|
+
// package.json "arcway" supplies the base metadata; an explicit plugin.js/plugin.json
|
|
78
|
+
// manifest (legacy) wins on conflict so existing plugins behave identically.
|
|
57
79
|
const manifest = validateManifest(
|
|
58
80
|
{
|
|
59
81
|
version: packageJson.version,
|
|
60
82
|
name: packageJson.name,
|
|
83
|
+
...packageJson.arcway,
|
|
61
84
|
...rawManifest,
|
|
62
85
|
},
|
|
63
86
|
{ packageName: packageJson.name },
|
|
@@ -65,10 +88,6 @@ async function loadPluginPackage(packageDir) {
|
|
|
65
88
|
|
|
66
89
|
const routesDir = path.join(packageDir, 'routes');
|
|
67
90
|
const jobsDir = path.join(packageDir, 'jobs');
|
|
68
|
-
const migrationsDir =
|
|
69
|
-
typeof manifest.contributes.migrations === 'string'
|
|
70
|
-
? path.join(packageDir, manifest.contributes.migrations)
|
|
71
|
-
: path.join(packageDir, 'migrations');
|
|
72
91
|
|
|
73
92
|
return {
|
|
74
93
|
id: manifest.id,
|
|
@@ -79,7 +98,8 @@ async function loadPluginPackage(packageDir) {
|
|
|
79
98
|
manifestPath,
|
|
80
99
|
routesDir: (await exists(routesDir)) ? routesDir : null,
|
|
81
100
|
jobsDir: (await exists(jobsDir)) ? jobsDir : null,
|
|
82
|
-
|
|
101
|
+
capabilities: await loadConventionModules(path.join(packageDir, 'capabilities'), 'capability'),
|
|
102
|
+
hooks: await loadConventionModules(path.join(packageDir, 'hooks'), 'lifecycle hook'),
|
|
83
103
|
};
|
|
84
104
|
}
|
|
85
105
|
|
|
@@ -89,11 +109,12 @@ async function discoverPlugins(dirs) {
|
|
|
89
109
|
|
|
90
110
|
for (const dir of dirs ?? []) {
|
|
91
111
|
const packages = await discoverPackageDirectories(dir, {
|
|
92
|
-
markerFiles: ['
|
|
112
|
+
markerFiles: ['package.json'],
|
|
93
113
|
label: 'plugin',
|
|
94
114
|
});
|
|
95
115
|
for (const entry of packages) {
|
|
96
116
|
const plugin = await loadPluginPackage(entry.dir);
|
|
117
|
+
if (!plugin) continue;
|
|
97
118
|
if (seen.has(plugin.id)) {
|
|
98
119
|
throw new Error(
|
|
99
120
|
`Duplicate plugin id ${plugin.id} in ${seen.get(plugin.id)} and ${plugin.dir}`,
|
|
@@ -19,6 +19,7 @@ function pluginEnabledByDefault(plugin, enablements) {
|
|
|
19
19
|
|
|
20
20
|
function capabilityFactory(plugin, capability) {
|
|
21
21
|
return (
|
|
22
|
+
plugin.capabilities?.[capability] ??
|
|
22
23
|
plugin.module.capabilities?.[capability] ??
|
|
23
24
|
plugin.module.default?.capabilities?.[capability] ??
|
|
24
25
|
plugin.module[`create${capability[0]?.toUpperCase() ?? ''}${capability.slice(1)}`]
|
|
@@ -132,16 +133,6 @@ class PluginManager {
|
|
|
132
133
|
return jobs;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
migrations() {
|
|
136
|
-
return this.enabledPlugins
|
|
137
|
-
.filter((plugin) => plugin.migrationsDir)
|
|
138
|
-
.map((plugin) => ({
|
|
139
|
-
pluginId: plugin.id,
|
|
140
|
-
dir: plugin.migrationsDir,
|
|
141
|
-
contributes: plugin.manifest.contributes.migrations,
|
|
142
|
-
}));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
136
|
#rebuildGraph() {
|
|
146
137
|
const providers = buildProviderMap(this.plugins);
|
|
147
138
|
for (const plugin of this.plugins) {
|
|
@@ -190,7 +181,7 @@ class PluginManager {
|
|
|
190
181
|
}
|
|
191
182
|
|
|
192
183
|
async #runLifecycle(plugin, name) {
|
|
193
|
-
const hook = plugin.manifest.lifecycle?.[name];
|
|
184
|
+
const hook = plugin.hooks?.[name] ?? plugin.manifest.lifecycle?.[name];
|
|
194
185
|
if (!hook) return;
|
|
195
186
|
const fn =
|
|
196
187
|
typeof hook === 'function' ? hook : (plugin.module[hook] ?? plugin.module.default?.[hook]);
|