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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcway",
3
- "version": "0.1.29",
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
  }
@@ -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;
@@ -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, pluginMigrations = []) {
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
- const pluginMigrations = config.pluginMigrations ?? [];
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, pluginMigrations),
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
- const pluginMigrations = config.pluginMigrations ?? [];
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, pluginMigrations),
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
- throw new Error(`Plugin package at ${packageDir} must include plugin.js or plugin.json`);
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
- migrationsDir: (await exists(migrationsDir)) ? migrationsDir : null,
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: ['plugin.js', 'plugin.json'],
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]);
@@ -30,7 +30,6 @@ function normalizeContributes(value) {
30
30
  tools: value.tools ?? [],
31
31
  routes: value.routes ?? [],
32
32
  jobs: value.jobs ?? [],
33
- migrations: value.migrations,
34
33
  ui: value.ui,
35
34
  };
36
35
  }