tailar 0.4.1 → 0.5.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 (106) hide show
  1. package/README.md +4 -1
  2. package/README.npm.md +4 -1
  3. package/dist/api/client.d.ts +22 -28
  4. package/dist/api/client.d.ts.map +1 -1
  5. package/dist/api/client.js +111 -236
  6. package/dist/api/client.js.map +1 -1
  7. package/dist/api/errors.d.ts +16 -0
  8. package/dist/api/errors.d.ts.map +1 -0
  9. package/dist/api/errors.js +32 -0
  10. package/dist/api/errors.js.map +1 -0
  11. package/dist/auth/flow.js +1 -1
  12. package/dist/auth/flow.js.map +1 -1
  13. package/dist/commands/add.d.ts +9 -0
  14. package/dist/commands/add.d.ts.map +1 -1
  15. package/dist/commands/add.js +63 -113
  16. package/dist/commands/add.js.map +1 -1
  17. package/dist/commands/init.d.ts.map +1 -1
  18. package/dist/commands/init.js +106 -70
  19. package/dist/commands/init.js.map +1 -1
  20. package/dist/commands/list.d.ts.map +1 -1
  21. package/dist/commands/list.js +1 -23
  22. package/dist/commands/list.js.map +1 -1
  23. package/dist/commands/playground.d.ts.map +1 -1
  24. package/dist/commands/playground.js +2 -1
  25. package/dist/commands/playground.js.map +1 -1
  26. package/dist/commands/sync.d.ts.map +1 -1
  27. package/dist/commands/sync.js +9 -4
  28. package/dist/commands/sync.js.map +1 -1
  29. package/dist/installers/agent-files.d.ts +26 -0
  30. package/dist/installers/agent-files.d.ts.map +1 -0
  31. package/dist/installers/agent-files.js +110 -0
  32. package/dist/installers/agent-files.js.map +1 -0
  33. package/dist/installers/dependencies.d.ts +10 -0
  34. package/dist/installers/dependencies.d.ts.map +1 -1
  35. package/dist/installers/dependencies.js +52 -8
  36. package/dist/installers/dependencies.js.map +1 -1
  37. package/dist/installers/files.d.ts +2 -6
  38. package/dist/installers/files.d.ts.map +1 -1
  39. package/dist/installers/files.js +30 -35
  40. package/dist/installers/files.js.map +1 -1
  41. package/dist/installers/install-feature.d.ts +25 -1
  42. package/dist/installers/install-feature.d.ts.map +1 -1
  43. package/dist/installers/install-feature.js +100 -59
  44. package/dist/installers/install-feature.js.map +1 -1
  45. package/dist/installers/managed-blocks.d.ts +26 -0
  46. package/dist/installers/managed-blocks.d.ts.map +1 -0
  47. package/dist/installers/managed-blocks.js +47 -0
  48. package/dist/installers/managed-blocks.js.map +1 -0
  49. package/dist/installers/post-install.d.ts +12 -0
  50. package/dist/installers/post-install.d.ts.map +1 -1
  51. package/dist/installers/post-install.js +19 -0
  52. package/dist/installers/post-install.js.map +1 -1
  53. package/dist/installers/setup-guide.d.ts +38 -0
  54. package/dist/installers/setup-guide.d.ts.map +1 -0
  55. package/dist/installers/setup-guide.js +206 -0
  56. package/dist/installers/setup-guide.js.map +1 -0
  57. package/dist/installers/variant-switch.d.ts +34 -0
  58. package/dist/installers/variant-switch.d.ts.map +1 -0
  59. package/dist/installers/variant-switch.js +139 -0
  60. package/dist/installers/variant-switch.js.map +1 -0
  61. package/dist/installers/when.d.ts +22 -0
  62. package/dist/installers/when.d.ts.map +1 -0
  63. package/dist/installers/when.js +35 -0
  64. package/dist/installers/when.js.map +1 -0
  65. package/dist/registry/query.d.ts +6 -1
  66. package/dist/registry/query.d.ts.map +1 -1
  67. package/dist/registry/query.js +22 -0
  68. package/dist/registry/query.js.map +1 -1
  69. package/dist/types/config.d.ts +2 -0
  70. package/dist/types/config.d.ts.map +1 -1
  71. package/dist/types/registry.d.ts +65 -6
  72. package/dist/types/registry.d.ts.map +1 -1
  73. package/dist/types/wizard.d.ts +34 -8
  74. package/dist/types/wizard.d.ts.map +1 -1
  75. package/dist/types/wizard.js +74 -40
  76. package/dist/types/wizard.js.map +1 -1
  77. package/dist/utils/clack-wizard.d.ts +3 -1
  78. package/dist/utils/clack-wizard.d.ts.map +1 -1
  79. package/dist/utils/clack-wizard.js +205 -107
  80. package/dist/utils/clack-wizard.js.map +1 -1
  81. package/dist/utils/config.d.ts.map +1 -1
  82. package/dist/utils/config.js +12 -5
  83. package/dist/utils/config.js.map +1 -1
  84. package/dist/utils/init-flags.d.ts +43 -8
  85. package/dist/utils/init-flags.d.ts.map +1 -1
  86. package/dist/utils/init-flags.js +119 -32
  87. package/dist/utils/init-flags.js.map +1 -1
  88. package/dist/utils/local-options.js +1 -1
  89. package/dist/utils/local-options.js.map +1 -1
  90. package/dist/utils/next-steps.d.ts +9 -0
  91. package/dist/utils/next-steps.d.ts.map +1 -0
  92. package/dist/utils/next-steps.js +15 -0
  93. package/dist/utils/next-steps.js.map +1 -0
  94. package/dist/utils/presets.d.ts +34 -0
  95. package/dist/utils/presets.d.ts.map +1 -0
  96. package/dist/utils/presets.js +82 -0
  97. package/dist/utils/presets.js.map +1 -0
  98. package/dist/utils/recipe-id.d.ts +19 -0
  99. package/dist/utils/recipe-id.d.ts.map +1 -0
  100. package/dist/utils/recipe-id.js +72 -0
  101. package/dist/utils/recipe-id.js.map +1 -0
  102. package/dist/utils/recipe.d.ts +49 -0
  103. package/dist/utils/recipe.d.ts.map +1 -0
  104. package/dist/utils/recipe.js +227 -0
  105. package/dist/utils/recipe.js.map +1 -0
  106. package/package.json +2 -1
@@ -3,9 +3,58 @@
3
3
  *
4
4
  * Shared types for the Tailar init guided wizard.
5
5
  */
6
+ /** Install order of modules. */
7
+ export const MODULE_ORDER = [
8
+ 'database',
9
+ 'email',
10
+ 'auth',
11
+ 'payments',
12
+ 'storage',
13
+ 'ai',
14
+ ];
15
+ /** Registry feature that installs each module. */
16
+ export const MODULE_FEATURES = {
17
+ database: 'db',
18
+ email: 'emails',
19
+ auth: 'auth',
20
+ payments: 'payments',
21
+ storage: 'storage',
22
+ ai: 'ai',
23
+ };
6
24
  /**
7
- * Normalize multiselect result: enforce Auth → DB+Email, Payments → Auth+DB+Email;
8
- * deselecting DB/Email drops Auth+Payments; deselecting Auth drops Payments.
25
+ * Direct module dependencies. Kept in step with `featureDependencies` in
26
+ * components/registry.json by test/wizard.test.ts; update both together.
27
+ */
28
+ export const MODULE_REQUIRES = {
29
+ database: [],
30
+ email: [],
31
+ auth: ['database', 'email'],
32
+ payments: ['auth', 'database', 'email'],
33
+ storage: ['auth', 'database', 'email'],
34
+ ai: [],
35
+ };
36
+ export function formatModuleLabel(id) {
37
+ switch (id) {
38
+ case 'database':
39
+ return 'Database';
40
+ case 'email':
41
+ return 'Email';
42
+ case 'auth':
43
+ return 'Authentication';
44
+ case 'payments':
45
+ return 'Payments';
46
+ case 'storage':
47
+ return 'Storage';
48
+ case 'ai':
49
+ return 'AI';
50
+ default:
51
+ return id;
52
+ }
53
+ }
54
+ /**
55
+ * Normalize a module selection: every selected module pulls in what it
56
+ * requires (see MODULE_REQUIRES), so deselecting a dependency cannot leave a
57
+ * module that would not run.
9
58
  */
10
59
  export function normalizeModuleSelection(raw) {
11
60
  const notes = [];
@@ -14,46 +63,31 @@ export function normalizeModuleSelection(raw) {
14
63
  notes.push(s);
15
64
  };
16
65
  const set = new Set(raw);
17
- if (set.has('payments')) {
18
- if (!set.has('auth')) {
19
- set.add('auth');
20
- addNote('Payments requires Authentication — enabled automatically.');
21
- }
22
- if (!set.has('database')) {
23
- set.add('database');
24
- addNote('Payments requires Database — enabled automatically.');
25
- }
26
- if (!set.has('email')) {
27
- set.add('email');
28
- addNote('Payments requires Email — enabled automatically.');
29
- }
30
- }
31
- if (set.has('auth')) {
32
- if (!set.has('database')) {
33
- set.add('database');
34
- addNote('Authentication requires Database — enabled automatically.');
35
- }
36
- if (!set.has('email')) {
37
- set.add('email');
38
- addNote('Authentication requires Email — enabled automatically.');
66
+ // Dependents first, so a note names the module the user actually picked.
67
+ const pending = [...MODULE_ORDER].reverse().filter((m) => set.has(m));
68
+ while (pending.length > 0) {
69
+ const module = pending.shift();
70
+ for (const required of MODULE_REQUIRES[module]) {
71
+ if (set.has(required))
72
+ continue;
73
+ set.add(required);
74
+ pending.push(required);
75
+ addNote(`${formatModuleLabel(module)} requires ${formatModuleLabel(required)} — enabled automatically.`);
39
76
  }
40
77
  }
41
- if (!set.has('database') || !set.has('email')) {
42
- if (set.has('auth')) {
43
- set.delete('auth');
44
- addNote('Authentication requires Database and Email — disabled automatically.');
45
- }
46
- if (set.has('payments')) {
47
- set.delete('payments');
48
- addNote('Payments requires Auth, Database, and Email — disabled automatically.');
49
- }
50
- }
51
- if (!set.has('auth') && set.has('payments')) {
52
- set.delete('payments');
53
- addNote('Payments requires Authentication — disabled automatically.');
54
- }
55
- const order = ['database', 'email', 'auth', 'payments'];
56
- const modules = order.filter((m) => set.has(m));
78
+ const modules = MODULE_ORDER.filter((m) => set.has(m));
57
79
  return { modules, autoNotes: notes };
58
80
  }
81
+ export const ATTACHMENTS_HINT = 'Chat attachments need the Storage module (it also installs Database and Auth).';
82
+ /**
83
+ * Advice about a module selection that is valid but leaves something out.
84
+ * Never changes the selection.
85
+ */
86
+ export function moduleSelectionHints(modules) {
87
+ const hints = [];
88
+ if (modules.includes('ai') && !modules.includes('storage')) {
89
+ hints.push(ATTACHMENTS_HINT);
90
+ }
91
+ return hints;
92
+ }
59
93
  //# sourceMappingURL=wizard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/types/wizard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAmCH;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,GAAmB;IAI1D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE;QAC5B,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAEzB,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAChB,OAAO,CAAC,2DAA2D,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO,CAAC,qDAAqD,CAAC,CAAC;QACjE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjB,OAAO,CAAC,kDAAkD,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC;IACD,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACpB,OAAO,CAAC,2DAA2D,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjB,OAAO,CAAC,wDAAwD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC9C,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACnB,OAAO,CACL,sEAAsE,CACvE,CAAC;QACJ,CAAC;QACD,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACxB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACvB,OAAO,CACL,uEAAuE,CACxE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACvB,OAAO,CAAC,4DAA4D,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,KAAK,GAAmB,CAAC,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC;IACxE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACvC,CAAC"}
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/types/wizard.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,15 @@
1
1
  import { TailarInitWizardState } from '../types/wizard.js';
2
2
  import type { PackageManager } from '../types/index.js';
3
3
  export declare function clackTextInput(message: string, defaultValue?: string): Promise<string | undefined>;
4
- export declare function runInitWizard(): Promise<TailarInitWizardState | null>;
4
+ export declare function runInitWizard(initialProjectName?: string): Promise<TailarInitWizardState | null>;
5
5
  export declare function runGenerationFromWizardConfig(projectPath: string, wizard: TailarInitWizardState, opts?: {
6
6
  verbose?: boolean;
7
7
  /** Skip npm install and post-install shell commands (tests and CI). */
8
8
  skipInstall?: boolean;
9
9
  /** Skip printing env and instruction next-steps (playground writes .env itself). */
10
10
  skipNextSteps?: boolean;
11
+ /** Id of the recipe this app is scaffolded from; recorded in .tailar.json. */
12
+ recipeId?: string;
11
13
  /** Runs after files are in place and before post-install commands. */
12
14
  beforePostInstall?: (projectRoot: string) => Promise<void>;
13
15
  }): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"clack-wizard.d.ts","sourceRoot":"","sources":["../../src/utils/clack-wizard.ts"],"names":[],"mappings":"AAIA,OAAO,EAML,qBAAqB,EAEtB,MAAM,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAkCvE,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B;AAED,wBAAsB,aAAa,IAAI,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAyI3E;AAED,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,GAAE;IACJ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,GACL,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,cAAc,CAAA;CAAE,CAAC,CA6MlE"}
1
+ {"version":3,"file":"clack-wizard.d.ts","sourceRoot":"","sources":["../../src/utils/clack-wizard.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,qBAAqB,EAMtB,MAAM,oBAAoB,CAAC;AAkB5B,OAAO,KAAK,EAAE,cAAc,EAAiB,MAAM,mBAAmB,CAAC;AAwBvE,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAW7B;AAiBD,wBAAsB,aAAa,CACjC,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAuLvC;AAED,wBAAsB,6BAA6B,CACjD,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,qBAAqB,EAC7B,IAAI,GAAE;IACJ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uEAAuE;IACvE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvD,GACL,OAAO,CAAC;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,cAAc,CAAA;CAAE,CAAC,CA2RlE"}
@@ -2,38 +2,32 @@ import * as p from '@clack/prompts';
2
2
  import { copyFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
3
3
  import { dirname, join } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { normalizeModuleSelection, } from '../types/wizard.js';
5
+ import { MODULE_FEATURES, MODULE_ORDER, formatModuleLabel, normalizeModuleSelection, moduleSelectionHints, } from '../types/wizard.js';
6
+ import { loadRegistry } from '../registry/loader.js';
7
+ import { getEntry } from '../registry/query.js';
8
+ import { featureNames, featureProviderChoices, getPresets, missingRequirements, } from './presets.js';
6
9
  import { createInitialConfig, writeConfig, } from './config.js';
7
10
  import { detectPackageManager, } from './package-manager.js';
8
11
  import { detectProjectStructure } from './validation.js';
9
12
  import { installFeature } from '../installers/install-feature.js';
10
13
  import { runInstall } from '../installers/dependencies.js';
11
14
  import { executePostInstallSteps } from '../installers/post-install.js';
15
+ import { SETUP_FILE } from '../installers/setup-guide.js';
12
16
  import { readConfig } from './config.js';
13
17
  // Resolve the bundled favicon.ico relative to this compiled module.
14
18
  // In the dist tree: dist/utils/clack-wizard.js → ../../assets/favicon.ico
15
19
  const _thisFile = fileURLToPath(import.meta.url);
16
20
  const BUNDLED_FAVICON = join(dirname(_thisFile), '../../assets/favicon.ico');
17
- const CORE_MODULES = ['database', 'email', 'auth', 'payments'];
18
- function formatModuleLabel(id) {
19
- switch (id) {
20
- case 'database':
21
- return 'Database';
22
- case 'email':
23
- return 'Email';
24
- case 'auth':
25
- return 'Authentication';
26
- case 'payments':
27
- return 'Payments';
28
- default:
29
- return id;
30
- }
31
- }
32
21
  function formatModuleSummary(modules) {
33
22
  if (modules.length === 0)
34
23
  return 'No modules selected';
35
24
  return modules.map((m) => formatModuleLabel(m)).join(', ');
36
25
  }
26
+ const MODULE_HINTS = {
27
+ auth: 'requires Database, Email',
28
+ payments: 'requires Auth, Database, Email',
29
+ storage: 'requires Auth, Database, Email',
30
+ };
37
31
  export async function clackTextInput(message, defaultValue) {
38
32
  const result = await p.text({
39
33
  message,
@@ -46,41 +40,59 @@ export async function clackTextInput(message, defaultValue) {
46
40
  const value = typeof result === 'string' ? result.trim() : undefined;
47
41
  return value || defaultValue;
48
42
  }
49
- export async function runInitWizard() {
50
- p.intro('Tailar Init — Guided Wizard');
51
- const projectType = await p.select({
52
- message: 'What kind of project are you building?',
53
- options: [
54
- { value: 'saas', label: 'SaaS product' },
55
- { value: 'ai', label: 'AI product (coming soon)' },
56
- ],
43
+ async function selectProvider(registry, featureName, message, fallback, fallbackDefault) {
44
+ const choices = featureProviderChoices(registry, featureName);
45
+ return p.select({
46
+ message,
47
+ options: choices?.values ?? fallback,
48
+ initialValue: choices?.default ?? fallbackDefault,
57
49
  });
58
- if (p.isCancel(projectType)) {
59
- p.cancel('Initialization cancelled.');
60
- return null;
50
+ }
51
+ export async function runInitWizard(initialProjectName) {
52
+ p.intro('Tailar Init — Guided Wizard');
53
+ // The registry is optional here: without it the wizard uses built-in presets and providers.
54
+ let registry;
55
+ try {
56
+ registry = await loadRegistry();
61
57
  }
62
- if (projectType === 'ai') {
63
- p.outro('AI product templates are coming soon. No files were generated.');
64
- return null;
58
+ catch {
59
+ registry = undefined;
60
+ }
61
+ const presets = getPresets(registry);
62
+ const features = featureNames(registry);
63
+ let preset = presets[0];
64
+ for (;;) {
65
+ const choice = await p.select({
66
+ message: 'What kind of project are you building?',
67
+ options: presets.map((candidate) => {
68
+ const missing = missingRequirements(candidate, features);
69
+ return {
70
+ value: candidate.id,
71
+ label: missing.length > 0 ? `${candidate.label} (coming soon)` : candidate.label,
72
+ hint: candidate.description,
73
+ };
74
+ }),
75
+ });
76
+ if (p.isCancel(choice)) {
77
+ p.cancel('Initialization cancelled.');
78
+ return null;
79
+ }
80
+ preset = presets.find((candidate) => candidate.id === choice);
81
+ if (missingRequirements(preset, features).length === 0)
82
+ break;
83
+ p.note(`${preset.label} is coming soon. Pick another project type.`, 'Not available yet');
65
84
  }
66
- const projectName = (await clackTextInput('Project name:', 'my-app')) ?? 'my-app';
85
+ const projectName = initialProjectName ||
86
+ ((await clackTextInput('Project name:', 'my-app')) ?? 'my-app');
87
+ const available = MODULE_ORDER.filter((m) => !features || features.includes(MODULE_FEATURES[m]));
67
88
  const rawSelection = await p.multiselect({
68
89
  message: 'Select modules to include (space to toggle, enter to submit):',
69
- options: [
70
- { value: 'database', label: 'Database' },
71
- { value: 'email', label: 'Email' },
72
- {
73
- value: 'auth',
74
- label: 'Authentication',
75
- hint: 'requires Database, Email',
76
- },
77
- {
78
- value: 'payments',
79
- label: 'Payments',
80
- hint: 'requires Auth, Database, Email (Stripe)',
81
- },
82
- ],
83
- initialValues: [...CORE_MODULES],
90
+ options: available.map((m) => ({
91
+ value: m,
92
+ label: formatModuleLabel(m),
93
+ hint: MODULE_HINTS[m],
94
+ })),
95
+ initialValues: available.filter((m) => preset.modules.includes(m)),
84
96
  required: false,
85
97
  });
86
98
  if (p.isCancel(rawSelection)) {
@@ -91,19 +103,20 @@ export async function runInitWizard() {
91
103
  if (autoNotes.length > 0) {
92
104
  p.note(autoNotes.join('\n'), 'Selection adjusted');
93
105
  }
94
- p.note('Storage and Analytics — coming soon.', 'Roadmap');
106
+ const hints = moduleSelectionHints(modules);
107
+ if (hints.length > 0) {
108
+ p.note(hints.join('\n'), 'Hint');
109
+ }
110
+ p.note('Analytics — coming soon.', 'Roadmap');
95
111
  const providers = {
96
112
  authProvider: 'better-auth',
97
113
  };
98
114
  if (modules.includes('database')) {
99
- const db = await p.select({
100
- message: 'Database provider:',
101
- options: [
102
- { value: 'postgres', label: 'PostgreSQL' },
103
- { value: 'mysql', label: 'MySQL' },
104
- { value: 'mongodb', label: 'MongoDB' },
105
- ],
106
- });
115
+ const db = await selectProvider(registry, MODULE_FEATURES.database, 'Database provider:', [
116
+ { value: 'postgres', label: 'PostgreSQL' },
117
+ { value: 'mysql', label: 'MySQL' },
118
+ { value: 'mongodb', label: 'MongoDB' },
119
+ ], 'postgres');
107
120
  if (p.isCancel(db)) {
108
121
  p.cancel('Initialization cancelled.');
109
122
  return null;
@@ -111,14 +124,11 @@ export async function runInitWizard() {
111
124
  providers.database = db;
112
125
  }
113
126
  if (modules.includes('email')) {
114
- const email = await p.select({
115
- message: 'Email provider:',
116
- options: [
117
- { value: 'resend', label: 'Resend' },
118
- { value: 'postmark', label: 'Postmark' },
119
- { value: 'sendgrid', label: 'SendGrid' },
120
- ],
121
- });
127
+ const email = await selectProvider(registry, MODULE_FEATURES.email, 'Email provider:', [
128
+ { value: 'resend', label: 'Resend' },
129
+ { value: 'postmark', label: 'Postmark' },
130
+ { value: 'sendgrid', label: 'SendGrid' },
131
+ ], 'resend');
122
132
  if (p.isCancel(email)) {
123
133
  p.cancel('Initialization cancelled.');
124
134
  return null;
@@ -140,17 +150,32 @@ export async function runInitWizard() {
140
150
  providers.authStructure = structure;
141
151
  }
142
152
  if (modules.includes('payments')) {
143
- providers.payments = 'stripe';
153
+ const payments = await selectProvider(registry, MODULE_FEATURES.payments, 'Billing provider:', [{ value: 'stripe', label: 'Stripe' }], 'stripe');
154
+ if (p.isCancel(payments)) {
155
+ p.cancel('Initialization cancelled.');
156
+ return null;
157
+ }
158
+ providers.payments = payments;
159
+ }
160
+ if (modules.includes('storage')) {
161
+ const storage = await selectProvider(registry, MODULE_FEATURES.storage, 'Storage provider:', [{ value: 'r2', label: 'Cloudflare R2' }], 'r2');
162
+ if (p.isCancel(storage)) {
163
+ p.cancel('Initialization cancelled.');
164
+ return null;
165
+ }
166
+ providers.storage = storage;
144
167
  }
145
168
  return {
146
- projectType: 'saas',
169
+ preset: preset.id,
147
170
  projectName,
148
171
  modules,
149
172
  providers,
173
+ items: preset.items ?? [],
174
+ template: preset.template,
150
175
  };
151
176
  }
152
177
  export async function runGenerationFromWizardConfig(projectPath, wizard, opts = {}) {
153
- p.intro('Scaffolding Tailar SaaS project...');
178
+ p.intro(`Scaffolding Tailar project (${wizard.preset})...`);
154
179
  const projectName = wizard.projectName || 'my-app';
155
180
  const actualProjectPath = join(projectPath, projectName);
156
181
  // ── Create the project directory ──────────────────────────────────────────
@@ -171,10 +196,11 @@ export async function runGenerationFromWizardConfig(projectPath, wizard, opts =
171
196
  const structure = { hasSrcFolder: false, hasAppFolder: true };
172
197
  const packageManager = detectPackageManager(actualProjectPath);
173
198
  const meta = {
174
- projectType: wizard.projectType,
199
+ preset: wizard.preset,
175
200
  modules: wizard.modules,
176
201
  providers: wizard.providers,
177
202
  packageManager,
203
+ ...(opts.recipeId ? { recipe: opts.recipeId } : {}),
178
204
  };
179
205
  const baseConfig = createInitialConfig({
180
206
  name: projectName,
@@ -189,8 +215,11 @@ export async function runGenerationFromWizardConfig(projectPath, wizard, opts =
189
215
  // ── Install base scaffold from registry ───────────────────────────────────
190
216
  const allPostInstall = [];
191
217
  let needsInstall = false;
218
+ // @clack/prompts 0.7 registers five process listeners every time p.spinner() is
219
+ // created and never removes them, so one shared spinner avoids the
220
+ // MaxListenersExceededWarning that one spinner per step caused.
221
+ const sp = p.spinner();
192
222
  const runInstallStep = async (label, fn) => {
193
- const sp = p.spinner();
194
223
  sp.start(label);
195
224
  try {
196
225
  const r = await fn();
@@ -218,50 +247,93 @@ export async function runGenerationFromWizardConfig(projectPath, wizard, opts =
218
247
  }
219
248
  await runInstallStep('Installing Shared UI (Tailar base)…', () => installFeature(actualProjectPath, 'shared', { yes: true }));
220
249
  await runInstallStep('Installing i18n (next-intl)…', () => installFeature(actualProjectPath, 'i18n', { yes: true }));
221
- if (wizard.modules.includes('database') && wizard.providers.database) {
222
- await runInstallStep('Installing Database (Prisma)…', () => installFeature(actualProjectPath, 'db', {
223
- yes: true,
250
+ // Features present so far; a template is applied only when its dependencies are here.
251
+ const installed = new Set(['base', 'shared', 'i18n']);
252
+ const moduleSteps = [
253
+ {
254
+ module: 'database',
255
+ label: 'Installing Database (Prisma)…',
256
+ when: Boolean(wizard.providers.database),
224
257
  presetAnswers: { dbProvider: wizard.providers.database },
225
- }));
226
- }
227
- if (wizard.modules.includes('email') && wizard.providers.email) {
228
- await runInstallStep('Installing Email…', () => installFeature(actualProjectPath, 'emails', {
229
- yes: true,
258
+ },
259
+ {
260
+ module: 'email',
261
+ label: 'Installing Email…',
262
+ when: Boolean(wizard.providers.email),
230
263
  presetAnswers: { emailProvider: wizard.providers.email },
231
- }));
232
- }
233
- if (wizard.modules.includes('auth') && wizard.providers.authStructure) {
234
- await runInstallStep('Installing Authentication (Better Auth)…', () => installFeature(actualProjectPath, 'auth', {
235
- yes: true,
264
+ },
265
+ {
266
+ module: 'auth',
267
+ label: 'Installing Authentication (Better Auth)…',
268
+ when: Boolean(wizard.providers.authStructure),
236
269
  presetAnswers: {
237
270
  organizationBased: wizard.providers.authStructure === 'org-based',
238
271
  },
272
+ },
273
+ {
274
+ module: 'payments',
275
+ label: 'Installing Payments…',
276
+ when: true,
277
+ presetAnswers: wizard.providers.payments
278
+ ? { billingProvider: wizard.providers.payments }
279
+ : undefined,
280
+ },
281
+ {
282
+ module: 'storage',
283
+ label: 'Installing Storage…',
284
+ when: true,
285
+ presetAnswers: wizard.providers.storage
286
+ ? { storageProvider: wizard.providers.storage }
287
+ : undefined,
288
+ },
289
+ {
290
+ module: 'ai',
291
+ label: 'Installing AI…',
292
+ when: true,
293
+ },
294
+ ];
295
+ // Modules selected for this run, so `when` can see features installed later.
296
+ const planned = moduleSteps
297
+ .filter((step) => wizard.modules.includes(step.module) && step.when)
298
+ .map((step) => MODULE_FEATURES[step.module]);
299
+ for (const step of moduleSteps) {
300
+ if (!wizard.modules.includes(step.module) || !step.when)
301
+ continue;
302
+ const feature = MODULE_FEATURES[step.module];
303
+ await runInstallStep(step.label, () => installFeature(actualProjectPath, feature, {
304
+ yes: true,
305
+ plannedFeatures: planned,
306
+ ...(step.presetAnswers ? { presetAnswers: step.presetAnswers } : {}),
239
307
  }));
308
+ installed.add(feature);
240
309
  }
241
- if (wizard.modules.includes('payments')) {
242
- await runInstallStep('Installing Payments (Stripe)…', () => installFeature(actualProjectPath, 'payments', { yes: true }));
243
- }
244
- let sidebarVariant = 'default';
245
- if (wizard.modules.includes('auth') && wizard.providers.authStructure) {
246
- sidebarVariant =
247
- wizard.providers.authStructure === 'org-based' ? 'org-based' : 'user-based';
248
- }
249
- await runInstallStep('Installing App Sidebar…', () => installFeature(actualProjectPath, 'app-sidebar', {
250
- yes: true,
251
- variant: sidebarVariant,
252
- }));
253
- if (wizard.modules.includes('auth')) {
254
- await runInstallStep('Applying SaaS root page template…', () => installFeature(actualProjectPath, 'saas', {
310
+ for (const item of wizard.items) {
311
+ // The sidebar follows the auth structure; other items have one variant.
312
+ const variant = item === 'app-sidebar'
313
+ ? wizard.modules.includes('auth') && wizard.providers.authStructure
314
+ ? wizard.providers.authStructure
315
+ : 'default'
316
+ : undefined;
317
+ await runInstallStep(`Installing ${item}…`, () => installFeature(actualProjectPath, item, {
255
318
  yes: true,
319
+ plannedFeatures: planned,
320
+ ...(variant ? { variant } : {}),
256
321
  }));
257
322
  }
323
+ if (wizard.template) {
324
+ const registry = await loadRegistry();
325
+ const template = getEntry(registry, wizard.template);
326
+ const missing = (template?.featureDependencies ?? []).filter((dependency) => !installed.has(dependency));
327
+ if (template && missing.length === 0) {
328
+ await runInstallStep(`Applying ${template.label} root page template…`, () => installFeature(actualProjectPath, wizard.template, { yes: true }));
329
+ }
330
+ }
258
331
  if (needsInstall && !opts.skipInstall) {
259
332
  if (opts.verbose) {
260
333
  console.log('Installing npm dependencies…');
261
334
  await runInstall(actualProjectPath, packageManager, { silent: false });
262
335
  }
263
336
  else {
264
- const sp = p.spinner();
265
337
  sp.start('Installing npm dependencies…');
266
338
  await runInstall(actualProjectPath, packageManager, { silent: true });
267
339
  sp.stop('Dependencies installed.');
@@ -270,6 +342,13 @@ export async function runGenerationFromWizardConfig(projectPath, wizard, opts =
270
342
  if (opts.beforePostInstall) {
271
343
  await opts.beforePostInstall(actualProjectPath);
272
344
  }
345
+ const pmRunDev = packageManager === 'yarn'
346
+ ? 'yarn dev'
347
+ : packageManager === 'pnpm'
348
+ ? 'pnpm dev'
349
+ : packageManager === 'bun'
350
+ ? 'bun dev'
351
+ : 'npm run dev';
273
352
  const config = await readConfig(actualProjectPath);
274
353
  if (config && allPostInstall.length > 0) {
275
354
  const detectedStructure = detectProjectStructure(actualProjectPath);
@@ -279,25 +358,44 @@ export async function runGenerationFromWizardConfig(projectPath, wizard, opts =
279
358
  structure: detectedStructure,
280
359
  config,
281
360
  };
282
- await executePostInstallSteps(actualProjectPath, allPostInstall, ctx, { skipCommands: opts.skipInstall, skipNextSteps: opts.skipNextSteps });
361
+ await executePostInstallSteps(actualProjectPath, allPostInstall, ctx, {
362
+ skipCommands: opts.skipInstall,
363
+ skipNextSteps: opts.skipNextSteps,
364
+ setupGuide: {
365
+ projectName,
366
+ preset: wizard.preset,
367
+ hasDatabase: wizard.modules.includes('database'),
368
+ runDevCommand: pmRunDev,
369
+ print: opts.verbose,
370
+ },
371
+ });
283
372
  }
373
+ const hasGuide = !opts.skipNextSteps && existsSync(join(actualProjectPath, SETUP_FILE));
284
374
  p.outro([
285
- 'Tailar SaaS project created successfully.',
375
+ 'Tailar project created successfully.',
286
376
  '',
287
377
  `Project directory: ${projectName}`,
378
+ `Preset: ${wizard.preset}`,
379
+ ...(opts.recipeId ? [`Recipe: ${opts.recipeId}`] : []),
288
380
  `Modules: ${formatModuleSummary(wizard.modules)}`,
289
381
  ].join('\n'));
290
- const pmRunDev = packageManager === 'yarn'
291
- ? 'yarn dev'
292
- : packageManager === 'pnpm'
293
- ? 'pnpm dev'
294
- : packageManager === 'bun'
295
- ? 'bun dev'
296
- : 'npm run dev';
297
382
  console.log('');
298
383
  console.log('Next steps:');
299
- console.log(` • cd ${projectName}`);
300
- console.log(` • ${pmRunDev}`);
384
+ if (hasGuide) {
385
+ const steps = [
386
+ `cd ${projectName}`,
387
+ `Fill in .env (the variables and where to get them are in ${SETUP_FILE})`,
388
+ ...(wizard.modules.includes('database') ? ['npx prisma db push'] : []),
389
+ pmRunDev,
390
+ ];
391
+ steps.forEach((step, index) => console.log(` ${index + 1}. ${step}`));
392
+ console.log('');
393
+ console.log(`Setup guide for the modules you picked: ${SETUP_FILE}`);
394
+ }
395
+ else {
396
+ console.log(` • cd ${projectName}`);
397
+ console.log(` • ${pmRunDev}`);
398
+ }
301
399
  return { projectRoot: actualProjectPath, packageManager };
302
400
  }
303
401
  //# sourceMappingURL=clack-wizard.js.map