adonia 0.1.0

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.
Files changed (140) hide show
  1. package/README.md +64 -0
  2. package/build/adonis_peers.d.ts +14 -0
  3. package/build/adonis_peers.js +56 -0
  4. package/build/cli.d.ts +2 -0
  5. package/build/cli.js +111 -0
  6. package/build/commands/add.d.ts +18 -0
  7. package/build/commands/add.js +246 -0
  8. package/build/commands/check.d.ts +4 -0
  9. package/build/commands/check.js +159 -0
  10. package/build/commands/diff.d.ts +2 -0
  11. package/build/commands/diff.js +74 -0
  12. package/build/commands/init.d.ts +6 -0
  13. package/build/commands/init.js +124 -0
  14. package/build/commands/list.d.ts +2 -0
  15. package/build/commands/list.js +34 -0
  16. package/build/config_merge.d.ts +10 -0
  17. package/build/config_merge.js +71 -0
  18. package/build/constants_merge.d.ts +4 -0
  19. package/build/constants_merge.js +85 -0
  20. package/build/copy.d.ts +8 -0
  21. package/build/copy.js +33 -0
  22. package/build/host.d.ts +15 -0
  23. package/build/host.js +85 -0
  24. package/build/registry/index.d.ts +26 -0
  25. package/build/registry/index.js +116 -0
  26. package/build/registry/plan.d.ts +53 -0
  27. package/build/registry/plan.js +159 -0
  28. package/build/schema.d.ts +136 -0
  29. package/build/schema.js +58 -0
  30. package/build/types.d.ts +21 -0
  31. package/build/types.js +1 -0
  32. package/build/wire.d.ts +16 -0
  33. package/build/wire.js +162 -0
  34. package/package.json +51 -0
  35. package/registry/README.md +86 -0
  36. package/registry/account/contracts/avatar_storage.ts +7 -0
  37. package/registry/account/contracts/index.ts +7 -0
  38. package/registry/account/contracts/types.ts +23 -0
  39. package/registry/account/events.ts +14 -0
  40. package/registry/account/module.json +31 -0
  41. package/registry/account/options.ts +11 -0
  42. package/registry/account/service.ts +155 -0
  43. package/registry/account/stubs/adapters/account.ts +21 -0
  44. package/registry/account/stubs/adapters/drive_avatar_storage.ts +15 -0
  45. package/registry/account/stubs/controllers/account_controller.ts +190 -0
  46. package/registry/account/stubs/routes/account.ts +53 -0
  47. package/registry/account/stubs/validators/account.ts +37 -0
  48. package/registry/account/tests/account_service.spec.ts +132 -0
  49. package/registry/account/tests/fakes/memory_avatar_storage.ts +12 -0
  50. package/registry/api/envelope.ts +66 -0
  51. package/registry/api/exception.ts +38 -0
  52. package/registry/api/exception_handler.ts +54 -0
  53. package/registry/api/module.json +27 -0
  54. package/registry/api/provider.ts +129 -0
  55. package/registry/api/service.ts +88 -0
  56. package/registry/api/tests/emit_safe.spec.ts +107 -0
  57. package/registry/api/tests/respond.spec.ts +40 -0
  58. package/registry/auth/contracts/index.ts +18 -0
  59. package/registry/auth/contracts/password_reset_store.ts +21 -0
  60. package/registry/auth/contracts/session_manager.ts +19 -0
  61. package/registry/auth/contracts/types.ts +32 -0
  62. package/registry/auth/events.ts +21 -0
  63. package/registry/auth/module.json +45 -0
  64. package/registry/auth/options.ts +23 -0
  65. package/registry/auth/service.ts +150 -0
  66. package/registry/auth/stubs/adapters/auth.ts +21 -0
  67. package/registry/auth/stubs/adapters/lucid_access_token_session.ts +33 -0
  68. package/registry/auth/stubs/adapters/lucid_password_reset_store.ts +68 -0
  69. package/registry/auth/stubs/adapters/lucid_user_store.ts +86 -0
  70. package/registry/auth/stubs/adapters/map_user.ts +19 -0
  71. package/registry/auth/stubs/controllers/auth_controller.ts +126 -0
  72. package/registry/auth/stubs/migrations/add_pending_email_to_users_table.ts +17 -0
  73. package/registry/auth/stubs/migrations/create_password_resets_table.ts +27 -0
  74. package/registry/auth/stubs/migrations/create_users_table.ts +31 -0
  75. package/registry/auth/stubs/models/password_reset.ts +29 -0
  76. package/registry/auth/stubs/models/user.ts +99 -0
  77. package/registry/auth/stubs/providers/vine_provider.ts +43 -0
  78. package/registry/auth/stubs/routes/auth.ts +36 -0
  79. package/registry/auth/stubs/start/limiter.ts +17 -0
  80. package/registry/auth/stubs/validators/auth.ts +48 -0
  81. package/registry/auth/stubs/validators/rules/exists.ts +21 -0
  82. package/registry/auth/stubs/validators/rules/unique.ts +21 -0
  83. package/registry/auth/stubs/validators/rules/valid_password.ts +20 -0
  84. package/registry/auth/stubs/validators/rules/valid_token.ts +27 -0
  85. package/registry/auth/stubs/validators/vine.d.ts +10 -0
  86. package/registry/auth/tests/auth_service.spec.ts +145 -0
  87. package/registry/auth/tests/auth_update_password.spec.ts +99 -0
  88. package/registry/auth/tests/fakes/index.ts +3 -0
  89. package/registry/auth/tests/fakes/memory_password_reset_store.ts +71 -0
  90. package/registry/auth/tests/fakes/memory_session_manager.ts +23 -0
  91. package/registry/auth/tests/fakes/memory_user_store.ts +89 -0
  92. package/registry/constants/exceptions.ts +64 -0
  93. package/registry/constants/index.ts +12 -0
  94. package/registry/constants/responseCodes.ts +87 -0
  95. package/registry/contracts/index.ts +5 -0
  96. package/registry/contracts/user.ts +78 -0
  97. package/registry/creem/contracts/creem_client.ts +34 -0
  98. package/registry/creem/contracts/index.ts +8 -0
  99. package/registry/creem/contracts/types.ts +30 -0
  100. package/registry/creem/events.ts +9 -0
  101. package/registry/creem/module.json +28 -0
  102. package/registry/creem/service.ts +87 -0
  103. package/registry/creem/stubs/adapters/creem.ts +23 -0
  104. package/registry/creem/stubs/adapters/creem_io_client.ts +70 -0
  105. package/registry/creem/tests/fakes/fake_creem_client.ts +68 -0
  106. package/registry/notification/contracts/app_notification_store.ts +8 -0
  107. package/registry/notification/contracts/index.ts +9 -0
  108. package/registry/notification/contracts/mail_transport.ts +7 -0
  109. package/registry/notification/contracts/types.ts +32 -0
  110. package/registry/notification/module.json +23 -0
  111. package/registry/notification/options.ts +11 -0
  112. package/registry/notification/service.ts +103 -0
  113. package/registry/notification/stubs/adapters/adonis_mail_transport.ts +15 -0
  114. package/registry/notification/stubs/adapters/lucid_app_notification_store.ts +21 -0
  115. package/registry/notification/stubs/adapters/notification.ts +36 -0
  116. package/registry/notification/stubs/migrations/create_notifications_table.ts +23 -0
  117. package/registry/notification/stubs/models/app_notification.ts +45 -0
  118. package/registry/notification/tests/fakes/memory_app_notification_store.ts +19 -0
  119. package/registry/notification/tests/fakes/memory_mail_transport.ts +16 -0
  120. package/registry/notification/tests/notification_service.spec.ts +63 -0
  121. package/registry/registry.json +4 -0
  122. package/registry/subscription/contracts/index.ts +9 -0
  123. package/registry/subscription/contracts/subscription_store.ts +15 -0
  124. package/registry/subscription/contracts/types.ts +39 -0
  125. package/registry/subscription/events.ts +13 -0
  126. package/registry/subscription/module.json +31 -0
  127. package/registry/subscription/options.ts +14 -0
  128. package/registry/subscription/service.ts +140 -0
  129. package/registry/subscription/stubs/adapters/lucid_subscription_store.ts +69 -0
  130. package/registry/subscription/stubs/adapters/subscription.ts +21 -0
  131. package/registry/subscription/stubs/migrations/create_subscriptions_table.ts +26 -0
  132. package/registry/subscription/stubs/migrations/subscriptions_user_creem_unique.ts +19 -0
  133. package/registry/subscription/stubs/models/subscription.ts +45 -0
  134. package/registry/subscription/tests/billing_service.spec.ts +189 -0
  135. package/registry/subscription/tests/fakes/memory_subscription_store.ts +63 -0
  136. package/registry/types/api.ts +41 -0
  137. package/registry/types/constants.ts +7 -0
  138. package/registry/types/index.ts +6 -0
  139. package/registry/types/models.ts +32 -0
  140. package/schema/adonia.schema.json +39 -0
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # `@adonia` CLI (`packages/modules`)
2
+
3
+ npm package name: `adonia`. Bin: `adonia`.
4
+
5
+ In this monorepo, module sources are authored in
6
+ `apps/adonis-api-stater/modules/`. Sync before pack/test:
7
+
8
+ ```bash
9
+ npm run sync-registry -w adonia
10
+ npm test -w adonia
11
+ ```
12
+
13
+ ## Host install
14
+
15
+ ```bash
16
+ npx adonia@latest init --wire
17
+ npx adonia@latest add auth
18
+ # optional host scaffolding:
19
+ npx adonia@latest add auth --with-adapters
20
+ npx adonia@latest add auth --with-stubs
21
+ npx adonia@latest add auth --with-routes --wire-routes
22
+ # or full kit:
23
+ npx adonia@latest init --scaffold
24
+ ```
25
+
26
+ ## Commands
27
+
28
+ | Command | Role |
29
+ |---------|------|
30
+ | `init` / `init --wire` | `adonia.json` + core; optional host wiring |
31
+ | `init --scaffold` | Core + all feature modules with stubs and routes (implies `--wire`) |
32
+ | `add [names…]` | Copy module files under `modules/`, merge config (no app stubs by default) |
33
+ | `list` / `diff` / `check` | Discovery, drift, validation |
34
+
35
+ ### `add` scaffolding flags
36
+
37
+ | Flag | Effect |
38
+ |------|--------|
39
+ | `--with-models` | Copy `stubs/models/**` |
40
+ | `--with-migrations` | Copy `stubs/migrations/**` |
41
+ | `--with-controllers` | Copy `stubs/controllers/**` (implies `--with-adapters`) |
42
+ | `--with-validators` | Copy `stubs/validators/**` and `stubs/providers/**` |
43
+ | `--with-adapters` | Copy Lucid/SDK adapters → `app/adapters/` |
44
+ | `--with-stubs` | Models, migrations, controllers, validators, adapters, Vine provider, limiter stub (not routes) |
45
+ | `--with-routes` | Copy `stubs/routes/**` → `start/routes/` and `stubs/start/**` → `start/` |
46
+ | `--wire-routes` | Append `import './routes/<name>.js'` in `start/routes.ts` |
47
+ | `--with-tests` | Copy colocated module tests |
48
+
49
+ `diff` accepts the same `--with-*` flags; without them it diffs the
50
+ module tree only.
51
+
52
+ Adonis kits used by stubs (`@adonisjs/mail`, `@adonisjs/drive`,
53
+ `@adonisjs/limiter`) are not installed by `adonia add`. The CLI assumes
54
+ they are already configured, or `add` / `check` prints
55
+ `node ace add @adonisjs/<pkg>`.
56
+
57
+ Shared flags: `--yes`, `--dry-run`, `--cwd`, `--registry`, `--overwrite`.
58
+
59
+ ## Developing in the monorepo
60
+
61
+ ```bash
62
+ node packages/modules/build/cli.js add auth \
63
+ --registry apps/adonis-api-stater/modules
64
+ ```
@@ -0,0 +1,14 @@
1
+ export interface AdonisPeer {
2
+ pkg: string;
3
+ reason: string;
4
+ /** Host-relative path that means this Adonis kit is actually in use. */
5
+ whenFile?: string;
6
+ }
7
+ /** Adonis kits we assume are configured, or we tell the host to `ace add`. */
8
+ export declare const ADONIS_PEERS: Record<string, AdonisPeer[]>;
9
+ export declare function hostPackageNames(cwd: string): Set<string>;
10
+ export declare function missingAdonisPeers(cwd: string, moduleName: string, opts?: {
11
+ plannedFiles?: Iterable<string>;
12
+ }): AdonisPeer[];
13
+ export declare function aceAddHint(pkg: string): string;
14
+ export declare function formatMissingPeer(peer: AdonisPeer): string;
@@ -0,0 +1,56 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /** Adonis kits we assume are configured, or we tell the host to `ace add`. */
4
+ export const ADONIS_PEERS = {
5
+ auth: [
6
+ {
7
+ pkg: '@adonisjs/limiter',
8
+ reason: 'rate-limit auth routes',
9
+ whenFile: 'start/limiter.ts',
10
+ },
11
+ ],
12
+ account: [
13
+ {
14
+ pkg: '@adonisjs/drive',
15
+ reason: 'avatar file cleanup',
16
+ whenFile: 'app/adapters/drive_avatar_storage.ts',
17
+ },
18
+ ],
19
+ notification: [
20
+ {
21
+ pkg: '@adonisjs/mail',
22
+ reason: 'email notification channel',
23
+ whenFile: 'app/adapters/adonis_mail_transport.ts',
24
+ },
25
+ ],
26
+ };
27
+ export function hostPackageNames(cwd) {
28
+ const pkgPath = join(cwd, 'package.json');
29
+ if (!existsSync(pkgPath))
30
+ return new Set();
31
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
32
+ return new Set([
33
+ ...Object.keys(pkg.dependencies ?? {}),
34
+ ...Object.keys(pkg.devDependencies ?? {}),
35
+ ]);
36
+ }
37
+ export function missingAdonisPeers(cwd, moduleName, opts) {
38
+ const have = hostPackageNames(cwd);
39
+ const planned = new Set(opts?.plannedFiles ?? []);
40
+ return (ADONIS_PEERS[moduleName] ?? []).filter((peer) => {
41
+ if (have.has(peer.pkg))
42
+ return false;
43
+ if (peer.whenFile) {
44
+ const dest = join(cwd, peer.whenFile);
45
+ if (!planned.has(dest) && !existsSync(dest))
46
+ return false;
47
+ }
48
+ return true;
49
+ });
50
+ }
51
+ export function aceAddHint(pkg) {
52
+ return `node ace add ${pkg}`;
53
+ }
54
+ export function formatMissingPeer(peer) {
55
+ return `Missing ${peer.pkg} (${peer.reason}). Run: ${aceAddHint(peer.pkg)}`;
56
+ }
package/build/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/build/cli.js ADDED
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { resolve } from 'node:path';
4
+ import { runAdd } from './commands/add.js';
5
+ import { runCheck } from './commands/check.js';
6
+ import { runDiff } from './commands/diff.js';
7
+ import { runInit } from './commands/init.js';
8
+ import { runList } from './commands/list.js';
9
+ import { packageVersion } from './host.js';
10
+ const program = new Command();
11
+ program
12
+ .name('adonia')
13
+ .description('Adonia modules for AdonisJS')
14
+ .version(packageVersion());
15
+ function shared(cmd) {
16
+ const opts = cmd.optsWithGlobals();
17
+ return {
18
+ cwd: resolve(opts.cwd ?? process.cwd()),
19
+ yes: Boolean(opts.yes),
20
+ dryRun: Boolean(opts.dryRun),
21
+ registry: opts.registry,
22
+ overwrite: Boolean(opts.overwrite),
23
+ };
24
+ }
25
+ function scaffoldFromOpts(opts) {
26
+ return {
27
+ withModels: Boolean(opts.withModels),
28
+ withMigrations: Boolean(opts.withMigrations),
29
+ withControllers: Boolean(opts.withControllers),
30
+ withValidators: Boolean(opts.withValidators),
31
+ withAdapters: Boolean(opts.withAdapters),
32
+ withStubs: Boolean(opts.withStubs),
33
+ withRoutes: Boolean(opts.withRoutes),
34
+ wireRoutes: Boolean(opts.wireRoutes),
35
+ withTests: Boolean(opts.withTests),
36
+ };
37
+ }
38
+ function addScaffoldOptions(cmd) {
39
+ return cmd
40
+ .option('--with-models', 'copy stubs/models into the host', false)
41
+ .option('--with-migrations', 'copy stubs/migrations into the host', false)
42
+ .option('--with-controllers', 'copy stubs/controllers (implies --with-adapters)', false)
43
+ .option('--with-validators', 'copy stubs/validators and Vine provider into the host', false)
44
+ .option('--with-adapters', 'copy Lucid/SDK adapters into app/adapters/', false)
45
+ .option('--with-stubs', 'copy models, migrations, controllers, validators, adapters, Vine provider, and limiter stub', false)
46
+ .option('--with-routes', 'copy stubs/routes and start/limiter into the host', false)
47
+ .option('--wire-routes', 'append imports in start/routes.ts for module route files', false)
48
+ .option('--with-tests', 'copy colocated module tests', false);
49
+ }
50
+ program
51
+ .option('--cwd <dir>', 'host app directory', process.cwd())
52
+ .option('-y, --yes', 'skip prompts', false)
53
+ .option('--dry-run', 'print plan without writing', false)
54
+ .option('--registry <dir>', 'local registry path (fixture testing)')
55
+ .option('--overwrite', 'replace differing host files', false);
56
+ program
57
+ .command('init')
58
+ .description('Write adonia.json and install shared API core')
59
+ .option('--wire', 'apply host aliases / provider / exception handler', false)
60
+ .option('--skip-core', 'do not copy api + core packages', false)
61
+ .option('--scaffold', 'install all feature modules with stubs + routes (implies --wire)', false)
62
+ .action(async (_args, cmd) => {
63
+ const flags = shared(cmd);
64
+ const opts = cmd.opts();
65
+ await runInit({
66
+ ...flags,
67
+ wire: Boolean(opts.wire),
68
+ skipCore: Boolean(opts.skipCore),
69
+ scaffold: Boolean(opts.scaffold),
70
+ });
71
+ });
72
+ addScaffoldOptions(program
73
+ .command('add')
74
+ .description('Add one or more modules from the registry')
75
+ .argument('[names...]', 'module names')
76
+ .option('--overwrite', 'replace differing host files', false)).action(async (names, _opts, cmd) => {
77
+ const flags = shared(cmd);
78
+ const opts = cmd.opts();
79
+ await runAdd(names, {
80
+ ...flags,
81
+ overwrite: flags.overwrite || Boolean(opts.overwrite),
82
+ ...scaffoldFromOpts(opts),
83
+ });
84
+ });
85
+ program
86
+ .command('list')
87
+ .description('List registry modules and install status')
88
+ .action(async (_args, cmd) => {
89
+ await runList(shared(cmd));
90
+ });
91
+ addScaffoldOptions(program
92
+ .command('diff')
93
+ .description('Diff host files against the registry (module files; stubs only with --with-*)')
94
+ .argument('[name]', 'module name')).action(async (name, _opts, cmd) => {
95
+ const flags = shared(cmd);
96
+ const opts = cmd.opts();
97
+ await runDiff(name, { ...flags, ...scaffoldFromOpts(opts) });
98
+ });
99
+ program
100
+ .command('check')
101
+ .description('Validate host modules install')
102
+ .option('--strict', 'treat warnings as failures', false)
103
+ .action(async (_args, cmd) => {
104
+ const flags = shared(cmd);
105
+ const opts = cmd.opts();
106
+ await runCheck({ ...flags, strict: Boolean(opts.strict) });
107
+ });
108
+ program.parseAsync(process.argv).catch((err) => {
109
+ console.error(err instanceof Error ? err.message : err);
110
+ process.exitCode = 1;
111
+ });
@@ -0,0 +1,18 @@
1
+ import { type Registry } from '../registry/index.js';
2
+ import { type PlannedCopy } from '../registry/plan.js';
3
+ import type { HostModulesConfig } from '../schema.js';
4
+ import type { ScaffoldFlags, SharedFlags } from '../types.js';
5
+ export interface AddFlags extends SharedFlags, ScaffoldFlags {
6
+ /** Hide the post-install next-steps footer (used by init core). */
7
+ suppressNextSteps?: boolean;
8
+ }
9
+ export declare function runAdd(names: string[], flags: AddFlags): Promise<void>;
10
+ export declare function installModules(opts: {
11
+ names: string[];
12
+ flags: AddFlags;
13
+ config: HostModulesConfig;
14
+ registry: Registry;
15
+ }): Promise<{
16
+ conflicts: PlannedCopy[];
17
+ wireErrors: string[];
18
+ }>;
@@ -0,0 +1,246 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { mergeModulesConfig } from '../config_merge.js';
5
+ import { mergeConstantsCatalogs } from '../constants_merge.js';
6
+ import { detectPackageManager, loadHostConfig, writeHostConfig } from '../host.js';
7
+ import { expandDependencies, loadManifest, moduleDir, resolveRegistry, } from '../registry/index.js';
8
+ import { anyAppStubsSelected, describePlan, executePlan, hostRouteFile, manifestHasRouteStubs, planModuleFiles, planStubs, planTests, resolveStubSelect, } from '../registry/plan.js';
9
+ import { formatMissingPeer, missingAdonisPeers } from '../adonis_peers.js';
10
+ import { wireModuleRoute } from '../wire.js';
11
+ export async function runAdd(names, flags) {
12
+ if (!names.length) {
13
+ console.error('Usage: adonia add <name…>');
14
+ process.exitCode = 1;
15
+ return;
16
+ }
17
+ let config;
18
+ try {
19
+ config = loadHostConfig(flags.cwd);
20
+ }
21
+ catch (err) {
22
+ console.error(err instanceof Error ? err.message : err);
23
+ process.exitCode = 1;
24
+ return;
25
+ }
26
+ let registry;
27
+ try {
28
+ registry = await resolveRegistry({
29
+ registryFlag: flags.registry,
30
+ hostRegistry: config.registry,
31
+ ref: config.ref,
32
+ cwd: flags.cwd,
33
+ });
34
+ }
35
+ catch (err) {
36
+ console.error(err instanceof Error ? err.message : err);
37
+ process.exitCode = 1;
38
+ return;
39
+ }
40
+ let ordered;
41
+ try {
42
+ ordered = expandDependencies(registry, names, new Set(config.installed), flags.overwrite);
43
+ }
44
+ catch (err) {
45
+ console.error(err instanceof Error ? err.message : err);
46
+ process.exitCode = 1;
47
+ return;
48
+ }
49
+ if (!ordered.length) {
50
+ console.log('Nothing to install (already in adonia.json installed). Use --overwrite to recopy.');
51
+ return;
52
+ }
53
+ console.log(`Plan: ${ordered.join(' → ')}`);
54
+ if (!flags.yes && !flags.dryRun) {
55
+ console.log('(continuing; pass --yes to silence)');
56
+ }
57
+ const result = await installModules({ names: ordered, flags, config, registry });
58
+ if (result.conflicts.length) {
59
+ console.error('\nConflicts (use --overwrite to replace). Not recorded in adonia.json\n' +
60
+ 'installed until all planned files for the module copy cleanly.');
61
+ for (const c of result.conflicts) {
62
+ console.error(` ${describePlan(c, flags.cwd)}`);
63
+ }
64
+ process.exitCode = 1;
65
+ }
66
+ if (result.wireErrors.length) {
67
+ for (const e of result.wireErrors)
68
+ console.error(e);
69
+ process.exitCode = 1;
70
+ }
71
+ }
72
+ export async function installModules(opts) {
73
+ const { names, flags, registry } = opts;
74
+ let config = opts.config;
75
+ const allConflicts = [];
76
+ const wireErrors = [];
77
+ const warnings = [];
78
+ const npmDeps = new Map();
79
+ const plannedDests = new Set();
80
+ const stubSelect = resolveStubSelect(flags);
81
+ const requestedNames = new Set(names);
82
+ for (const name of names) {
83
+ const manifest = loadManifest(registry, name);
84
+ const plans = [];
85
+ plans.push(...planModuleFiles(registry, name, manifest, config, flags.cwd));
86
+ if (stubSelect) {
87
+ plans.push(...planStubs(registry, name, manifest, config, flags.cwd, warnings, stubSelect));
88
+ }
89
+ if (flags.withTests) {
90
+ plans.push(...planTests(registry, name, config, flags.cwd));
91
+ }
92
+ for (const p of plans)
93
+ plannedDests.add(p.dest);
94
+ if (flags.dryRun) {
95
+ for (const p of plans) {
96
+ console.log(`would process ${describePlan(p, flags.cwd)}`);
97
+ }
98
+ }
99
+ const { results, conflicts: moduleConflicts } = executePlan(plans, {
100
+ overwrite: flags.overwrite,
101
+ dryRun: flags.dryRun,
102
+ });
103
+ allConflicts.push(...moduleConflicts);
104
+ for (const { plan, result } of results) {
105
+ if (result === 'copied' || result === 'would-copy') {
106
+ console.log(`${result}: ${describePlan(plan, flags.cwd)}`);
107
+ }
108
+ }
109
+ for (const [pkg, version] of Object.entries(manifest.dependencies)) {
110
+ npmDeps.set(pkg, version);
111
+ }
112
+ // Constants merge after core is present: re-merge registry constants into host
113
+ if (name !== 'constants' && !flags.dryRun) {
114
+ const hostConstants = join(flags.cwd, config.paths.modules, 'constants');
115
+ const regConstants = join(moduleDir(registry, 'constants'));
116
+ if (existsSync(hostConstants) && existsSync(regConstants)) {
117
+ warnings.push(...mergeConstantsCatalogs(regConstants, hostConstants, flags.dryRun));
118
+ }
119
+ }
120
+ // Merge config/modules.ts for feature modules with events
121
+ if (manifest.events.length &&
122
+ name !== 'api' &&
123
+ !['types', 'constants', 'contracts'].includes(name)) {
124
+ const configPath = join(flags.cwd, 'config/modules.ts');
125
+ const merged = mergeModulesConfig(configPath, name, manifest.description, manifest.events, flags.dryRun);
126
+ if (!merged.ok) {
127
+ console.warn(`Could not merge config/modules.ts for ${name}. Add manually:\n${merged.snippet}`);
128
+ }
129
+ else {
130
+ console.log(`${flags.dryRun ? 'Would update' : 'Updated'} config/modules.ts (${name})`);
131
+ }
132
+ }
133
+ // Env hints
134
+ if (manifest.env.length) {
135
+ const missing = missingEnvKeys(flags.cwd, manifest.env);
136
+ if (missing.length) {
137
+ console.log(`Missing env keys for ${name} (not written): ${missing.join(', ')}`);
138
+ }
139
+ }
140
+ if (flags.wireRoutes && manifestHasRouteStubs(manifest)) {
141
+ const routeDest = hostRouteFile(flags.cwd, name);
142
+ const plannedRoute = plans.some((p) => p.dest === routeDest);
143
+ const haveRoute = existsSync(routeDest) || plannedRoute;
144
+ if (!haveRoute) {
145
+ wireErrors.push(`Cannot --wire-routes for "${name}": missing start/routes/${name}.ts. ` +
146
+ `Pass --with-routes first, or create the file.`);
147
+ }
148
+ else if (flags.dryRun && !existsSync(routeDest)) {
149
+ console.log(`Would wire routes: start/routes.ts → ./routes/${name}.js`);
150
+ }
151
+ else {
152
+ const wired = wireModuleRoute(flags.cwd, name, flags.dryRun);
153
+ if (!wired.ok) {
154
+ wireErrors.push(wired.error);
155
+ }
156
+ else if (wired.actions.length) {
157
+ console.log(`${flags.dryRun ? 'Would wire' : 'Wired'} routes: ${wired.actions.join(', ')}`);
158
+ }
159
+ }
160
+ }
161
+ if (!flags.dryRun && moduleConflicts.length === 0 && !config.installed.includes(name)) {
162
+ config = {
163
+ ...config,
164
+ installed: [...config.installed, name],
165
+ };
166
+ }
167
+ }
168
+ if (!flags.dryRun) {
169
+ writeHostConfig(flags.cwd, config);
170
+ }
171
+ if (npmDeps.size && !flags.dryRun) {
172
+ installNpmDeps(flags.cwd, npmDeps);
173
+ }
174
+ else if (npmDeps.size && flags.dryRun) {
175
+ console.log(`Would install npm deps: ${[...npmDeps.keys()].join(', ')}`);
176
+ }
177
+ for (const name of names) {
178
+ for (const peer of missingAdonisPeers(flags.cwd, name, { plannedFiles: plannedDests })) {
179
+ warnings.push(formatMissingPeer(peer));
180
+ }
181
+ }
182
+ for (const w of warnings) {
183
+ if (w)
184
+ console.warn(w);
185
+ }
186
+ printNextSteps(flags, stubSelect, [...requestedNames][0] ?? 'auth');
187
+ return { conflicts: allConflicts, wireErrors };
188
+ }
189
+ function printNextSteps(flags, stubSelect, exampleName) {
190
+ if (flags.suppressNextSteps)
191
+ return;
192
+ console.log('\nNext steps (not run by the CLI):');
193
+ if (!anyAppStubsSelected(stubSelect) && !stubSelect?.routes) {
194
+ console.log('Models/migrations/controllers/validators/adapters/routes were not copied.');
195
+ console.log(` adonia add ${exampleName} --with-stubs`);
196
+ console.log(` adonia add ${exampleName} --with-adapters`);
197
+ console.log(` adonia add ${exampleName} --with-routes --wire-routes`);
198
+ }
199
+ else {
200
+ if (stubSelect?.routes && !flags.wireRoutes) {
201
+ console.log(` adonia add ${exampleName} --wire-routes # mount start/routes/${exampleName}.ts`);
202
+ }
203
+ if (stubSelect?.providers) {
204
+ console.log(' Register providers/vine_provider.ts in adonisrc.ts providers.');
205
+ }
206
+ if (stubSelect?.start) {
207
+ console.log(' After limiter is installed, uncomment .use(authLimiter) / .use(accountLimiter) on sensitive routes.');
208
+ }
209
+ console.log(' 1. Run migrations: node ace migration:run');
210
+ console.log(' 2. Listen for module events as needed');
211
+ }
212
+ }
213
+ function missingEnvKeys(cwd, keys) {
214
+ const sources = [];
215
+ for (const f of ['.env', '.env.example', 'start/env.ts']) {
216
+ const p = join(cwd, f);
217
+ if (existsSync(p))
218
+ sources.push(readFileSync(p, 'utf8'));
219
+ }
220
+ const blob = sources.join('\n');
221
+ return keys.filter((k) => !blob.includes(k));
222
+ }
223
+ function installNpmDeps(cwd, deps) {
224
+ const pkgPath = join(cwd, 'package.json');
225
+ if (!existsSync(pkgPath))
226
+ return;
227
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
228
+ const have = { ...pkg.dependencies, ...pkg.devDependencies };
229
+ const missing = [...deps.entries()].filter(([name]) => !have[name]);
230
+ if (!missing.length) {
231
+ console.log('npm dependencies already satisfied.');
232
+ return;
233
+ }
234
+ const pm = detectPackageManager(cwd);
235
+ const specs = missing.map(([name, version]) => `${name}@${version}`);
236
+ console.log(`Installing with ${pm}: ${specs.join(' ')}`);
237
+ const args = pm === 'yarn'
238
+ ? ['add', ...specs]
239
+ : pm === 'pnpm'
240
+ ? ['add', ...specs]
241
+ : ['install', ...specs];
242
+ const result = spawnSync(pm, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });
243
+ if (result.status !== 0) {
244
+ console.warn(`Package install exited ${result.status}; install manually: ${specs.join(' ')}`);
245
+ }
246
+ }
@@ -0,0 +1,4 @@
1
+ import type { SharedFlags } from '../types.js';
2
+ export declare function runCheck(flags: SharedFlags & {
3
+ strict?: boolean;
4
+ }): Promise<void>;
@@ -0,0 +1,159 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { formatMissingPeer, missingAdonisPeers } from '../adonis_peers.js';
4
+ import { loadHostConfig } from '../host.js';
5
+ import { loadManifest, resolveRegistry } from '../registry/index.js';
6
+ import { CORE_PACKAGES } from '../schema.js';
7
+ import { REQUIRED_IMPORTS } from '../wire.js';
8
+ // Reserved manifest fields (not checked yet): hostTests, hostFacingExceptions.
9
+ export async function runCheck(flags) {
10
+ let config;
11
+ try {
12
+ config = loadHostConfig(flags.cwd);
13
+ }
14
+ catch (err) {
15
+ console.error(err instanceof Error ? err.message : err);
16
+ process.exitCode = 1;
17
+ return;
18
+ }
19
+ const errors = [];
20
+ const warnings = [];
21
+ const installed = new Set(config.installed);
22
+ for (const [key, rel] of Object.entries(config.paths)) {
23
+ const p = join(flags.cwd, rel);
24
+ if (!existsSync(p)) {
25
+ // paths may not exist until first add — warn for empty dirs
26
+ warnings.push(`path.${key} missing: ${rel}`);
27
+ }
28
+ }
29
+ let registry;
30
+ try {
31
+ registry = await resolveRegistry({
32
+ registryFlag: flags.registry,
33
+ hostRegistry: config.registry,
34
+ ref: config.ref,
35
+ cwd: flags.cwd,
36
+ });
37
+ }
38
+ catch (err) {
39
+ console.error(err instanceof Error ? err.message : err);
40
+ process.exitCode = 1;
41
+ return;
42
+ }
43
+ for (const name of config.installed) {
44
+ const folder = join(flags.cwd, config.paths.modules, name);
45
+ if (!existsSync(folder)) {
46
+ errors.push(`installed module folder missing: ${folder}`);
47
+ continue;
48
+ }
49
+ const manifestPath = join(folder, 'module.json');
50
+ if (existsSync(manifestPath)) {
51
+ try {
52
+ const local = JSON.parse(readFileSync(manifestPath, 'utf8'));
53
+ if (local.name && local.name !== name) {
54
+ errors.push(`module.json name mismatch in ${name}: ${local.name}`);
55
+ }
56
+ }
57
+ catch {
58
+ warnings.push(`could not parse ${manifestPath}`);
59
+ }
60
+ }
61
+ try {
62
+ const manifest = loadManifest(registry, name);
63
+ const requiredDeps = [...manifest.registryDependencies];
64
+ if (name === 'api') {
65
+ for (const core of CORE_PACKAGES) {
66
+ if (!requiredDeps.includes(core))
67
+ requiredDeps.push(core);
68
+ }
69
+ }
70
+ for (const dep of requiredDeps) {
71
+ if (!installed.has(dep)) {
72
+ errors.push(`installed module "${name}" requires "${dep}" (not in adonia.json installed)`);
73
+ }
74
+ }
75
+ for (const peer of manifest.peerModels) {
76
+ const file = peerModelToPath(peer, config.paths.models, flags.cwd);
77
+ if (!existsSync(file)) {
78
+ errors.push(`peerModel missing: ${peer} → ${file}`);
79
+ }
80
+ }
81
+ if (manifest.events.length) {
82
+ const configPath = join(flags.cwd, 'config/modules.ts');
83
+ if (!existsSync(configPath)) {
84
+ warnings.push(`config/modules.ts missing (events for ${name})`);
85
+ }
86
+ else {
87
+ const text = readFileSync(configPath, 'utf8');
88
+ for (const ev of manifest.events) {
89
+ if (!text.includes(ev)) {
90
+ warnings.push(`config/modules.ts missing event ${ev} for ${name}`);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ for (const peer of missingAdonisPeers(flags.cwd, name)) {
96
+ warnings.push(formatMissingPeer(peer));
97
+ }
98
+ if (manifest.configKeys.length) {
99
+ const configPath = join(flags.cwd, 'config/modules.ts');
100
+ if (!existsSync(configPath)) {
101
+ warnings.push(`config/modules.ts missing (configKeys for ${name})`);
102
+ }
103
+ else {
104
+ const text = readFileSync(configPath, 'utf8');
105
+ for (const key of manifest.configKeys) {
106
+ const leaf = key.includes('.') ? key.slice(key.lastIndexOf('.') + 1) : key;
107
+ if (!hasConfigKey(text, leaf)) {
108
+ warnings.push(`config/modules.ts missing key ${leaf} (${key}) for ${name}`);
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ catch (err) {
115
+ warnings.push(err instanceof Error ? err.message : String(err));
116
+ }
117
+ }
118
+ // aliases
119
+ const pkgPath = join(flags.cwd, 'package.json');
120
+ if (existsSync(pkgPath)) {
121
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
122
+ const imports = pkg.imports ?? {};
123
+ for (const key of Object.keys(REQUIRED_IMPORTS)) {
124
+ if (!(key in imports)) {
125
+ warnings.push(`package.json imports missing ${key}`);
126
+ }
127
+ }
128
+ }
129
+ const adonisrc = join(flags.cwd, 'adonisrc.ts');
130
+ if (existsSync(adonisrc) && !readFileSync(adonisrc, 'utf8').includes('#modules/api/provider')) {
131
+ warnings.push('adonisrc.ts missing #modules/api/provider (run init --wire)');
132
+ }
133
+ const kernel = join(flags.cwd, 'start/kernel.ts');
134
+ if (existsSync(kernel) && !readFileSync(kernel, 'utf8').includes('#modules/api/exception_handler')) {
135
+ warnings.push('start/kernel.ts missing exception_handler (run init --wire)');
136
+ }
137
+ for (const w of warnings)
138
+ console.warn(`warn: ${w}`);
139
+ for (const e of errors)
140
+ console.error(`error: ${e}`);
141
+ if (errors.length)
142
+ process.exitCode = 1;
143
+ else if (flags.strict && warnings.length)
144
+ process.exitCode = 1;
145
+ else if (!errors.length)
146
+ console.log('check passed' + (warnings.length ? ` (${warnings.length} warnings)` : ''));
147
+ }
148
+ function hasConfigKey(text, leaf) {
149
+ const re = new RegExp(`\\b${escapeRegExp(leaf)}\\s*:`);
150
+ return re.test(text);
151
+ }
152
+ function escapeRegExp(s) {
153
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
154
+ }
155
+ function peerModelToPath(alias, modelsPath, cwd) {
156
+ // #models/user → app/models/user.ts
157
+ const name = alias.replace(/^#models\//, '');
158
+ return join(cwd, modelsPath, `${name}.ts`);
159
+ }
@@ -0,0 +1,2 @@
1
+ import type { ScaffoldFlags, SharedFlags } from '../types.js';
2
+ export declare function runDiff(name: string | undefined, flags: SharedFlags & ScaffoldFlags): Promise<void>;