directus-template-cli 0.7.0-beta.1 → 0.7.0-beta.10

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/bin/dev.js +6 -0
  2. package/bin/run.js +5 -0
  3. package/dist/commands/apply.d.ts +17 -17
  4. package/dist/commands/apply.js +165 -173
  5. package/dist/commands/base.d.ts +15 -0
  6. package/dist/commands/base.js +45 -0
  7. package/dist/commands/extract.d.ts +16 -7
  8. package/dist/commands/extract.js +81 -73
  9. package/dist/commands/init.d.ts +21 -15
  10. package/dist/commands/init.js +204 -155
  11. package/dist/flags/common.d.ts +8 -7
  12. package/dist/flags/common.js +13 -11
  13. package/dist/index.js +1 -5
  14. package/dist/lib/constants.d.ts +7 -5
  15. package/dist/lib/constants.js +17 -14
  16. package/dist/lib/extract/extract-access.js +11 -15
  17. package/dist/lib/extract/extract-assets.js +20 -25
  18. package/dist/lib/extract/extract-collections.js +12 -16
  19. package/dist/lib/extract/extract-content.js +14 -19
  20. package/dist/lib/extract/extract-dashboards.js +22 -28
  21. package/dist/lib/extract/extract-extensions.js +12 -16
  22. package/dist/lib/extract/extract-fields.js +13 -17
  23. package/dist/lib/extract/extract-files.js +15 -19
  24. package/dist/lib/extract/extract-flows.js +22 -28
  25. package/dist/lib/extract/extract-folders.js +15 -19
  26. package/dist/lib/extract/extract-permissions.js +12 -16
  27. package/dist/lib/extract/extract-policies.js +12 -16
  28. package/dist/lib/extract/extract-presets.js +12 -16
  29. package/dist/lib/extract/extract-relations.js +14 -18
  30. package/dist/lib/extract/extract-roles.js +15 -19
  31. package/dist/lib/extract/extract-schema.js +17 -21
  32. package/dist/lib/extract/extract-settings.js +12 -16
  33. package/dist/lib/extract/extract-translations.js +12 -16
  34. package/dist/lib/extract/extract-users.js +15 -19
  35. package/dist/lib/extract/index.js +47 -51
  36. package/dist/lib/init/config.d.ts +1 -1
  37. package/dist/lib/init/config.js +4 -7
  38. package/dist/lib/init/index.d.ts +10 -9
  39. package/dist/lib/init/index.js +129 -85
  40. package/dist/lib/init/types.js +1 -2
  41. package/dist/lib/load/apply-flags.js +17 -23
  42. package/dist/lib/load/index.d.ts +1 -12
  43. package/dist/lib/load/index.js +40 -44
  44. package/dist/lib/load/load-access.js +15 -20
  45. package/dist/lib/load/load-collections.js +27 -32
  46. package/dist/lib/load/load-dashboards.js +19 -25
  47. package/dist/lib/load/load-data.js +43 -49
  48. package/dist/lib/load/load-extensions.js +30 -38
  49. package/dist/lib/load/load-files.js +20 -24
  50. package/dist/lib/load/load-flows.js +23 -29
  51. package/dist/lib/load/load-folders.js +16 -20
  52. package/dist/lib/load/load-permissions.js +13 -17
  53. package/dist/lib/load/load-policies.js +14 -18
  54. package/dist/lib/load/load-presets.js +14 -18
  55. package/dist/lib/load/load-relations.d.ts +2 -0
  56. package/dist/lib/load/load-relations.js +16 -18
  57. package/dist/lib/load/load-roles.js +19 -23
  58. package/dist/lib/load/load-settings.js +18 -21
  59. package/dist/lib/load/load-translations.js +14 -18
  60. package/dist/lib/load/load-users.js +21 -25
  61. package/dist/lib/load/update-required-fields.js +13 -17
  62. package/dist/lib/sdk.d.ts +1 -2
  63. package/dist/lib/sdk.js +27 -27
  64. package/dist/lib/types/extension.js +1 -2
  65. package/dist/lib/types.d.ts +18 -0
  66. package/dist/lib/types.js +1 -0
  67. package/dist/lib/utils/animated-bunny.d.ts +2 -0
  68. package/dist/lib/utils/animated-bunny.js +62 -0
  69. package/dist/lib/utils/auth.d.ts +8 -6
  70. package/dist/lib/utils/auth.js +48 -39
  71. package/dist/lib/utils/catch-error.js +8 -11
  72. package/dist/lib/utils/check-template.js +4 -8
  73. package/dist/lib/utils/chunk-array.js +1 -5
  74. package/dist/lib/utils/ensure-dir.js +7 -12
  75. package/dist/lib/utils/filter-fields.js +1 -4
  76. package/dist/lib/utils/get-role-ids.d.ts +1 -1
  77. package/dist/lib/utils/get-role-ids.js +7 -12
  78. package/dist/lib/utils/get-template.js +33 -37
  79. package/dist/lib/utils/logger.js +11 -13
  80. package/dist/lib/utils/open-url.js +5 -8
  81. package/dist/lib/utils/parse-github-url.d.ts +10 -5
  82. package/dist/lib/utils/parse-github-url.js +80 -45
  83. package/dist/lib/utils/path.js +6 -10
  84. package/dist/lib/utils/protected-domains.js +1 -4
  85. package/dist/lib/utils/read-file.js +8 -12
  86. package/dist/lib/utils/read-templates.js +9 -15
  87. package/dist/lib/utils/sanitize-flags.d.ts +3 -0
  88. package/dist/lib/utils/sanitize-flags.js +4 -0
  89. package/dist/lib/utils/system-fields.js +19 -22
  90. package/dist/lib/utils/template-config.d.ts +16 -0
  91. package/dist/lib/utils/template-config.js +34 -0
  92. package/dist/lib/utils/template-defaults.d.ts +1 -1
  93. package/dist/lib/utils/template-defaults.js +5 -14
  94. package/dist/lib/utils/transform-github-url.js +1 -5
  95. package/dist/lib/utils/validate-url.js +3 -6
  96. package/dist/lib/utils/wait.js +1 -5
  97. package/dist/lib/utils/write-to-file.js +8 -11
  98. package/dist/services/docker.js +82 -29
  99. package/dist/services/github.d.ts +1 -1
  100. package/dist/services/github.js +53 -22
  101. package/dist/services/posthog.d.ts +37 -0
  102. package/dist/services/posthog.js +104 -0
  103. package/oclif.manifest.json +32 -13
  104. package/package.json +41 -31
  105. package/bin/dev +0 -17
  106. package/bin/run +0 -5
@@ -1,18 +1,31 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const core_1 = require("@oclif/core");
5
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
- const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
7
- const node_path_1 = tslib_1.__importDefault(require("node:path"));
8
- const slugify_1 = tslib_1.__importDefault(require("slugify"));
9
- const customFlags = tslib_1.__importStar(require("../flags/common"));
10
- const constants_1 = require("../lib/constants");
11
- const extract_1 = tslib_1.__importDefault(require("../lib/extract/"));
12
- const auth_1 = require("../lib/utils/auth");
13
- const catch_error_1 = tslib_1.__importDefault(require("../lib/utils/catch-error"));
14
- const template_defaults_1 = require("../lib/utils/template-defaults");
15
- class ExtractCommand extends core_1.Command {
1
+ import { text, password, select, intro, log } from '@clack/prompts';
2
+ import { Command, ux } from '@oclif/core';
3
+ import slugify from '@sindresorhus/slugify';
4
+ import chalk from 'chalk';
5
+ import fs from 'node:fs';
6
+ import path from 'pathe';
7
+ import * as customFlags from '../flags/common.js';
8
+ import { DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR, BSL_LICENSE_TEXT } from '../lib/constants.js';
9
+ import { animatedBunny } from '../lib/utils/animated-bunny.js';
10
+ import extract from '../lib/extract/index.js';
11
+ import { getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags } from '../lib/utils/auth.js';
12
+ import catchError from '../lib/utils/catch-error.js';
13
+ import { generatePackageJsonContent, generateReadmeContent, } from '../lib/utils/template-defaults.js';
14
+ export default class ExtractCommand extends Command {
15
+ static description = 'Extract a template from a Directus instance.';
16
+ static examples = [
17
+ '$ directus-template-cli extract',
18
+ '$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"',
19
+ ];
20
+ static flags = {
21
+ directusToken: customFlags.directusToken,
22
+ directusUrl: customFlags.directusUrl,
23
+ programmatic: customFlags.programmatic,
24
+ templateLocation: customFlags.templateLocation,
25
+ templateName: customFlags.templateName,
26
+ userEmail: customFlags.userEmail,
27
+ userPassword: customFlags.userPassword,
28
+ };
16
29
  /**
17
30
  * Main run method for the ExtractCommand
18
31
  * @returns {Promise<void>} - Returns nothing
@@ -31,29 +44,30 @@ class ExtractCommand extends core_1.Command {
31
44
  */
32
45
  async extractTemplate(templateName, directory, flags) {
33
46
  try {
34
- if (!node_fs_1.default.existsSync(directory)) {
35
- node_fs_1.default.mkdirSync(directory, { recursive: true });
47
+ if (!fs.existsSync(directory)) {
48
+ fs.mkdirSync(directory, { recursive: true });
36
49
  }
37
- const packageJSONContent = (0, template_defaults_1.generatePackageJsonContent)(templateName);
38
- const readmeContent = (0, template_defaults_1.generateReadmeContent)(templateName);
39
- const packageJSONPath = node_path_1.default.join(directory, 'package.json');
40
- const readmePath = node_path_1.default.join(directory, 'README.md');
41
- node_fs_1.default.writeFileSync(packageJSONPath, packageJSONContent);
42
- node_fs_1.default.writeFileSync(readmePath, readmeContent);
50
+ const packageJSONContent = generatePackageJsonContent(templateName);
51
+ const readmeContent = generateReadmeContent(templateName);
52
+ const packageJSONPath = path.join(directory, 'package.json');
53
+ const readmePath = path.join(directory, 'README.md');
54
+ fs.writeFileSync(packageJSONPath, packageJSONContent);
55
+ fs.writeFileSync(readmePath, readmeContent);
43
56
  }
44
57
  catch (error) {
45
- (0, catch_error_1.default)(error, {
58
+ catchError(error, {
46
59
  context: { function: 'extractTemplate' },
47
60
  fatal: true,
48
61
  logToFile: true,
49
62
  });
50
63
  }
51
- core_1.ux.log(constants_1.SEPARATOR);
52
- core_1.ux.action.start(`Extracting template - ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, templateName)} from ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, flags.directusUrl)} to ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
53
- await (0, extract_1.default)(directory);
54
- core_1.ux.action.stop();
55
- core_1.ux.log(constants_1.SEPARATOR);
56
- core_1.ux.log('Template extracted successfully.');
64
+ ux.stdout(SEPARATOR);
65
+ ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`);
66
+ await extract(directory);
67
+ ux.action.stop();
68
+ log.warn(BSL_LICENSE_TEXT);
69
+ ux.stdout(SEPARATOR);
70
+ ux.stdout('Template extracted successfully.');
57
71
  this.exit(0);
58
72
  }
59
73
  /**
@@ -62,37 +76,46 @@ class ExtractCommand extends core_1.Command {
62
76
  * @returns {Promise<void>} - Returns nothing
63
77
  */
64
78
  async runInteractive(flags) {
65
- core_1.ux.styledHeader(core_1.ux.colorize(constants_1.DIRECTUS_PURPLE, 'Directus Template CLI - Extract'));
66
- const templateName = await core_1.ux.prompt('What is the name of the template you would like to extract?');
67
- const directory = await core_1.ux.prompt("What directory would you like to extract the template to? If it doesn't exist, it will be created.", { default: `templates/${(0, slugify_1.default)(templateName, { lower: true, strict: true })}` });
68
- core_1.ux.log(`You selected ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
69
- core_1.ux.log(constants_1.SEPARATOR);
79
+ await animatedBunny('Let\'s extract a template!');
80
+ intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Extract Template`);
81
+ const templateName = await text({
82
+ message: 'What is the name of the template you would like to extract?',
83
+ placeholder: 'My Template',
84
+ });
85
+ const directory = await text({
86
+ placeholder: `templates/${slugify(templateName)}`,
87
+ defaultValue: `templates/${slugify(templateName)}`,
88
+ message: "What directory would you like to extract the template to? If it doesn't exist, it will be created.",
89
+ });
90
+ ux.stdout(`You selected ${ux.colorize(DIRECTUS_PINK, directory)}`);
91
+ ux.stdout(SEPARATOR);
70
92
  // Get Directus URL
71
- const directusUrl = await (0, auth_1.getDirectusUrl)();
93
+ const directusUrl = await getDirectusUrl();
72
94
  flags.directusUrl = directusUrl;
73
95
  // Prompt for login method
74
- const loginMethod = await inquirer_1.default.prompt([
75
- {
76
- choices: [
77
- { name: 'Directus Access Token', value: 'token' },
78
- { name: 'Email and Password', value: 'email' },
79
- ],
80
- default: 'token',
81
- message: 'How do you want to log in?',
82
- name: 'loginMethod',
83
- type: 'list',
84
- },
85
- ]);
86
- if (loginMethod.loginMethod === 'token') {
87
- const directusToken = await (0, auth_1.getDirectusToken)(directusUrl);
96
+ const loginMethod = await select({
97
+ options: [
98
+ { label: 'Directus Access Token', value: 'token' },
99
+ { label: 'Email and Password', value: 'email' },
100
+ ],
101
+ message: 'How do you want to log in?',
102
+ });
103
+ if (loginMethod === 'token') {
104
+ const directusToken = await getDirectusToken(directusUrl);
88
105
  flags.directusToken = directusToken;
89
106
  }
90
107
  else {
91
- flags.userEmail = await core_1.ux.prompt('What is your email?');
92
- flags.userPassword = await core_1.ux.prompt('What is your password?', { type: 'hide' });
108
+ const email = await text({
109
+ message: 'What is your email?',
110
+ });
111
+ flags.userEmail = email;
112
+ const userPassword = await password({
113
+ message: 'What is our password?',
114
+ });
115
+ flags.userPassword = userPassword;
93
116
  }
94
- core_1.ux.log(constants_1.SEPARATOR);
95
- await (0, auth_1.initializeDirectusApi)(flags);
117
+ ux.stdout(SEPARATOR);
118
+ await initializeDirectusApi(flags);
96
119
  await this.extractTemplate(templateName, directory, flags);
97
120
  }
98
121
  /**
@@ -103,7 +126,7 @@ class ExtractCommand extends core_1.Command {
103
126
  async runProgrammatic(flags) {
104
127
  this.validateProgrammaticFlags(flags);
105
128
  const { templateLocation, templateName } = flags;
106
- await (0, auth_1.initializeDirectusApi)(flags);
129
+ await initializeDirectusApi(flags);
107
130
  await this.extractTemplate(templateName, templateLocation, flags);
108
131
  }
109
132
  /**
@@ -113,27 +136,12 @@ class ExtractCommand extends core_1.Command {
113
136
  * @returns {void}
114
137
  */
115
138
  validateProgrammaticFlags(flags) {
116
- (0, auth_1.validateAuthFlags)(flags);
139
+ validateAuthFlags(flags);
117
140
  if (!flags.templateLocation) {
118
- core_1.ux.error('Template location is required for programmatic mode.');
141
+ ux.error('Template location is required for programmatic mode.');
119
142
  }
120
143
  if (!flags.templateName) {
121
- core_1.ux.error('Template name is required for programmatic mode.');
144
+ ux.error('Template name is required for programmatic mode.');
122
145
  }
123
146
  }
124
147
  }
125
- ExtractCommand.description = 'Extract a template from a Directus instance.';
126
- ExtractCommand.examples = [
127
- '$ directus-template-cli extract',
128
- '$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"',
129
- ];
130
- ExtractCommand.flags = {
131
- directusToken: customFlags.directusToken,
132
- directusUrl: customFlags.directusUrl,
133
- programmatic: customFlags.programmatic,
134
- templateLocation: customFlags.templateLocation,
135
- templateName: customFlags.templateName,
136
- userEmail: customFlags.userEmail,
137
- userPassword: customFlags.userPassword,
138
- };
139
- exports.default = ExtractCommand;
@@ -1,19 +1,30 @@
1
- import { Command } from '@oclif/core';
2
- export default class InitCommand extends Command {
1
+ import { BaseCommand } from './base.js';
2
+ export interface InitFlags {
3
+ frontend?: string;
4
+ gitInit?: boolean;
5
+ installDeps?: boolean;
6
+ overrideDir?: boolean;
7
+ template?: string;
8
+ disableTelemetry?: boolean;
9
+ }
10
+ export interface InitArgs {
11
+ directory: string;
12
+ }
13
+ export default class InitCommand extends BaseCommand {
3
14
  static args: {
4
- directory: import("@oclif/core/lib/interfaces").Arg<string, {
15
+ directory: import("@oclif/core/interfaces").Arg<string, {
5
16
  exists?: boolean;
6
17
  }>;
7
18
  };
8
19
  static description: string;
9
20
  static examples: string[];
10
21
  static flags: {
11
- frontend: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
12
- gitInit: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
- installDeps: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
- overrideDir: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
- programmatic: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
16
- template: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ frontend: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
23
+ gitInit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ installDeps: import("@oclif/core/interfaces").BooleanFlag<boolean>;
25
+ overrideDir: import("@oclif/core/interfaces").BooleanFlag<boolean>;
26
+ template: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
27
+ disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
28
  };
18
29
  private targetDir;
19
30
  /**
@@ -24,13 +35,8 @@ export default class InitCommand extends Command {
24
35
  /**
25
36
  * Interactive mode: prompts the user for each piece of info, with added template checks.
26
37
  * @param flags - The flags passed to the command.
38
+ * @param args - The arguments passed to the command.
27
39
  * @returns void
28
40
  */
29
41
  private runInteractive;
30
- /**
31
- * Programmatic mode: relies on flags only, with checks for template existence and valid frontend.
32
- * @param flags - The flags passed to the command.
33
- * @returns void
34
- */
35
- private runProgrammatic;
36
42
  }
@@ -1,16 +1,60 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- const core_1 = require("@oclif/core");
5
- const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
6
- const node_path_1 = tslib_1.__importDefault(require("node:path"));
7
- const init_1 = require("../lib/init");
8
- const github_1 = require("../services/github");
9
- class InitCommand extends core_1.Command {
10
- constructor() {
11
- super(...arguments);
12
- this.targetDir = '.';
13
- }
1
+ import { confirm, intro, select, text, isCancel, cancel } from '@clack/prompts';
2
+ import { Args, Flags, ux } from '@oclif/core';
3
+ import chalk from 'chalk';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
6
+ import path from 'pathe';
7
+ import { disableTelemetry } from '../flags/common.js';
8
+ import { DIRECTUS_PURPLE } from '../lib/constants.js';
9
+ import { init } from '../lib/init/index.js';
10
+ import { animatedBunny } from '../lib/utils/animated-bunny.js';
11
+ import { createGitHub } from '../services/github.js';
12
+ import { readTemplateConfig } from '../lib/utils/template-config.js';
13
+ import { createGigetString, parseGitHubUrl } from '../lib/utils/parse-github-url.js';
14
+ import { downloadTemplate } from 'giget';
15
+ import { BaseCommand } from './base.js';
16
+ import { track, shutdown } from '../services/posthog.js';
17
+ export default class InitCommand extends BaseCommand {
18
+ static args = {
19
+ directory: Args.directory({
20
+ default: '.',
21
+ description: 'Directory to create the project in',
22
+ required: false,
23
+ }),
24
+ };
25
+ static description = 'Initialize a new Directus + Frontend monorepo using official or community starters.';
26
+ static examples = [
27
+ '$ directus-template-cli init',
28
+ '$ directus-template-cli init my-project',
29
+ '$ directus-template-cli init --frontend=nextjs --template=simple-cms',
30
+ '$ directus-template-cli init my-project --frontend=nextjs --template=simple-cms',
31
+ ];
32
+ static flags = {
33
+ frontend: Flags.string({
34
+ description: 'Frontend framework to use (e.g., nextjs, nuxt, astro)',
35
+ }),
36
+ gitInit: Flags.boolean({
37
+ aliases: ['git-init'],
38
+ allowNo: true,
39
+ default: true,
40
+ description: 'Initialize a new Git repository',
41
+ }),
42
+ installDeps: Flags.boolean({
43
+ aliases: ['install-deps'],
44
+ allowNo: true,
45
+ default: true,
46
+ description: 'Install dependencies automatically',
47
+ }),
48
+ overrideDir: Flags.boolean({
49
+ default: false,
50
+ description: 'Override the default directory',
51
+ }),
52
+ template: Flags.string({
53
+ description: 'Template name (e.g., simple-cms) or GitHub URL (e.g., https://github.com/directus-labs/starters/tree/main/simple-cms)',
54
+ }),
55
+ disableTelemetry: disableTelemetry,
56
+ };
57
+ targetDir = '.';
14
58
  /**
15
59
  * Entrypoint for the command.
16
60
  * @returns Promise that resolves when the command is complete.
@@ -18,175 +62,180 @@ class InitCommand extends core_1.Command {
18
62
  async run() {
19
63
  const { args, flags } = await this.parse(InitCommand);
20
64
  const typedFlags = flags;
65
+ const typedArgs = args;
21
66
  // Set the target directory and create it if it doesn't exist
22
- this.targetDir = node_path_1.default.resolve(args.directory);
23
- // if (!fs.existsSync(this.targetDir)) {
24
- // fs.mkdirSync(this.targetDir, {recursive: true})
25
- // }
26
- await (typedFlags.programmatic ? this.runProgrammatic(typedFlags) : this.runInteractive(typedFlags));
67
+ this.targetDir = path.resolve(args.directory);
68
+ await this.runInteractive(typedFlags, typedArgs);
27
69
  }
28
70
  /**
29
71
  * Interactive mode: prompts the user for each piece of info, with added template checks.
30
72
  * @param flags - The flags passed to the command.
73
+ * @param args - The arguments passed to the command.
31
74
  * @returns void
32
75
  */
33
- async runInteractive(flags) {
34
- core_1.ux.styledHeader('Directus Template CLI - Init');
76
+ async runInteractive(flags, args) {
77
+ // Show animated intro
78
+ await animatedBunny('Let\'s create a new Directus project!');
79
+ intro(`${chalk.bgHex(DIRECTUS_PURPLE).white.bold('Directus Template CLI')} - Create Project`);
35
80
  // Create GitHub service
36
- const github = (0, github_1.createGitHub)();
81
+ const github = createGitHub();
82
+ // If no dir is provided, ask for it
83
+ if (!args.directory || args.directory === '.') {
84
+ const dirResponse = await text({
85
+ message: 'Enter the directory to create the project in:',
86
+ placeholder: './my-directus-project',
87
+ });
88
+ if (isCancel(dirResponse)) {
89
+ cancel('Project creation cancelled.');
90
+ process.exit(0);
91
+ }
92
+ this.targetDir = dirResponse;
93
+ }
94
+ if (fs.existsSync(this.targetDir) && !flags.overrideDir) {
95
+ const overrideDirResponse = await confirm({
96
+ message: 'Directory already exists. Would you like to overwrite it?',
97
+ });
98
+ if (isCancel(overrideDirResponse)) {
99
+ cancel('Project creation cancelled.');
100
+ process.exit(0);
101
+ }
102
+ if (overrideDirResponse) {
103
+ flags.overrideDir = true;
104
+ }
105
+ }
37
106
  // 1. Fetch available templates
38
107
  const availableTemplates = await github.getTemplates();
39
108
  // 2. Prompt for template if not provided
40
109
  let { template } = flags;
41
110
  if (!template) {
42
- template = await inquirer_1.default
43
- .prompt([
44
- {
45
- choices: availableTemplates,
46
- message: 'Which Directus starters template would you like to use?',
47
- name: 'template',
48
- type: 'list',
49
- },
50
- ])
51
- .then(ans => ans.template);
111
+ const templateResponse = await select({
112
+ message: 'Which Directus backend template would you like to use?',
113
+ options: availableTemplates.map(template => ({
114
+ label: template,
115
+ value: template,
116
+ })),
117
+ });
118
+ if (isCancel(templateResponse)) {
119
+ cancel('Project creation cancelled.');
120
+ process.exit(0);
121
+ }
122
+ template = templateResponse;
52
123
  }
53
124
  // 3. Validate that the template exists, fetch subdirectories
54
125
  let directories = await github.getTemplateDirectories(template);
55
- while (directories.length === 0) {
126
+ const isDirectUrl = template?.startsWith('http');
127
+ while (!isDirectUrl && directories.length === 0) {
56
128
  this.log(`Template "${template}" doesn't seem to exist in directus-labs/directus-starters.`);
57
- template = await core_1.ux.prompt('Please enter a valid template name, or Ctrl+C to cancel:');
129
+ const templateNameResponse = await text({
130
+ message: 'Please enter a valid template name, or Ctrl+C to cancel:',
131
+ });
132
+ if (isCancel(templateNameResponse)) {
133
+ cancel('Project creation cancelled.');
134
+ process.exit(0);
135
+ }
136
+ template = templateNameResponse;
58
137
  directories = await github.getTemplateDirectories(template);
59
138
  }
60
139
  flags.template = template;
61
- // Filter out the 'directus' folder; the rest are potential frontends
62
- const potentialFrontends = directories.filter(dir => dir !== 'directus');
63
- if (potentialFrontends.length === 0) {
64
- this.error(`No frontends found for template "${template}". Exiting.`);
65
- }
66
- // 4. If user hasn't specified a valid flags.frontend, ask from the list
140
+ // Download the template to a temporary directory to read its configuration
141
+ const tempDir = path.join(os.tmpdir(), `directus-template-${Date.now()}`);
67
142
  let chosenFrontend = flags.frontend;
68
- if (!chosenFrontend || !potentialFrontends.includes(chosenFrontend)) {
69
- chosenFrontend = await inquirer_1.default
70
- .prompt([
71
- {
72
- choices: potentialFrontends,
143
+ try {
144
+ await downloadTemplate(createGigetString(parseGitHubUrl(template)), {
145
+ dir: tempDir,
146
+ force: true,
147
+ });
148
+ // Read template configuration
149
+ const templateInfo = readTemplateConfig(tempDir);
150
+ // 4. If template has frontends and user hasn't specified a valid one, ask from the list
151
+ if (templateInfo?.frontendOptions.length > 0 && (!chosenFrontend || !templateInfo.frontendOptions.find(f => f.id === chosenFrontend))) {
152
+ const frontendResponse = await select({
73
153
  message: 'Which frontend framework do you want to use?',
74
- name: 'chosenFrontend',
75
- type: 'list',
76
- },
77
- ])
78
- .then(ans => ans.chosenFrontend);
154
+ options: [
155
+ ...templateInfo.frontendOptions.map(frontend => ({
156
+ label: frontend.name,
157
+ value: frontend.id,
158
+ })),
159
+ // { label: 'No frontend', value: '' },
160
+ ],
161
+ });
162
+ if (isCancel(frontendResponse)) {
163
+ cancel('Project creation cancelled.');
164
+ process.exit(0);
165
+ }
166
+ chosenFrontend = frontendResponse;
167
+ }
168
+ flags.frontend = chosenFrontend;
79
169
  }
80
- flags.frontend = chosenFrontend;
81
- // 5. Continue with the rest of the interactive flow:
82
- // if (!this.checkDockerInstalled()) {
83
- // const {installDocker} = await inquirer.prompt<{ installDocker: boolean }>([
84
- // {
85
- // default: false,
86
- // message: 'Docker is not installed. Do you want to install Docker?',
87
- // name: 'installDocker',
88
- // type: 'confirm',
89
- // },
90
- // ])
91
- // if (installDocker) {
92
- // ux.log('Please follow Docker\'s official instructions to install Docker, then re-run the init command.')
93
- // this.exit(0)
94
- // }
95
- // }
96
- const { installDeps } = await inquirer_1.default.prompt([
97
- {
98
- default: true,
99
- message: 'Would you like to install project dependencies automatically?',
100
- name: 'installDeps',
101
- type: 'confirm',
102
- },
103
- ]);
104
- const { initGit } = await inquirer_1.default.prompt([
105
- {
106
- default: true,
107
- message: 'Initialize a new Git repository?',
108
- name: 'initGit',
109
- type: 'confirm',
110
- },
111
- ]);
112
- await (0, init_1.init)(this.targetDir, {
113
- frontend: chosenFrontend,
114
- gitInit: initGit,
115
- installDeps,
116
- template,
117
- });
118
- }
119
- /**
120
- * Programmatic mode: relies on flags only, with checks for template existence and valid frontend.
121
- * @param flags - The flags passed to the command.
122
- * @returns void
123
- */
124
- async runProgrammatic(flags) {
125
- const github = (0, github_1.createGitHub)();
126
- if (!flags.template) {
127
- core_1.ux.error('Missing --template parameter for programmatic mode.');
170
+ finally {
171
+ // Clean up temporary directory
172
+ if (fs.existsSync(tempDir)) {
173
+ fs.rmSync(tempDir, { recursive: true, force: true });
174
+ }
128
175
  }
129
- if (!flags.frontend) {
130
- core_1.ux.error('Missing --frontend parameter for programmatic mode.');
176
+ const installDepsResponse = await confirm({
177
+ initialValue: true,
178
+ message: 'Would you like to install project dependencies automatically?',
179
+ });
180
+ if (isCancel(installDepsResponse)) {
181
+ cancel('Project creation cancelled.');
182
+ process.exit(0);
131
183
  }
132
- const template = flags.template;
133
- const directories = await github.getTemplateDirectories(template);
134
- if (directories.length === 0) {
135
- core_1.ux.error(`Template "${template}" doesn't seem to exist in directus-labs/directus-starters.`);
184
+ const installDeps = installDepsResponse;
185
+ const initGitResponse = await confirm({
186
+ initialValue: true,
187
+ message: 'Initialize a new Git repository?',
188
+ });
189
+ if (isCancel(initGitResponse)) {
190
+ cancel('Project creation cancelled.');
191
+ process.exit(0);
136
192
  }
137
- const potentialFrontends = directories.filter(dir => dir !== 'directus');
138
- const frontend = flags.frontend;
139
- if (!potentialFrontends.includes(frontend)) {
140
- core_1.ux.error(`Frontend "${frontend}" doesn't exist in template "${template}". Available frontends: ${potentialFrontends.join(', ')}`);
193
+ const initGit = initGitResponse;
194
+ // Track the command start unless telemetry is disabled
195
+ if (!flags.disableTelemetry) {
196
+ await track({
197
+ lifecycle: 'start',
198
+ distinctId: this.userConfig.distinctId,
199
+ command: 'init',
200
+ flags: {
201
+ frontend: chosenFrontend,
202
+ gitInit: initGit,
203
+ installDeps,
204
+ template,
205
+ },
206
+ runId: this.runId,
207
+ config: this.config,
208
+ });
141
209
  }
142
- await (0, init_1.init)(this.targetDir, {
143
- frontend,
144
- installDeps: true,
145
- template,
210
+ // Initialize the project
211
+ await init({
212
+ dir: this.targetDir,
213
+ flags: {
214
+ frontend: chosenFrontend,
215
+ gitInit: initGit,
216
+ installDeps,
217
+ template,
218
+ overrideDir: flags.overrideDir,
219
+ },
146
220
  });
221
+ // Track the command completion unless telemetry is disabled
222
+ if (!flags.disableTelemetry) {
223
+ await track({
224
+ command: 'init',
225
+ lifecycle: 'complete',
226
+ distinctId: this.userConfig.distinctId,
227
+ flags: {
228
+ frontend: chosenFrontend,
229
+ gitInit: initGit,
230
+ installDeps,
231
+ template,
232
+ overrideDir: flags.overrideDir,
233
+ },
234
+ runId: this.runId,
235
+ config: this.config,
236
+ });
237
+ await shutdown();
238
+ }
239
+ ux.exit(0);
147
240
  }
148
241
  }
149
- InitCommand.args = {
150
- directory: core_1.Args.directory({
151
- default: '.',
152
- description: 'Directory to create the project in',
153
- required: false,
154
- }),
155
- };
156
- InitCommand.description = 'Initialize a new Directus + Frontend monorepo using official or community starters.';
157
- InitCommand.examples = [
158
- '$ directus-template-cli init',
159
- '$ directus-template-cli init my-project',
160
- '$ directus-template-cli init --frontend=nextjs --template=simple-cms --programmatic',
161
- '$ directus-template-cli init my-project --frontend=nextjs --template=simple-cms --programmatic',
162
- ];
163
- InitCommand.flags = {
164
- frontend: core_1.Flags.string({
165
- description: 'Frontend framework to use (e.g., nextjs, nuxt, astro)',
166
- }),
167
- gitInit: core_1.Flags.boolean({
168
- aliases: ['git-init'],
169
- allowNo: true,
170
- default: true,
171
- description: 'Initialize a new Git repository',
172
- }),
173
- installDeps: core_1.Flags.boolean({
174
- aliases: ['install-deps'],
175
- allowNo: true,
176
- default: true,
177
- description: 'Install dependencies automatically',
178
- }),
179
- overrideDir: core_1.Flags.boolean({
180
- default: false,
181
- description: 'Override the default directory',
182
- }),
183
- programmatic: core_1.Flags.boolean({
184
- char: 'p',
185
- default: false,
186
- description: 'Run in programmatic mode (non-interactive)',
187
- }),
188
- template: core_1.Flags.string({
189
- description: 'Template name (e.g., simple-cms) or GitHub URL (e.g., https://github.com/directus-labs/starters/tree/main/simple-cms)',
190
- }),
191
- };
192
- exports.default = InitCommand;