directus-template-cli 0.6.0-beta.2 → 0.7.0-beta.2
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 +37 -0
- package/dist/commands/init.js +178 -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 +14 -0
- package/dist/lib/init/index.js +167 -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/animated-bunny.d.ts +2 -0
- package/dist/lib/utils/animated-bunny.js +71 -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 +137 -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 +14 -3
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.init = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const prompts_1 = require("@clack/prompts");
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
8
|
+
const giget_1 = require("giget");
|
|
9
|
+
const glob_1 = require("glob");
|
|
10
|
+
const node_fs_1 = tslib_1.__importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
12
|
+
const nypm_1 = require("nypm");
|
|
13
|
+
const apply_1 = tslib_1.__importDefault(require("../../commands/apply"));
|
|
14
|
+
const docker_1 = require("../../services/docker");
|
|
15
|
+
const catch_error_1 = tslib_1.__importDefault(require("../utils/catch-error"));
|
|
16
|
+
const parse_github_url_1 = require("../utils/parse-github-url");
|
|
17
|
+
const config_1 = require("./config");
|
|
18
|
+
async function init(dir, flags) {
|
|
19
|
+
// Check target directory
|
|
20
|
+
const shouldForce = flags.overrideDir;
|
|
21
|
+
if (node_fs_1.default.existsSync(dir) && !shouldForce) {
|
|
22
|
+
throw new Error('Directory already exists. Use --override-dir to override.');
|
|
23
|
+
}
|
|
24
|
+
const frontendDir = node_path_1.default.join(dir, flags.frontend);
|
|
25
|
+
const directusDir = node_path_1.default.join(dir, 'directus');
|
|
26
|
+
let template;
|
|
27
|
+
try {
|
|
28
|
+
// Download the template from GitHub
|
|
29
|
+
const parsedUrl = (0, parse_github_url_1.parseGitHubUrl)(flags.template);
|
|
30
|
+
template = await (0, giget_1.downloadTemplate)((0, parse_github_url_1.createGigetString)(parsedUrl), {
|
|
31
|
+
dir,
|
|
32
|
+
force: shouldForce,
|
|
33
|
+
});
|
|
34
|
+
// Cleanup the template
|
|
35
|
+
if (flags.frontend) {
|
|
36
|
+
// Ensure directus directory exists before cleaning up
|
|
37
|
+
if (!node_fs_1.default.existsSync(directusDir)) {
|
|
38
|
+
node_fs_1.default.mkdirSync(directusDir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
// Read and parse package.json
|
|
41
|
+
const packageJsonPath = node_path_1.default.join(dir, 'package.json');
|
|
42
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
43
|
+
throw new Error('package.json not found in template');
|
|
44
|
+
}
|
|
45
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
|
|
46
|
+
const templateConfig = packageJson['directus:template'];
|
|
47
|
+
// Get all frontend paths from the configuration
|
|
48
|
+
const frontendPaths = Object.values((templateConfig === null || templateConfig === void 0 ? void 0 : templateConfig.frontends) || {})
|
|
49
|
+
.map(frontend => frontend.path.replace(/^\.\//, ''))
|
|
50
|
+
.filter(path => path !== flags.frontend); // Exclude the selected frontend
|
|
51
|
+
// Remove unused frontend directories
|
|
52
|
+
for (const frontendPath of frontendPaths) {
|
|
53
|
+
const pathToRemove = node_path_1.default.join(dir, frontendPath);
|
|
54
|
+
if (node_fs_1.default.existsSync(pathToRemove)) {
|
|
55
|
+
node_fs_1.default.rmSync(pathToRemove, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Find and copy all .env.example files
|
|
60
|
+
const envFiles = glob_1.glob.sync(node_path_1.default.join(dir, '**', '.env.example'));
|
|
61
|
+
for (const file of envFiles) {
|
|
62
|
+
const envFile = file.replace('.env.example', '.env');
|
|
63
|
+
node_fs_1.default.copyFileSync(file, envFile);
|
|
64
|
+
}
|
|
65
|
+
// Start Directus and apply template only if directus directory exists
|
|
66
|
+
if (node_fs_1.default.existsSync(directusDir)) {
|
|
67
|
+
// Initialize Docker service
|
|
68
|
+
const dockerService = (0, docker_1.createDocker)(config_1.DOCKER_CONFIG);
|
|
69
|
+
// Check if Docker is installed
|
|
70
|
+
const dockerStatus = await dockerService.checkDocker();
|
|
71
|
+
if (!dockerStatus.installed || !dockerStatus.running) {
|
|
72
|
+
throw new Error(dockerStatus.message);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
await dockerService.startContainers(directusDir);
|
|
76
|
+
const healthCheckUrl = `${config_1.DIRECTUS_CONFIG.url}:${config_1.DIRECTUS_CONFIG.port}${config_1.DOCKER_CONFIG.healthCheckEndpoint}`;
|
|
77
|
+
await dockerService.waitForHealthy(healthCheckUrl);
|
|
78
|
+
const templatePath = node_path_1.default.join(directusDir, 'template');
|
|
79
|
+
// const s = spinner()
|
|
80
|
+
// s.start(`Attempting to apply template from: ${templatePath}`)
|
|
81
|
+
// ux.log(`Attempting to apply template from: ${templatePath}`)
|
|
82
|
+
await apply_1.default.run([
|
|
83
|
+
'--directusUrl=http://localhost:8055',
|
|
84
|
+
'-p',
|
|
85
|
+
'--userEmail=admin@example.com',
|
|
86
|
+
'--userPassword=d1r3ctu5',
|
|
87
|
+
'--templateLocation=' + templatePath,
|
|
88
|
+
]);
|
|
89
|
+
// s.stop('Template applied!')
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
core_1.ux.error('Failed to start Directus containers or apply template');
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Install dependencies for frontend if it exists
|
|
97
|
+
if (flags.installDeps && node_fs_1.default.existsSync(frontendDir)) {
|
|
98
|
+
const s = (0, prompts_1.spinner)();
|
|
99
|
+
s.start('Installing dependencies');
|
|
100
|
+
// ux.action.start('Installing dependencies')
|
|
101
|
+
try {
|
|
102
|
+
const packageManager = await (0, nypm_1.detectPackageManager)(frontendDir);
|
|
103
|
+
await (0, nypm_1.installDependencies)({
|
|
104
|
+
cwd: frontendDir,
|
|
105
|
+
packageManager,
|
|
106
|
+
silent: true,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
core_1.ux.warn('Failed to install dependencies');
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
// ux.action.stop()
|
|
114
|
+
s.stop('Dependencies installed!');
|
|
115
|
+
}
|
|
116
|
+
// Initialize Git repo
|
|
117
|
+
if (flags.gitInit) {
|
|
118
|
+
const s = (0, prompts_1.spinner)();
|
|
119
|
+
s.start('Initializing git repository');
|
|
120
|
+
// ux.action.start('Initializing git repository')
|
|
121
|
+
await initGit(dir);
|
|
122
|
+
// ux.action.stop()
|
|
123
|
+
s.stop('Git repository initialized!');
|
|
124
|
+
}
|
|
125
|
+
// Finishing up
|
|
126
|
+
const relativeDir = node_path_1.default.relative(process.cwd(), dir);
|
|
127
|
+
const nextSteps = `- Directus is running on http://localhost:8055 \n- Navigate to your project directory using ${chalk_1.default.cyan(`cd ${relativeDir}`)} and start developing! \n- Review the \`./README.md\` file for next steps.`;
|
|
128
|
+
(0, prompts_1.note)(nextSteps, 'Next Steps');
|
|
129
|
+
// ux.log('You\'ll find the following directories in your project:')
|
|
130
|
+
// ux.log('• directus')
|
|
131
|
+
// ux.log(`• ${flags.frontend}`)
|
|
132
|
+
(0, prompts_1.outro)(`Problems? Join the community on Discord at ${chalk_1.default.underline(chalk_1.default.cyan('https://directus.chat'))}`);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
(0, catch_error_1.default)(error, {
|
|
136
|
+
context: { dir, flags, function: 'init' },
|
|
137
|
+
fatal: true,
|
|
138
|
+
logToFile: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
directusDir,
|
|
143
|
+
frontendDir,
|
|
144
|
+
template,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
exports.init = init;
|
|
148
|
+
/**
|
|
149
|
+
* Initialize a git repository
|
|
150
|
+
* @param targetDir - The directory to initialize the git repository in
|
|
151
|
+
* @returns void
|
|
152
|
+
*/
|
|
153
|
+
async function initGit(targetDir) {
|
|
154
|
+
try {
|
|
155
|
+
// ux.action.start('Initializing git repository')
|
|
156
|
+
const { execa } = await Promise.resolve().then(() => tslib_1.__importStar(require('execa')));
|
|
157
|
+
await execa('git', ['init'], { cwd: targetDir });
|
|
158
|
+
// ux.action.stop()
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
(0, catch_error_1.default)(error, {
|
|
162
|
+
context: { function: 'initGit', targetDir },
|
|
163
|
+
fatal: false,
|
|
164
|
+
logToFile: true,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -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,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.animatedBunny = exports.RANDOM_SAYINGS = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const log_update_1 = tslib_1.__importDefault(require("log-update"));
|
|
7
|
+
const constants_1 = require("../constants");
|
|
8
|
+
exports.RANDOM_SAYINGS = [
|
|
9
|
+
'One does not simply write backends...',
|
|
10
|
+
'I don\'t always test my code, but when I do, I use production.',
|
|
11
|
+
'A wild Directus appears!',
|
|
12
|
+
'Error 418: I\'m a teapot. Just kidding, I\'m Directus and I\'ve got your backend covered.',
|
|
13
|
+
'I\'ll fix it later. Narrator: They didn\'t fix it later.',
|
|
14
|
+
];
|
|
15
|
+
async function animatedBunny(customMessage) {
|
|
16
|
+
const saying = customMessage || exports.RANDOM_SAYINGS[Math.floor(Math.random() * exports.RANDOM_SAYINGS.length)];
|
|
17
|
+
let typedSaying = '';
|
|
18
|
+
let blinkState = true;
|
|
19
|
+
let charIndex = 0;
|
|
20
|
+
let isCleanedUp = false;
|
|
21
|
+
const cleanup = () => {
|
|
22
|
+
if (isCleanedUp)
|
|
23
|
+
return;
|
|
24
|
+
isCleanedUp = true;
|
|
25
|
+
clearInterval(animation);
|
|
26
|
+
clearInterval(typing);
|
|
27
|
+
log_update_1.default.done();
|
|
28
|
+
};
|
|
29
|
+
// Ensure cleanup on process exit
|
|
30
|
+
process.on('exit', cleanup);
|
|
31
|
+
process.on('SIGINT', () => {
|
|
32
|
+
cleanup();
|
|
33
|
+
process.exit(0);
|
|
34
|
+
});
|
|
35
|
+
const updateFrame = () => {
|
|
36
|
+
if (isCleanedUp)
|
|
37
|
+
return;
|
|
38
|
+
const eyes = blinkState ? '• •' : '- -';
|
|
39
|
+
const frame = `
|
|
40
|
+
(\\(\\
|
|
41
|
+
( ${eyes}) ${chalk_1.default.dim('.')}${chalk_1.default.hex(constants_1.DIRECTUS_PINK).visible(`"${typedSaying}"`)}
|
|
42
|
+
o_(")(")
|
|
43
|
+
`;
|
|
44
|
+
(0, log_update_1.default)(frame);
|
|
45
|
+
};
|
|
46
|
+
const animation = setInterval(() => {
|
|
47
|
+
blinkState = !blinkState;
|
|
48
|
+
updateFrame();
|
|
49
|
+
}, 500);
|
|
50
|
+
const typing = setInterval(() => {
|
|
51
|
+
if (charIndex < saying.length) {
|
|
52
|
+
typedSaying += saying[charIndex];
|
|
53
|
+
charIndex++;
|
|
54
|
+
updateFrame();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
clearInterval(typing);
|
|
58
|
+
}
|
|
59
|
+
}, 10);
|
|
60
|
+
try {
|
|
61
|
+
// Run the animation for the duration of typing plus 1 second
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, saying.length * 10 + 1000));
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
cleanup();
|
|
66
|
+
// Remove the event listeners
|
|
67
|
+
process.removeListener('exit', cleanup);
|
|
68
|
+
process.removeListener('SIGINT', cleanup);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.animatedBunny = animatedBunny;
|
|
@@ -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,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDocker = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const prompts_1 = require("@clack/prompts");
|
|
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
|
+
// ux.action.start('Starting Docker containers')
|
|
47
|
+
const s = (0, prompts_1.spinner)();
|
|
48
|
+
s.start('Starting Docker containers');
|
|
49
|
+
return (0, execa_1.execa)('docker-compose', ['up', '-d'], {
|
|
50
|
+
cwd,
|
|
51
|
+
// stdio: 'inherit',
|
|
52
|
+
}).then(() => {
|
|
53
|
+
s.stop('Docker containers running!');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
(0, catch_error_1.default)(error, {
|
|
58
|
+
context: { cwd, function: 'startContainers' },
|
|
59
|
+
fatal: true,
|
|
60
|
+
logToFile: true,
|
|
61
|
+
});
|
|
62
|
+
return Promise.reject(error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Stop Docker containers
|
|
67
|
+
* @param {string} cwd - The current working directory
|
|
68
|
+
* @returns {Promise<void>} - Returns nothing
|
|
69
|
+
*/
|
|
70
|
+
function stopContainers(cwd) {
|
|
71
|
+
try {
|
|
72
|
+
return (0, execa_1.execa)('docker-compose', ['down'], {
|
|
73
|
+
cwd,
|
|
74
|
+
// stdio: 'inherit',
|
|
75
|
+
}).then(() => { });
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
(0, catch_error_1.default)(error, {
|
|
79
|
+
context: { cwd, function: 'stopContainers' },
|
|
80
|
+
fatal: false,
|
|
81
|
+
logToFile: true,
|
|
82
|
+
});
|
|
83
|
+
return Promise.reject(error);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wait for service health check to pass
|
|
88
|
+
* @param {DockerConfig} config - The Docker configuration
|
|
89
|
+
* @returns {Promise<boolean>} - Returns true if the service is healthy, false otherwise
|
|
90
|
+
*/
|
|
91
|
+
function createWaitForHealthy(config) {
|
|
92
|
+
async function waitForHealthy(healthCheckUrl) {
|
|
93
|
+
const s = (0, prompts_1.spinner)();
|
|
94
|
+
s.start('Waiting for Directus to be ready.');
|
|
95
|
+
try {
|
|
96
|
+
await (0, wait_1.waitFor)(async () => {
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(healthCheckUrl);
|
|
99
|
+
return response.ok;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}, {
|
|
105
|
+
errorMessage: 'Service failed to become healthy',
|
|
106
|
+
interval: config.interval,
|
|
107
|
+
maxAttempts: config.maxAttempts,
|
|
108
|
+
});
|
|
109
|
+
s.stop('Directus is ready!');
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
s.stop('');
|
|
114
|
+
(0, catch_error_1.default)(error, {
|
|
115
|
+
context: { function: 'waitForHealthy', url: healthCheckUrl },
|
|
116
|
+
fatal: true,
|
|
117
|
+
logToFile: true,
|
|
118
|
+
});
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return waitForHealthy;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Create a Docker service instance
|
|
126
|
+
* @param {DockerConfig} config - The Docker configuration
|
|
127
|
+
* @returns {DockerService} - Returns a Docker service instance
|
|
128
|
+
*/
|
|
129
|
+
function createDocker(config) {
|
|
130
|
+
return {
|
|
131
|
+
checkDocker,
|
|
132
|
+
startContainers,
|
|
133
|
+
stopContainers,
|
|
134
|
+
waitForHealthy: createWaitForHealthy(config),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
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;
|