tailar 0.4.0 → 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.
- package/README.md +5 -2
- package/README.npm.md +5 -2
- package/dist/api/client.d.ts +22 -28
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +111 -236
- package/dist/api/client.js.map +1 -1
- package/dist/api/errors.d.ts +16 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +32 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/auth/flow.js +1 -1
- package/dist/auth/flow.js.map +1 -1
- package/dist/commands/add.d.ts +9 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +63 -113
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +106 -70
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +1 -23
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/playground.d.ts.map +1 -1
- package/dist/commands/playground.js +2 -1
- package/dist/commands/playground.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +9 -4
- package/dist/commands/sync.js.map +1 -1
- package/dist/installers/agent-files.d.ts +26 -0
- package/dist/installers/agent-files.d.ts.map +1 -0
- package/dist/installers/agent-files.js +110 -0
- package/dist/installers/agent-files.js.map +1 -0
- package/dist/installers/dependencies.d.ts +10 -0
- package/dist/installers/dependencies.d.ts.map +1 -1
- package/dist/installers/dependencies.js +52 -8
- package/dist/installers/dependencies.js.map +1 -1
- package/dist/installers/files.d.ts +2 -6
- package/dist/installers/files.d.ts.map +1 -1
- package/dist/installers/files.js +30 -35
- package/dist/installers/files.js.map +1 -1
- package/dist/installers/install-feature.d.ts +25 -1
- package/dist/installers/install-feature.d.ts.map +1 -1
- package/dist/installers/install-feature.js +100 -59
- package/dist/installers/install-feature.js.map +1 -1
- package/dist/installers/managed-blocks.d.ts +26 -0
- package/dist/installers/managed-blocks.d.ts.map +1 -0
- package/dist/installers/managed-blocks.js +47 -0
- package/dist/installers/managed-blocks.js.map +1 -0
- package/dist/installers/post-install.d.ts +12 -0
- package/dist/installers/post-install.d.ts.map +1 -1
- package/dist/installers/post-install.js +19 -0
- package/dist/installers/post-install.js.map +1 -1
- package/dist/installers/setup-guide.d.ts +38 -0
- package/dist/installers/setup-guide.d.ts.map +1 -0
- package/dist/installers/setup-guide.js +206 -0
- package/dist/installers/setup-guide.js.map +1 -0
- package/dist/installers/variant-switch.d.ts +34 -0
- package/dist/installers/variant-switch.d.ts.map +1 -0
- package/dist/installers/variant-switch.js +139 -0
- package/dist/installers/variant-switch.js.map +1 -0
- package/dist/installers/when.d.ts +22 -0
- package/dist/installers/when.d.ts.map +1 -0
- package/dist/installers/when.js +35 -0
- package/dist/installers/when.js.map +1 -0
- package/dist/registry/query.d.ts +6 -1
- package/dist/registry/query.d.ts.map +1 -1
- package/dist/registry/query.js +22 -0
- package/dist/registry/query.js.map +1 -1
- package/dist/types/config.d.ts +2 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/registry.d.ts +65 -6
- package/dist/types/registry.d.ts.map +1 -1
- package/dist/types/wizard.d.ts +34 -8
- package/dist/types/wizard.d.ts.map +1 -1
- package/dist/types/wizard.js +74 -40
- package/dist/types/wizard.js.map +1 -1
- package/dist/utils/clack-wizard.d.ts +3 -1
- package/dist/utils/clack-wizard.d.ts.map +1 -1
- package/dist/utils/clack-wizard.js +205 -107
- package/dist/utils/clack-wizard.js.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +12 -5
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/init-flags.d.ts +43 -8
- package/dist/utils/init-flags.d.ts.map +1 -1
- package/dist/utils/init-flags.js +119 -32
- package/dist/utils/init-flags.js.map +1 -1
- package/dist/utils/local-options.js +1 -1
- package/dist/utils/local-options.js.map +1 -1
- package/dist/utils/next-steps.d.ts +9 -0
- package/dist/utils/next-steps.d.ts.map +1 -0
- package/dist/utils/next-steps.js +15 -0
- package/dist/utils/next-steps.js.map +1 -0
- package/dist/utils/presets.d.ts +34 -0
- package/dist/utils/presets.d.ts.map +1 -0
- package/dist/utils/presets.js +82 -0
- package/dist/utils/presets.js.map +1 -0
- package/dist/utils/recipe-id.d.ts +19 -0
- package/dist/utils/recipe-id.d.ts.map +1 -0
- package/dist/utils/recipe-id.js +72 -0
- package/dist/utils/recipe-id.js.map +1 -0
- package/dist/utils/recipe.d.ts +49 -0
- package/dist/utils/recipe.d.ts.map +1 -0
- package/dist/utils/recipe.js +227 -0
- package/dist/utils/recipe.js.map +1 -0
- package/package.json +7 -1
package/dist/types/wizard.js
CHANGED
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
package/dist/types/wizard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/types/wizard.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
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,
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
message
|
|
53
|
-
options:
|
|
54
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 =
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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, {
|
|
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
|
|
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
|
-
|
|
300
|
-
|
|
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
|