directus-template-cli 0.6.0-beta.2 → 0.7.0-beta.1
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 +0 -14
- package/dist/commands/apply.js +1 -1
- package/dist/commands/extract.d.ts +0 -2
- package/dist/commands/extract.js +2 -29
- package/dist/commands/init.d.ts +36 -0
- package/dist/commands/init.js +192 -0
- package/dist/lib/constants.d.ts +16 -0
- package/dist/lib/constants.js +17 -1
- package/dist/lib/extract/extract-content.d.ts +1 -1
- package/dist/lib/extract/extract-content.js +5 -9
- package/dist/lib/extract/extract-fields.js +1 -1
- package/dist/lib/extract/index.d.ts +1 -6
- package/dist/lib/extract/index.js +5 -12
- package/dist/lib/init/config.d.ts +3 -0
- package/dist/lib/init/config.js +15 -0
- package/dist/lib/init/index.d.ts +9 -0
- package/dist/lib/init/index.js +148 -0
- package/dist/lib/init/types.d.ts +30 -0
- package/dist/lib/init/types.js +2 -0
- package/dist/lib/load/load-collections.d.ts +2 -0
- package/dist/lib/load/load-collections.js +2 -0
- package/dist/lib/utils/ensure-dir.d.ts +2 -0
- package/dist/lib/utils/ensure-dir.js +16 -0
- package/dist/lib/utils/get-template.js +2 -1
- package/dist/lib/utils/parse-github-url.d.ts +14 -0
- package/dist/lib/utils/parse-github-url.js +54 -0
- package/dist/lib/utils/wait.d.ts +7 -0
- package/dist/lib/utils/wait.js +13 -0
- package/dist/services/docker.d.ts +23 -0
- package/dist/services/docker.js +134 -0
- package/dist/services/github.d.ts +18 -0
- package/dist/services/github.js +57 -0
- package/oclif.manifest.json +81 -21
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -181,18 +181,6 @@ Using email/password:
|
|
|
181
181
|
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --userEmail="admin@example.com" --userPassword="admin" --directusUrl="http://localhost:8055"
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
Skipping extracting content from sensitive or large collections:
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --excludeCollections="posts,globals"
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
Skipping extracting files and assets:
|
|
191
|
-
|
|
192
|
-
```
|
|
193
|
-
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --skipFiles
|
|
194
|
-
```
|
|
195
|
-
|
|
196
184
|
Available flags for programmatic mode:
|
|
197
185
|
|
|
198
186
|
- `--directusUrl`: URL of the Directus instance to extract the template from (required)
|
|
@@ -201,8 +189,6 @@ Available flags for programmatic mode:
|
|
|
201
189
|
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
202
190
|
- `--templateLocation`: Directory to extract the template to (required)
|
|
203
191
|
- `--templateName`: Name of the template (required)
|
|
204
|
-
- `--excludeCollections`: Comma-separated list of collection names to exclude from extraction
|
|
205
|
-
- `--skipFiles`: Skip extracting files and assets
|
|
206
192
|
|
|
207
193
|
#### Using Environment Variables
|
|
208
194
|
|
package/dist/commands/apply.js
CHANGED
|
@@ -5,9 +5,7 @@ export default class ExtractCommand extends Command {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
directusToken: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
7
7
|
directusUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
-
excludeCollections: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
8
|
programmatic: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
skipFiles: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
9
|
templateLocation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
10
|
templateName: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
11
|
userEmail: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/commands/extract.js
CHANGED
|
@@ -30,7 +30,6 @@ class ExtractCommand extends core_1.Command {
|
|
|
30
30
|
* @returns {Promise<void>} - Returns nothing
|
|
31
31
|
*/
|
|
32
32
|
async extractTemplate(templateName, directory, flags) {
|
|
33
|
-
var _a;
|
|
34
33
|
try {
|
|
35
34
|
if (!node_fs_1.default.existsSync(directory)) {
|
|
36
35
|
node_fs_1.default.mkdirSync(directory, { recursive: true });
|
|
@@ -50,14 +49,8 @@ class ExtractCommand extends core_1.Command {
|
|
|
50
49
|
});
|
|
51
50
|
}
|
|
52
51
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
: '';
|
|
56
|
-
core_1.ux.action.start(`Extracting template - ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, templateName)}${exclusionMessage} from ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, flags.directusUrl)} to ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
|
|
57
|
-
await (0, extract_1.default)(directory, {
|
|
58
|
-
excludeCollections: flags.excludeCollections,
|
|
59
|
-
skipFiles: flags.skipFiles,
|
|
60
|
-
});
|
|
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);
|
|
61
54
|
core_1.ux.action.stop();
|
|
62
55
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
63
56
|
core_1.ux.log('Template extracted successfully.');
|
|
@@ -73,12 +66,6 @@ class ExtractCommand extends core_1.Command {
|
|
|
73
66
|
const templateName = await core_1.ux.prompt('What is the name of the template you would like to extract?');
|
|
74
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 })}` });
|
|
75
68
|
core_1.ux.log(`You selected ${core_1.ux.colorize(constants_1.DIRECTUS_PINK, directory)}`);
|
|
76
|
-
const excludeCollectionsInput = await core_1.ux.prompt('Enter collection names to exclude (comma-separated) or press enter to skip', { required: false });
|
|
77
|
-
if (excludeCollectionsInput) {
|
|
78
|
-
flags.excludeCollections = excludeCollectionsInput.split(',').map(name => name.trim());
|
|
79
|
-
}
|
|
80
|
-
const skipFiles = await core_1.ux.confirm('Skip extracting files and assets? (y/N)');
|
|
81
|
-
flags.skipFiles = skipFiles;
|
|
82
69
|
core_1.ux.log(constants_1.SEPARATOR);
|
|
83
70
|
// Get Directus URL
|
|
84
71
|
const directusUrl = await (0, auth_1.getDirectusUrl)();
|
|
@@ -139,25 +126,11 @@ ExtractCommand.description = 'Extract a template from a Directus instance.';
|
|
|
139
126
|
ExtractCommand.examples = [
|
|
140
127
|
'$ directus-template-cli extract',
|
|
141
128
|
'$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055"',
|
|
142
|
-
'$ directus-template-cli extract -p --templateName="My Template" --templateLocation="./my-template" --directusToken="admin-token-here" --directusUrl="http://localhost:8055" --excludeCollections=collection1,collection2',
|
|
143
129
|
];
|
|
144
130
|
ExtractCommand.flags = {
|
|
145
131
|
directusToken: customFlags.directusToken,
|
|
146
132
|
directusUrl: customFlags.directusUrl,
|
|
147
|
-
excludeCollections: core_1.Flags.string({
|
|
148
|
-
char: 'e',
|
|
149
|
-
delimiter: ',', // Will split on commas and return an array
|
|
150
|
-
description: 'Comma-separated list of collection names to exclude from extraction',
|
|
151
|
-
multiple: true,
|
|
152
|
-
required: false,
|
|
153
|
-
}),
|
|
154
133
|
programmatic: customFlags.programmatic,
|
|
155
|
-
skipFiles: core_1.Flags.boolean({
|
|
156
|
-
char: 'f',
|
|
157
|
-
default: false,
|
|
158
|
-
description: 'Skip extracting files and assets',
|
|
159
|
-
required: false,
|
|
160
|
-
}),
|
|
161
134
|
templateLocation: customFlags.templateLocation,
|
|
162
135
|
templateName: customFlags.templateName,
|
|
163
136
|
userEmail: customFlags.userEmail,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class InitCommand extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
directory: import("@oclif/core/lib/interfaces").Arg<string, {
|
|
5
|
+
exists?: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
};
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
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>;
|
|
17
|
+
};
|
|
18
|
+
private targetDir;
|
|
19
|
+
/**
|
|
20
|
+
* Entrypoint for the command.
|
|
21
|
+
* @returns Promise that resolves when the command is complete.
|
|
22
|
+
*/
|
|
23
|
+
run(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Interactive mode: prompts the user for each piece of info, with added template checks.
|
|
26
|
+
* @param flags - The flags passed to the command.
|
|
27
|
+
* @returns void
|
|
28
|
+
*/
|
|
29
|
+
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
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Entrypoint for the command.
|
|
16
|
+
* @returns Promise that resolves when the command is complete.
|
|
17
|
+
*/
|
|
18
|
+
async run() {
|
|
19
|
+
const { args, flags } = await this.parse(InitCommand);
|
|
20
|
+
const typedFlags = flags;
|
|
21
|
+
// 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));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Interactive mode: prompts the user for each piece of info, with added template checks.
|
|
30
|
+
* @param flags - The flags passed to the command.
|
|
31
|
+
* @returns void
|
|
32
|
+
*/
|
|
33
|
+
async runInteractive(flags) {
|
|
34
|
+
core_1.ux.styledHeader('Directus Template CLI - Init');
|
|
35
|
+
// Create GitHub service
|
|
36
|
+
const github = (0, github_1.createGitHub)();
|
|
37
|
+
// 1. Fetch available templates
|
|
38
|
+
const availableTemplates = await github.getTemplates();
|
|
39
|
+
// 2. Prompt for template if not provided
|
|
40
|
+
let { template } = flags;
|
|
41
|
+
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);
|
|
52
|
+
}
|
|
53
|
+
// 3. Validate that the template exists, fetch subdirectories
|
|
54
|
+
let directories = await github.getTemplateDirectories(template);
|
|
55
|
+
while (directories.length === 0) {
|
|
56
|
+
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:');
|
|
58
|
+
directories = await github.getTemplateDirectories(template);
|
|
59
|
+
}
|
|
60
|
+
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
|
|
67
|
+
let chosenFrontend = flags.frontend;
|
|
68
|
+
if (!chosenFrontend || !potentialFrontends.includes(chosenFrontend)) {
|
|
69
|
+
chosenFrontend = await inquirer_1.default
|
|
70
|
+
.prompt([
|
|
71
|
+
{
|
|
72
|
+
choices: potentialFrontends,
|
|
73
|
+
message: 'Which frontend framework do you want to use?',
|
|
74
|
+
name: 'chosenFrontend',
|
|
75
|
+
type: 'list',
|
|
76
|
+
},
|
|
77
|
+
])
|
|
78
|
+
.then(ans => ans.chosenFrontend);
|
|
79
|
+
}
|
|
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.');
|
|
128
|
+
}
|
|
129
|
+
if (!flags.frontend) {
|
|
130
|
+
core_1.ux.error('Missing --frontend parameter for programmatic mode.');
|
|
131
|
+
}
|
|
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.`);
|
|
136
|
+
}
|
|
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(', ')}`);
|
|
141
|
+
}
|
|
142
|
+
await (0, init_1.init)(this.targetDir, {
|
|
143
|
+
frontend,
|
|
144
|
+
installDeps: true,
|
|
145
|
+
template,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
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;
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
1
|
export declare const DIRECTUS_PURPLE = "#6644ff";
|
|
2
2
|
export declare const DIRECTUS_PINK = "#FF99DD";
|
|
3
3
|
export declare const SEPARATOR = "------------------";
|
|
4
|
+
export declare const COMMUNITY_TEMPLATE_REPO: {
|
|
5
|
+
string: string;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const STARTERS_TEMPLATE_REPO: {
|
|
9
|
+
branch: string;
|
|
10
|
+
string: string;
|
|
11
|
+
url: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const DEFAULT_REPO: {
|
|
14
|
+
owner: string;
|
|
15
|
+
path: string;
|
|
16
|
+
ref: string;
|
|
17
|
+
repo: string;
|
|
18
|
+
url: string;
|
|
19
|
+
};
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SEPARATOR = exports.DIRECTUS_PINK = exports.DIRECTUS_PURPLE = void 0;
|
|
3
|
+
exports.DEFAULT_REPO = exports.STARTERS_TEMPLATE_REPO = exports.COMMUNITY_TEMPLATE_REPO = exports.SEPARATOR = exports.DIRECTUS_PINK = exports.DIRECTUS_PURPLE = void 0;
|
|
4
4
|
exports.DIRECTUS_PURPLE = '#6644ff';
|
|
5
5
|
exports.DIRECTUS_PINK = '#FF99DD';
|
|
6
6
|
exports.SEPARATOR = '------------------';
|
|
7
|
+
exports.COMMUNITY_TEMPLATE_REPO = {
|
|
8
|
+
string: 'github:directus-labs/directus-templates',
|
|
9
|
+
url: 'https://github.com/directus-labs/directus-templates',
|
|
10
|
+
};
|
|
11
|
+
exports.STARTERS_TEMPLATE_REPO = {
|
|
12
|
+
branch: 'cms-template',
|
|
13
|
+
string: 'github:directus-labs/starters',
|
|
14
|
+
url: 'https://github.com/directus-labs/starters',
|
|
15
|
+
};
|
|
16
|
+
exports.DEFAULT_REPO = {
|
|
17
|
+
owner: 'directus-labs',
|
|
18
|
+
path: '',
|
|
19
|
+
ref: 'cms-template',
|
|
20
|
+
repo: 'starters',
|
|
21
|
+
url: 'https://github.com/directus-labs/starters',
|
|
22
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function extractContent(dir: string
|
|
1
|
+
export declare function extractContent(dir: string): Promise<void>;
|
|
@@ -8,12 +8,11 @@ const constants_1 = require("../constants");
|
|
|
8
8
|
const sdk_2 = require("../sdk");
|
|
9
9
|
const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
|
|
10
10
|
const write_to_file_1 = tslib_1.__importDefault(require("../utils/write-to-file"));
|
|
11
|
-
async function getCollections(
|
|
11
|
+
async function getCollections() {
|
|
12
12
|
const response = await sdk_2.api.client.request((0, sdk_1.readCollections)());
|
|
13
13
|
return response
|
|
14
14
|
.filter(item => !item.collection.startsWith('directus_', 0))
|
|
15
|
-
.filter(item => item.schema
|
|
16
|
-
.filter(item => !(excludeCollections === null || excludeCollections === void 0 ? void 0 : excludeCollections.includes(item.collection)))
|
|
15
|
+
.filter(item => item.schema != null)
|
|
17
16
|
.map(i => i.collection);
|
|
18
17
|
}
|
|
19
18
|
async function getDataFromCollection(collection, dir) {
|
|
@@ -25,13 +24,10 @@ async function getDataFromCollection(collection, dir) {
|
|
|
25
24
|
(0, catch_error_1.default)(error);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
async function extractContent(dir
|
|
29
|
-
|
|
30
|
-
? ` (excluding ${excludeCollections.join(', ')})`
|
|
31
|
-
: '';
|
|
32
|
-
core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, `Extracting content${exclusionMessage}`));
|
|
27
|
+
async function extractContent(dir) {
|
|
28
|
+
core_1.ux.action.start(core_1.ux.colorize(constants_1.DIRECTUS_PINK, 'Extracting content'));
|
|
33
29
|
try {
|
|
34
|
-
const collections = await getCollections(
|
|
30
|
+
const collections = await getCollections();
|
|
35
31
|
await Promise.all(collections.map(collection => getDataFromCollection(collection, dir)));
|
|
36
32
|
}
|
|
37
33
|
catch (error) {
|
|
@@ -22,8 +22,7 @@ const extract_schema_1 = tslib_1.__importDefault(require("./extract-schema"));
|
|
|
22
22
|
const extract_settings_1 = tslib_1.__importDefault(require("./extract-settings"));
|
|
23
23
|
const extract_translations_1 = tslib_1.__importDefault(require("./extract-translations"));
|
|
24
24
|
const extract_users_1 = tslib_1.__importDefault(require("./extract-users"));
|
|
25
|
-
async function extract(dir
|
|
26
|
-
const { excludeCollections, skipFiles = false } = options;
|
|
25
|
+
async function extract(dir) {
|
|
27
26
|
// Get the destination directory for the actual files
|
|
28
27
|
const destination = dir + '/src';
|
|
29
28
|
// Check if directory exists, if not, then create it.
|
|
@@ -35,11 +34,8 @@ async function extract(dir, options = {}) {
|
|
|
35
34
|
await (0, extract_collections_1.default)(destination);
|
|
36
35
|
await (0, extract_fields_1.default)(destination);
|
|
37
36
|
await (0, extract_relations_1.default)(destination);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
await (0, extract_folders_1.default)(destination);
|
|
41
|
-
await (0, extract_files_1.default)(destination);
|
|
42
|
-
}
|
|
37
|
+
await (0, extract_folders_1.default)(destination);
|
|
38
|
+
await (0, extract_files_1.default)(destination);
|
|
43
39
|
await (0, extract_users_1.default)(destination);
|
|
44
40
|
await (0, extract_roles_1.default)(destination);
|
|
45
41
|
await (0, extract_permissions_1.default)(destination);
|
|
@@ -53,11 +49,8 @@ async function extract(dir, options = {}) {
|
|
|
53
49
|
await (0, extract_dashboards_1.extractPanels)(destination);
|
|
54
50
|
await (0, extract_settings_1.default)(destination);
|
|
55
51
|
await (0, extract_extensions_1.default)(destination);
|
|
56
|
-
await (0, extract_content_1.extractContent)(destination
|
|
57
|
-
|
|
58
|
-
if (!skipFiles) {
|
|
59
|
-
await (0, extract_assets_1.downloadAllFiles)(destination);
|
|
60
|
-
}
|
|
52
|
+
await (0, extract_content_1.extractContent)(destination);
|
|
53
|
+
await (0, extract_assets_1.downloadAllFiles)(destination);
|
|
61
54
|
return {};
|
|
62
55
|
}
|
|
63
56
|
exports.default = extract;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DOCKER_CONFIG = exports.DIRECTUS_CONFIG = void 0;
|
|
4
|
+
exports.DIRECTUS_CONFIG = {
|
|
5
|
+
adminEmail: 'admin@example.com',
|
|
6
|
+
adminPassword: 'd1r3ctu5',
|
|
7
|
+
port: 8055,
|
|
8
|
+
url: 'http://localhost',
|
|
9
|
+
};
|
|
10
|
+
exports.DOCKER_CONFIG = {
|
|
11
|
+
composeFile: 'docker-compose.yml',
|
|
12
|
+
healthCheckEndpoint: '/server/health',
|
|
13
|
+
interval: 5000,
|
|
14
|
+
maxAttempts: 30,
|
|
15
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const giget_1 = require("giget");
|
|
7
|
+
const glob_1 = require("glob");
|
|
8
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
10
|
+
const nypm_1 = require("nypm");
|
|
11
|
+
const apply_1 = tslib_1.__importDefault(require("../../commands/apply"));
|
|
12
|
+
const docker_1 = require("../../services/docker");
|
|
13
|
+
const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
|
|
14
|
+
const parse_github_url_1 = require("../utils/parse-github-url");
|
|
15
|
+
const config_1 = require("./config");
|
|
16
|
+
async function init(dir, flags) {
|
|
17
|
+
// Check target directory
|
|
18
|
+
const shouldForce = flags.overrideDir;
|
|
19
|
+
if (node_fs_1.default.existsSync(dir) && !shouldForce) {
|
|
20
|
+
throw new Error('Directory already exists. Use --override-dir to override.');
|
|
21
|
+
}
|
|
22
|
+
const frontendDir = node_path_1.default.join(dir, flags.frontend);
|
|
23
|
+
const directusDir = node_path_1.default.join(dir, 'directus');
|
|
24
|
+
let template;
|
|
25
|
+
try {
|
|
26
|
+
// Download the template from GitHub
|
|
27
|
+
const parsedUrl = (0, parse_github_url_1.parseGitHubUrl)(flags.template);
|
|
28
|
+
template = await (0, giget_1.downloadTemplate)((0, parse_github_url_1.createGigetString)(parsedUrl), {
|
|
29
|
+
dir,
|
|
30
|
+
force: shouldForce,
|
|
31
|
+
});
|
|
32
|
+
// Cleanup the template
|
|
33
|
+
if (flags.frontend) {
|
|
34
|
+
// Ensure directus directory exists before cleaning up
|
|
35
|
+
if (!node_fs_1.default.existsSync(directusDir)) {
|
|
36
|
+
node_fs_1.default.mkdirSync(directusDir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
// Read and parse package.json
|
|
39
|
+
const packageJsonPath = node_path_1.default.join(dir, 'package.json');
|
|
40
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
41
|
+
throw new Error('package.json not found in template');
|
|
42
|
+
}
|
|
43
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
44
|
+
const templateConfig = packageJson['directus:template'];
|
|
45
|
+
// Get all frontend paths from the configuration
|
|
46
|
+
const frontendPaths = Object.values((templateConfig === null || templateConfig === void 0 ? void 0 : templateConfig.frontends) || {})
|
|
47
|
+
.map(frontend => frontend.path.replace(/^\.\//, ''))
|
|
48
|
+
.filter(path => path !== flags.frontend); // Exclude the selected frontend
|
|
49
|
+
// Remove unused frontend directories
|
|
50
|
+
for (const frontendPath of frontendPaths) {
|
|
51
|
+
const pathToRemove = node_path_1.default.join(dir, frontendPath);
|
|
52
|
+
if (node_fs_1.default.existsSync(pathToRemove)) {
|
|
53
|
+
node_fs_1.default.rmSync(pathToRemove, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Find and copy all .env.example files
|
|
58
|
+
const envFiles = glob_1.glob.sync(node_path_1.default.join(dir, '**', '.env.example'));
|
|
59
|
+
for (const file of envFiles) {
|
|
60
|
+
const envFile = file.replace('.env.example', '.env');
|
|
61
|
+
node_fs_1.default.copyFileSync(file, envFile);
|
|
62
|
+
}
|
|
63
|
+
// Start Directus and apply template only if directus directory exists
|
|
64
|
+
if (node_fs_1.default.existsSync(directusDir)) {
|
|
65
|
+
// Initialize Docker service
|
|
66
|
+
const dockerService = (0, docker_1.createDocker)(config_1.DOCKER_CONFIG);
|
|
67
|
+
// Check if Docker is installed
|
|
68
|
+
const dockerStatus = await dockerService.checkDocker();
|
|
69
|
+
if (!dockerStatus.installed || !dockerStatus.running) {
|
|
70
|
+
throw new Error(dockerStatus.message);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await dockerService.startContainers(directusDir);
|
|
74
|
+
const healthCheckUrl = `${config_1.DIRECTUS_CONFIG.url}:${config_1.DIRECTUS_CONFIG.port}${config_1.DOCKER_CONFIG.healthCheckEndpoint}`;
|
|
75
|
+
await dockerService.waitForHealthy(healthCheckUrl);
|
|
76
|
+
const templatePath = node_path_1.default.join(directusDir, 'template');
|
|
77
|
+
core_1.ux.log(`Attempting to apply template from: ${templatePath}`);
|
|
78
|
+
await apply_1.default.run([
|
|
79
|
+
'--directusUrl=http://localhost:8055',
|
|
80
|
+
'-p',
|
|
81
|
+
'--userEmail=admin@example.com',
|
|
82
|
+
'--userPassword=d1r3ctu5',
|
|
83
|
+
'--templateLocation=' + templatePath,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
core_1.ux.error('Failed to start Directus containers or apply template');
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Install dependencies for frontend if it exists
|
|
92
|
+
if (flags.installDeps && node_fs_1.default.existsSync(frontendDir)) {
|
|
93
|
+
core_1.ux.action.start('Installing dependencies');
|
|
94
|
+
try {
|
|
95
|
+
const packageManager = await (0, nypm_1.detectPackageManager)(frontendDir);
|
|
96
|
+
await (0, nypm_1.installDependencies)({
|
|
97
|
+
cwd: frontendDir,
|
|
98
|
+
packageManager,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
core_1.ux.warn('Failed to install dependencies');
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
core_1.ux.action.stop();
|
|
106
|
+
}
|
|
107
|
+
// Initialize Git repo
|
|
108
|
+
if (flags.gitInit) {
|
|
109
|
+
core_1.ux.action.start('Initializing git repository');
|
|
110
|
+
await initGit(dir);
|
|
111
|
+
core_1.ux.action.stop();
|
|
112
|
+
}
|
|
113
|
+
// Finishing up
|
|
114
|
+
core_1.ux.log(`🚀 Directus initialized with template – ${flags.template}`);
|
|
115
|
+
core_1.ux.log('You\'ll find the following directories in your project:');
|
|
116
|
+
core_1.ux.log('• directus');
|
|
117
|
+
core_1.ux.log(`• ${flags.frontend}`);
|
|
118
|
+
core_1.ux.exit(0);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
(0, catch_error_1.default)(error, {
|
|
122
|
+
context: { dir, flags, function: 'init' },
|
|
123
|
+
fatal: true,
|
|
124
|
+
logToFile: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.init = init;
|
|
129
|
+
/**
|
|
130
|
+
* Initialize a git repository
|
|
131
|
+
* @param targetDir - The directory to initialize the git repository in
|
|
132
|
+
* @returns void
|
|
133
|
+
*/
|
|
134
|
+
async function initGit(targetDir) {
|
|
135
|
+
try {
|
|
136
|
+
core_1.ux.action.start('Initializing git repository');
|
|
137
|
+
const { execa } = await Promise.resolve().then(() => tslib_1.__importStar(require('execa')));
|
|
138
|
+
await execa('git', ['init'], { cwd: targetDir });
|
|
139
|
+
core_1.ux.action.stop();
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
(0, catch_error_1.default)(error, {
|
|
143
|
+
context: { function: 'initGit', targetDir },
|
|
144
|
+
fatal: false,
|
|
145
|
+
logToFile: true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface InitOptions {
|
|
2
|
+
frontend?: string;
|
|
3
|
+
initGit?: boolean;
|
|
4
|
+
installDeps?: boolean;
|
|
5
|
+
programmatic?: boolean;
|
|
6
|
+
targetDir: string;
|
|
7
|
+
template: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TemplateInfo {
|
|
10
|
+
frontends: string[];
|
|
11
|
+
hasDirectus: boolean;
|
|
12
|
+
name: string;
|
|
13
|
+
}
|
|
14
|
+
export interface InitResult {
|
|
15
|
+
directusUrl?: string;
|
|
16
|
+
error?: Error;
|
|
17
|
+
success: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface DirectusConfig {
|
|
20
|
+
adminEmail: string;
|
|
21
|
+
adminPassword: string;
|
|
22
|
+
port: number;
|
|
23
|
+
url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface DockerConfig {
|
|
26
|
+
composeFile: string;
|
|
27
|
+
healthCheckEndpoint: string;
|
|
28
|
+
interval: number;
|
|
29
|
+
maxAttempts: number;
|
|
30
|
+
}
|
|
@@ -9,6 +9,8 @@ const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
|
|
|
9
9
|
const read_file_1 = tslib_1.__importDefault(require("../utils/read-file"));
|
|
10
10
|
/**
|
|
11
11
|
* Load collections into the Directus instance
|
|
12
|
+
* @param dir - The directory to read the collections and fields from
|
|
13
|
+
* @returns {Promise<void>} - Returns nothing
|
|
12
14
|
*/
|
|
13
15
|
async function loadCollections(dir) {
|
|
14
16
|
const collectionsToAdd = (0, read_file_1.default)('collections', dir);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureDirSync = exports.ensureDir = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
function ensureDir(dir) {
|
|
6
|
+
if (!(0, node_fs_1.existsSync)(dir)) {
|
|
7
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
exports.ensureDir = ensureDir;
|
|
11
|
+
function ensureDirSync(dir) {
|
|
12
|
+
if (!(0, node_fs_1.existsSync)(dir)) {
|
|
13
|
+
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.ensureDirSync = ensureDirSync;
|
|
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
|
|
|
5
5
|
const giget_1 = require("giget");
|
|
6
6
|
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
7
7
|
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
8
|
+
const constants_1 = require("../constants");
|
|
8
9
|
const path_1 = tslib_1.__importDefault(require("./path"));
|
|
9
10
|
const read_templates_1 = require("./read-templates");
|
|
10
11
|
const transform_github_url_1 = require("./transform-github-url");
|
|
@@ -14,7 +15,7 @@ async function getCommunityTemplates() {
|
|
|
14
15
|
throw new Error(`Invalid download directory: ${node_path_1.default.join(__dirname, '..', 'downloads', 'official')}`);
|
|
15
16
|
}
|
|
16
17
|
try {
|
|
17
|
-
const { dir } = await (0, giget_1.downloadTemplate)(
|
|
18
|
+
const { dir } = await (0, giget_1.downloadTemplate)(constants_1.COMMUNITY_TEMPLATE_REPO.string, {
|
|
18
19
|
dir: downloadDir,
|
|
19
20
|
force: true,
|
|
20
21
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface GitHubUrlParts {
|
|
2
|
+
owner: string;
|
|
3
|
+
path?: string;
|
|
4
|
+
ref?: string;
|
|
5
|
+
repo: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse a GitHub URL into its components.
|
|
9
|
+
* @param url - The GitHub URL to parse.
|
|
10
|
+
* @returns The parsed GitHub URL components.
|
|
11
|
+
*/
|
|
12
|
+
export declare function parseGitHubUrl(url: string): GitHubUrlParts;
|
|
13
|
+
export declare function createGigetString({ owner, path, ref, repo }: GitHubUrlParts): string;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGigetString = exports.parseGitHubUrl = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
/**
|
|
6
|
+
* Parse a GitHub URL into its components.
|
|
7
|
+
* @param url - The GitHub URL to parse.
|
|
8
|
+
* @returns The parsed GitHub URL components.
|
|
9
|
+
*/
|
|
10
|
+
function parseGitHubUrl(url) {
|
|
11
|
+
// Handle simple template names by using default repo
|
|
12
|
+
if (!url.includes('/')) {
|
|
13
|
+
return { ...constants_1.DEFAULT_REPO, path: url };
|
|
14
|
+
}
|
|
15
|
+
// Handle different GitHub URL formats:
|
|
16
|
+
// - https://github.com/owner/repo
|
|
17
|
+
// - https://github.com/owner/repo/tree/branch
|
|
18
|
+
// - https://github.com/owner/repo/tree/branch/path
|
|
19
|
+
// - owner/repo
|
|
20
|
+
// - owner/repo/path
|
|
21
|
+
try {
|
|
22
|
+
if (url.startsWith('https://github.com/')) {
|
|
23
|
+
url = url.replace('https://github.com/', '');
|
|
24
|
+
}
|
|
25
|
+
const parts = url.split('/');
|
|
26
|
+
const owner = parts[0];
|
|
27
|
+
const repo = parts[1];
|
|
28
|
+
let path = '';
|
|
29
|
+
let ref;
|
|
30
|
+
if (parts.length > 2) {
|
|
31
|
+
if (parts[2] === 'tree' && parts.length > 3) {
|
|
32
|
+
ref = parts[3];
|
|
33
|
+
path = parts.slice(4).join('/');
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
path = parts.slice(2).join('/');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { owner, path, ref, repo };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
throw new Error(`Invalid GitHub URL format: ${url}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
exports.parseGitHubUrl = parseGitHubUrl;
|
|
46
|
+
function createGigetString({ owner, path, ref, repo }) {
|
|
47
|
+
let source = `github:${owner}/${repo}`;
|
|
48
|
+
if (path)
|
|
49
|
+
source += `/${path}`;
|
|
50
|
+
if (ref)
|
|
51
|
+
source += `#${ref}`;
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
exports.createGigetString = createGigetString;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.waitFor = void 0;
|
|
4
|
+
async function waitFor(checkFn, options = {}) {
|
|
5
|
+
const { errorMessage = 'Operation timed out', interval = 2000, maxAttempts = 30, } = options;
|
|
6
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
7
|
+
if (await checkFn())
|
|
8
|
+
return true;
|
|
9
|
+
await new Promise(resolve => setTimeout(resolve, interval));
|
|
10
|
+
}
|
|
11
|
+
throw new Error(errorMessage);
|
|
12
|
+
}
|
|
13
|
+
exports.waitFor = waitFor;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface DockerConfig {
|
|
2
|
+
composeFile: string;
|
|
3
|
+
healthCheckEndpoint: string;
|
|
4
|
+
interval: number;
|
|
5
|
+
maxAttempts: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DockerService {
|
|
8
|
+
checkDocker: () => Promise<DockerCheckResult>;
|
|
9
|
+
startContainers: (cwd: string) => Promise<void>;
|
|
10
|
+
stopContainers: (cwd: string) => Promise<void>;
|
|
11
|
+
waitForHealthy: (healthCheckUrl: string) => Promise<boolean>;
|
|
12
|
+
}
|
|
13
|
+
export interface DockerCheckResult {
|
|
14
|
+
installed: boolean;
|
|
15
|
+
message?: string;
|
|
16
|
+
running: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a Docker service instance
|
|
20
|
+
* @param {DockerConfig} config - The Docker configuration
|
|
21
|
+
* @returns {DockerService} - Returns a Docker service instance
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDocker(config: DockerConfig): DockerService;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDocker = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const execa_1 = require("execa");
|
|
7
|
+
const catch_error_1 = tslib_1.__importDefault(require("../lib/utils/catch-error"));
|
|
8
|
+
const wait_1 = require("../lib/utils/wait");
|
|
9
|
+
/**
|
|
10
|
+
* Check if Docker is installed and running
|
|
11
|
+
* @returns {Promise<DockerCheckResult>} Docker installation and running status
|
|
12
|
+
*/
|
|
13
|
+
async function checkDocker() {
|
|
14
|
+
try {
|
|
15
|
+
// Check if Docker is installed
|
|
16
|
+
const versionResult = await (0, execa_1.execa)('docker', ['--version']);
|
|
17
|
+
const isInstalled = versionResult.exitCode === 0;
|
|
18
|
+
if (!isInstalled) {
|
|
19
|
+
return { installed: false, message: 'Docker is not installed. Please install Docker at https://docs.docker.com/get-started/get-docker/', running: false };
|
|
20
|
+
}
|
|
21
|
+
// Check if Docker daemon is running
|
|
22
|
+
const statusResult = await (0, execa_1.execa)('docker', ['info']);
|
|
23
|
+
const isRunning = statusResult.exitCode === 0;
|
|
24
|
+
return {
|
|
25
|
+
installed: true,
|
|
26
|
+
message: 'Docker is installed and running.',
|
|
27
|
+
running: isRunning,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// If any command fails, Docker is either not installed or not running
|
|
32
|
+
return {
|
|
33
|
+
installed: false,
|
|
34
|
+
message: 'Docker is not running. Please start Docker and try again.',
|
|
35
|
+
running: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Start Docker containers using docker-compose
|
|
41
|
+
* @param {string} cwd - The current working directory
|
|
42
|
+
* @returns {Promise<void>} - Returns nothing
|
|
43
|
+
*/
|
|
44
|
+
function startContainers(cwd) {
|
|
45
|
+
try {
|
|
46
|
+
core_1.ux.action.start('Starting Docker containers');
|
|
47
|
+
return (0, execa_1.execa)('docker-compose', ['up', '-d'], {
|
|
48
|
+
cwd,
|
|
49
|
+
// stdio: 'inherit',
|
|
50
|
+
}).then(() => {
|
|
51
|
+
core_1.ux.action.stop();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
(0, catch_error_1.default)(error, {
|
|
56
|
+
context: { cwd, function: 'startContainers' },
|
|
57
|
+
fatal: true,
|
|
58
|
+
logToFile: true,
|
|
59
|
+
});
|
|
60
|
+
return Promise.reject(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Stop Docker containers
|
|
65
|
+
* @param {string} cwd - The current working directory
|
|
66
|
+
* @returns {Promise<void>} - Returns nothing
|
|
67
|
+
*/
|
|
68
|
+
function stopContainers(cwd) {
|
|
69
|
+
try {
|
|
70
|
+
return (0, execa_1.execa)('docker-compose', ['down'], {
|
|
71
|
+
cwd,
|
|
72
|
+
// stdio: 'inherit',
|
|
73
|
+
}).then(() => { });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
(0, catch_error_1.default)(error, {
|
|
77
|
+
context: { cwd, function: 'stopContainers' },
|
|
78
|
+
fatal: false,
|
|
79
|
+
logToFile: true,
|
|
80
|
+
});
|
|
81
|
+
return Promise.reject(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Wait for service health check to pass
|
|
86
|
+
* @param {DockerConfig} config - The Docker configuration
|
|
87
|
+
* @returns {Promise<boolean>} - Returns true if the service is healthy, false otherwise
|
|
88
|
+
*/
|
|
89
|
+
function createWaitForHealthy(config) {
|
|
90
|
+
async function waitForHealthy(healthCheckUrl) {
|
|
91
|
+
core_1.ux.action.start('Waiting for service to be healthy');
|
|
92
|
+
try {
|
|
93
|
+
await (0, wait_1.waitFor)(async () => {
|
|
94
|
+
try {
|
|
95
|
+
const response = await fetch(healthCheckUrl);
|
|
96
|
+
return response.ok;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}, {
|
|
102
|
+
errorMessage: 'Service failed to become healthy',
|
|
103
|
+
interval: config.interval,
|
|
104
|
+
maxAttempts: config.maxAttempts,
|
|
105
|
+
});
|
|
106
|
+
core_1.ux.action.stop();
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
core_1.ux.action.stop('failed');
|
|
111
|
+
(0, catch_error_1.default)(error, {
|
|
112
|
+
context: { function: 'waitForHealthy', url: healthCheckUrl },
|
|
113
|
+
fatal: true,
|
|
114
|
+
logToFile: true,
|
|
115
|
+
});
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return waitForHealthy;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Create a Docker service instance
|
|
123
|
+
* @param {DockerConfig} config - The Docker configuration
|
|
124
|
+
* @returns {DockerService} - Returns a Docker service instance
|
|
125
|
+
*/
|
|
126
|
+
function createDocker(config) {
|
|
127
|
+
return {
|
|
128
|
+
checkDocker,
|
|
129
|
+
startContainers,
|
|
130
|
+
stopContainers,
|
|
131
|
+
waitForHealthy: createWaitForHealthy(config),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
exports.createDocker = createDocker;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { parseGitHubUrl } from '../lib/utils/parse-github-url';
|
|
2
|
+
interface GitHubUrlParts {
|
|
3
|
+
owner: string;
|
|
4
|
+
path?: string;
|
|
5
|
+
ref?: string;
|
|
6
|
+
repo: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GitHubService {
|
|
9
|
+
getTemplateDirectories(template: string, customUrl?: string): Promise<string[]>;
|
|
10
|
+
getTemplates(customUrl?: string): Promise<string[]>;
|
|
11
|
+
parseGitHubUrl(url: string): GitHubUrlParts;
|
|
12
|
+
}
|
|
13
|
+
export declare function createGitHub(token?: string): {
|
|
14
|
+
getTemplateDirectories: (template: string, customUrl?: string) => Promise<string[]>;
|
|
15
|
+
getTemplates: (customUrl?: string) => Promise<string[]>;
|
|
16
|
+
parseGitHubUrl: typeof parseGitHubUrl;
|
|
17
|
+
};
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createGitHub = void 0;
|
|
4
|
+
const rest_1 = require("@octokit/rest");
|
|
5
|
+
const constants_1 = require("../lib/constants");
|
|
6
|
+
const parse_github_url_1 = require("../lib/utils/parse-github-url");
|
|
7
|
+
function createGitHub(token) {
|
|
8
|
+
const octokit = new rest_1.Octokit({
|
|
9
|
+
auth: token,
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* Get the directories for a template.
|
|
13
|
+
* @param template - The template to get the directories for.
|
|
14
|
+
* @param customUrl - The custom URL to get the directories for.
|
|
15
|
+
* @returns The directories for the template.
|
|
16
|
+
*/
|
|
17
|
+
async function getTemplateDirectories(template, customUrl) {
|
|
18
|
+
const repo = customUrl ? (0, parse_github_url_1.parseGitHubUrl)(customUrl) : constants_1.DEFAULT_REPO;
|
|
19
|
+
const templatePath = repo.path ? `${repo.path}/${template}` : template;
|
|
20
|
+
const { data } = await octokit.rest.repos.getContent({
|
|
21
|
+
owner: repo.owner,
|
|
22
|
+
path: templatePath,
|
|
23
|
+
ref: repo.ref,
|
|
24
|
+
repo: repo.repo,
|
|
25
|
+
});
|
|
26
|
+
if (!Array.isArray(data))
|
|
27
|
+
return [];
|
|
28
|
+
return data
|
|
29
|
+
.filter(item => item.type === 'dir' && item.name !== 'directus')
|
|
30
|
+
.map(item => item.name);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the templates for a repository.
|
|
34
|
+
* @param customUrl - The custom URL to get the templates for.
|
|
35
|
+
* @returns The templates for the repository.
|
|
36
|
+
*/
|
|
37
|
+
async function getTemplates(customUrl) {
|
|
38
|
+
const repo = customUrl ? (0, parse_github_url_1.parseGitHubUrl)(customUrl) : constants_1.DEFAULT_REPO;
|
|
39
|
+
const { data } = await octokit.rest.repos.getContent({
|
|
40
|
+
owner: repo.owner,
|
|
41
|
+
path: repo.path || '',
|
|
42
|
+
ref: repo.ref,
|
|
43
|
+
repo: repo.repo,
|
|
44
|
+
});
|
|
45
|
+
if (!Array.isArray(data))
|
|
46
|
+
return [];
|
|
47
|
+
return data
|
|
48
|
+
.filter(item => item.type === 'dir')
|
|
49
|
+
.map(item => item.name);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
getTemplateDirectories,
|
|
53
|
+
getTemplates,
|
|
54
|
+
parseGitHubUrl: parse_github_url_1.parseGitHubUrl,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
exports.createGitHub = createGitHub;
|
package/oclif.manifest.json
CHANGED
|
@@ -182,8 +182,7 @@
|
|
|
182
182
|
"description": "Extract a template from a Directus instance.",
|
|
183
183
|
"examples": [
|
|
184
184
|
"$ directus-template-cli extract",
|
|
185
|
-
"$ directus-template-cli extract -p --templateName=\"My Template\" --templateLocation=\"./my-template\" --directusToken=\"admin-token-here\" --directusUrl=\"http://localhost:8055\""
|
|
186
|
-
"$ directus-template-cli extract -p --templateName=\"My Template\" --templateLocation=\"./my-template\" --directusToken=\"admin-token-here\" --directusUrl=\"http://localhost:8055\" --excludeCollections=collection1,collection2"
|
|
185
|
+
"$ directus-template-cli extract -p --templateName=\"My Template\" --templateLocation=\"./my-template\" --directusToken=\"admin-token-here\" --directusUrl=\"http://localhost:8055\""
|
|
187
186
|
],
|
|
188
187
|
"flags": {
|
|
189
188
|
"directusToken": {
|
|
@@ -206,16 +205,6 @@
|
|
|
206
205
|
"multiple": false,
|
|
207
206
|
"type": "option"
|
|
208
207
|
},
|
|
209
|
-
"excludeCollections": {
|
|
210
|
-
"char": "e",
|
|
211
|
-
"description": "Comma-separated list of collection names to exclude from extraction",
|
|
212
|
-
"name": "excludeCollections",
|
|
213
|
-
"required": false,
|
|
214
|
-
"delimiter": ",",
|
|
215
|
-
"hasDynamicHelp": false,
|
|
216
|
-
"multiple": true,
|
|
217
|
-
"type": "option"
|
|
218
|
-
},
|
|
219
208
|
"programmatic": {
|
|
220
209
|
"char": "p",
|
|
221
210
|
"description": "Run in programmatic mode (non-interactive) for use cases such as CI/CD pipelines.",
|
|
@@ -224,14 +213,6 @@
|
|
|
224
213
|
"allowNo": false,
|
|
225
214
|
"type": "boolean"
|
|
226
215
|
},
|
|
227
|
-
"skipFiles": {
|
|
228
|
-
"char": "f",
|
|
229
|
-
"description": "Skip extracting files and assets",
|
|
230
|
-
"name": "skipFiles",
|
|
231
|
-
"required": false,
|
|
232
|
-
"allowNo": false,
|
|
233
|
-
"type": "boolean"
|
|
234
|
-
},
|
|
235
216
|
"templateLocation": {
|
|
236
217
|
"dependsOn": [
|
|
237
218
|
"programmatic"
|
|
@@ -297,7 +278,86 @@
|
|
|
297
278
|
"commands",
|
|
298
279
|
"extract.js"
|
|
299
280
|
]
|
|
281
|
+
},
|
|
282
|
+
"init": {
|
|
283
|
+
"aliases": [],
|
|
284
|
+
"args": {
|
|
285
|
+
"directory": {
|
|
286
|
+
"default": ".",
|
|
287
|
+
"description": "Directory to create the project in",
|
|
288
|
+
"name": "directory",
|
|
289
|
+
"required": false
|
|
290
|
+
}
|
|
291
|
+
},
|
|
292
|
+
"description": "Initialize a new Directus + Frontend monorepo using official or community starters.",
|
|
293
|
+
"examples": [
|
|
294
|
+
"$ directus-template-cli init",
|
|
295
|
+
"$ directus-template-cli init my-project",
|
|
296
|
+
"$ directus-template-cli init --frontend=nextjs --template=simple-cms --programmatic",
|
|
297
|
+
"$ directus-template-cli init my-project --frontend=nextjs --template=simple-cms --programmatic"
|
|
298
|
+
],
|
|
299
|
+
"flags": {
|
|
300
|
+
"frontend": {
|
|
301
|
+
"description": "Frontend framework to use (e.g., nextjs, nuxt, astro)",
|
|
302
|
+
"name": "frontend",
|
|
303
|
+
"hasDynamicHelp": false,
|
|
304
|
+
"multiple": false,
|
|
305
|
+
"type": "option"
|
|
306
|
+
},
|
|
307
|
+
"gitInit": {
|
|
308
|
+
"aliases": [
|
|
309
|
+
"git-init"
|
|
310
|
+
],
|
|
311
|
+
"description": "Initialize a new Git repository",
|
|
312
|
+
"name": "gitInit",
|
|
313
|
+
"allowNo": true,
|
|
314
|
+
"type": "boolean"
|
|
315
|
+
},
|
|
316
|
+
"installDeps": {
|
|
317
|
+
"aliases": [
|
|
318
|
+
"install-deps"
|
|
319
|
+
],
|
|
320
|
+
"description": "Install dependencies automatically",
|
|
321
|
+
"name": "installDeps",
|
|
322
|
+
"allowNo": true,
|
|
323
|
+
"type": "boolean"
|
|
324
|
+
},
|
|
325
|
+
"overrideDir": {
|
|
326
|
+
"description": "Override the default directory",
|
|
327
|
+
"name": "overrideDir",
|
|
328
|
+
"allowNo": false,
|
|
329
|
+
"type": "boolean"
|
|
330
|
+
},
|
|
331
|
+
"programmatic": {
|
|
332
|
+
"char": "p",
|
|
333
|
+
"description": "Run in programmatic mode (non-interactive)",
|
|
334
|
+
"name": "programmatic",
|
|
335
|
+
"allowNo": false,
|
|
336
|
+
"type": "boolean"
|
|
337
|
+
},
|
|
338
|
+
"template": {
|
|
339
|
+
"description": "Template name (e.g., simple-cms) or GitHub URL (e.g., https://github.com/directus-labs/starters/tree/main/simple-cms)",
|
|
340
|
+
"name": "template",
|
|
341
|
+
"hasDynamicHelp": false,
|
|
342
|
+
"multiple": false,
|
|
343
|
+
"type": "option"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
"hasDynamicHelp": false,
|
|
347
|
+
"hiddenAliases": [],
|
|
348
|
+
"id": "init",
|
|
349
|
+
"pluginAlias": "directus-template-cli",
|
|
350
|
+
"pluginName": "directus-template-cli",
|
|
351
|
+
"pluginType": "core",
|
|
352
|
+
"strict": true,
|
|
353
|
+
"enableJsonFlag": false,
|
|
354
|
+
"isESM": false,
|
|
355
|
+
"relativePath": [
|
|
356
|
+
"dist",
|
|
357
|
+
"commands",
|
|
358
|
+
"init.js"
|
|
359
|
+
]
|
|
300
360
|
}
|
|
301
361
|
},
|
|
302
|
-
"version": "0.
|
|
362
|
+
"version": "0.7.0-beta.1"
|
|
303
363
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directus-template-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-beta.1",
|
|
4
4
|
"description": "CLI Utility for applying templates to a Directus instance.",
|
|
5
5
|
"author": "bryantgillespie @bryantgillespie",
|
|
6
6
|
"bin": {
|
|
@@ -21,12 +21,17 @@
|
|
|
21
21
|
"@oclif/core": "^3.18.1",
|
|
22
22
|
"@oclif/plugin-help": "^6.0.12",
|
|
23
23
|
"@oclif/plugin-plugins": "^4.1.22",
|
|
24
|
+
"@octokit/rest": "^21.1.1",
|
|
24
25
|
"bottleneck": "^2.19.5",
|
|
25
26
|
"defu": "^6.1.4",
|
|
26
27
|
"dotenv": "^16.4.1",
|
|
28
|
+
"execa": "^9.5.2",
|
|
27
29
|
"formdata-node": "^6.0.3",
|
|
28
30
|
"giget": "^1.2.1",
|
|
31
|
+
"glob": "^11.0.1",
|
|
29
32
|
"inquirer": "^8.2.5",
|
|
33
|
+
"nypm": "^0.5.2",
|
|
34
|
+
"pkg-types": "^1.3.1",
|
|
30
35
|
"slugify": "^1.6.6"
|
|
31
36
|
},
|
|
32
37
|
"devDependencies": {
|
|
@@ -64,7 +69,8 @@
|
|
|
64
69
|
"posttest": "npm run lint",
|
|
65
70
|
"prepack": "npm run build && oclif manifest && oclif readme",
|
|
66
71
|
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
|
|
67
|
-
"version": "oclif readme && git add README.md"
|
|
72
|
+
"version": "oclif readme && git add README.md",
|
|
73
|
+
"run": "./bin/run"
|
|
68
74
|
},
|
|
69
75
|
"engines": {
|
|
70
76
|
"node": ">=18.0.0"
|
|
@@ -77,5 +83,6 @@
|
|
|
77
83
|
"directus cms",
|
|
78
84
|
"directus cli"
|
|
79
85
|
],
|
|
80
|
-
"types": "dist/index.d.ts"
|
|
86
|
+
"types": "dist/index.d.ts",
|
|
87
|
+
"packageManager": "pnpm@9.15.4+sha512.b2dc20e2fc72b3e18848459b37359a32064663e5627a51e4c74b2c29dd8e8e0491483c3abb40789cfd578bf362fb6ba8261b05f0387d76792ed6e23ea3b1b6a0"
|
|
81
88
|
}
|