directus-template-cli 0.7.0-beta.5 → 0.7.0-beta.6
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/bin/dev.js +3 -0
- package/dist/commands/apply.js +9 -6
- package/dist/commands/base.d.ts +15 -0
- package/dist/commands/base.js +45 -0
- package/dist/commands/extract.d.ts +9 -6
- package/dist/commands/extract.js +4 -13
- package/dist/commands/init.d.ts +14 -9
- package/dist/commands/init.js +140 -75
- package/dist/flags/common.d.ts +1 -0
- package/dist/flags/common.js +5 -0
- package/dist/lib/constants.d.ts +3 -5
- package/dist/lib/constants.js +3 -5
- package/dist/lib/init/config.js +1 -1
- package/dist/lib/init/index.d.ts +5 -9
- package/dist/lib/init/index.js +71 -46
- package/dist/lib/load/index.d.ts +1 -12
- package/dist/lib/types.d.ts +18 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/utils/auth.d.ts +10 -8
- package/dist/lib/utils/auth.js +17 -7
- package/dist/lib/utils/parse-github-url.d.ts +10 -5
- package/dist/lib/utils/parse-github-url.js +80 -40
- package/dist/lib/utils/sanitize-flags.d.ts +3 -0
- package/dist/lib/utils/sanitize-flags.js +4 -0
- package/dist/lib/utils/template-config.d.ts +16 -0
- package/dist/lib/utils/template-config.js +34 -0
- package/dist/services/docker.js +57 -3
- package/dist/services/github.js +45 -10
- package/dist/services/posthog.d.ts +37 -0
- package/dist/services/posthog.js +104 -0
- package/oclif.manifest.json +29 -10
- package/package.json +4 -3
- package/bin/dev +0 -16
- package/bin/run +0 -4
- package/dist/lib/init.d.ts +0 -1
- package/dist/lib/init.js +0 -2
package/dist/lib/init/index.js
CHANGED
|
@@ -7,57 +7,88 @@ import { glob } from 'glob';
|
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import { detectPackageManager, installDependencies } from 'nypm';
|
|
9
9
|
import path from 'pathe';
|
|
10
|
+
import dotenv from 'dotenv';
|
|
10
11
|
import ApplyCommand from '../../commands/apply.js';
|
|
11
12
|
import { createDocker } from '../../services/docker.js';
|
|
12
13
|
import catchError from '../utils/catch-error.js';
|
|
13
14
|
import { createGigetString, parseGitHubUrl } from '../utils/parse-github-url.js';
|
|
14
|
-
import {
|
|
15
|
-
|
|
15
|
+
import { readTemplateConfig } from '../utils/template-config.js';
|
|
16
|
+
import { DOCKER_CONFIG } from './config.js';
|
|
17
|
+
export async function init({ dir, flags }) {
|
|
16
18
|
// Check target directory
|
|
17
19
|
const shouldForce = flags.overrideDir;
|
|
18
20
|
if (fs.existsSync(dir) && !shouldForce) {
|
|
19
21
|
throw new Error('Directory already exists. Use --override-dir to override.');
|
|
20
22
|
}
|
|
21
|
-
|
|
23
|
+
// If template is a URL, we need to handle it differently
|
|
24
|
+
const isDirectUrl = flags.template?.startsWith('http');
|
|
22
25
|
const directusDir = path.join(dir, 'directus');
|
|
23
26
|
let template;
|
|
27
|
+
let packageManager = null;
|
|
24
28
|
try {
|
|
25
29
|
// Download the template from GitHub
|
|
26
30
|
const parsedUrl = parseGitHubUrl(flags.template);
|
|
31
|
+
// If it's a direct URL, we download the entire repository
|
|
32
|
+
// Otherwise, we use the template from the starters repo
|
|
27
33
|
template = await downloadTemplate(createGigetString(parsedUrl), {
|
|
28
34
|
dir,
|
|
29
35
|
force: shouldForce,
|
|
30
36
|
});
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
// For direct URLs, we need to check if there's a directus directory
|
|
38
|
+
// If not, assume the entire repo is a directus template
|
|
39
|
+
if (isDirectUrl) {
|
|
34
40
|
if (!fs.existsSync(directusDir)) {
|
|
41
|
+
// Move all files to directus directory
|
|
35
42
|
fs.mkdirSync(directusDir, { recursive: true });
|
|
43
|
+
const files = fs.readdirSync(dir);
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
if (file !== 'directus') {
|
|
46
|
+
fs.renameSync(path.join(dir, file), path.join(directusDir, file));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
36
49
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
50
|
+
}
|
|
51
|
+
// Read template configuration
|
|
52
|
+
const templateInfo = readTemplateConfig(dir);
|
|
53
|
+
let frontendDir;
|
|
54
|
+
// Handle frontends based on template configuration
|
|
55
|
+
if (flags.frontend && templateInfo) {
|
|
56
|
+
// Find the selected frontend in the configuration
|
|
57
|
+
const selectedFrontend = templateInfo.frontendOptions.find(f => f.id === flags.frontend);
|
|
58
|
+
if (!selectedFrontend) {
|
|
59
|
+
throw new Error(`Frontend "${flags.frontend}" not found in template configuration`);
|
|
41
60
|
}
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (const frontendPath of frontendPaths) {
|
|
50
|
-
const pathToRemove = path.join(dir, frontendPath);
|
|
51
|
-
if (fs.existsSync(pathToRemove)) {
|
|
52
|
-
fs.rmSync(pathToRemove, { recursive: true });
|
|
61
|
+
// Remove all frontend directories except the selected one
|
|
62
|
+
for (const frontend of templateInfo.frontendOptions) {
|
|
63
|
+
if (frontend.id !== flags.frontend) {
|
|
64
|
+
const pathToRemove = path.join(dir, frontend.path);
|
|
65
|
+
if (fs.existsSync(pathToRemove)) {
|
|
66
|
+
fs.rmSync(pathToRemove, { recursive: true });
|
|
67
|
+
}
|
|
53
68
|
}
|
|
54
69
|
}
|
|
70
|
+
// Move the selected frontend to the correct location if needed
|
|
71
|
+
frontendDir = path.join(dir, selectedFrontend.path);
|
|
72
|
+
if (frontendDir !== path.join(dir, flags.frontend)) {
|
|
73
|
+
fs.renameSync(frontendDir, path.join(dir, flags.frontend));
|
|
74
|
+
frontendDir = path.join(dir, flags.frontend);
|
|
75
|
+
}
|
|
55
76
|
}
|
|
77
|
+
const directusInfo = {
|
|
78
|
+
email: '',
|
|
79
|
+
password: '',
|
|
80
|
+
url: '',
|
|
81
|
+
};
|
|
56
82
|
// Find and copy all .env.example files
|
|
57
83
|
const envFiles = glob.sync(path.join(dir, '**', '.env.example'));
|
|
58
84
|
for (const file of envFiles) {
|
|
59
85
|
const envFile = file.replace('.env.example', '.env');
|
|
60
86
|
fs.copyFileSync(file, envFile);
|
|
87
|
+
// Read default Directus login info from .env
|
|
88
|
+
const parsedEnv = dotenv.parse(fs.readFileSync(file, 'utf8'));
|
|
89
|
+
directusInfo.email = parsedEnv.ADMIN_EMAIL;
|
|
90
|
+
directusInfo.password = parsedEnv.ADMIN_PASSWORD;
|
|
91
|
+
directusInfo.url = parsedEnv.PUBLIC_URL;
|
|
61
92
|
}
|
|
62
93
|
// Start Directus and apply template only if directus directory exists
|
|
63
94
|
if (fs.existsSync(directusDir)) {
|
|
@@ -70,12 +101,10 @@ export async function init(dir, flags) {
|
|
|
70
101
|
}
|
|
71
102
|
try {
|
|
72
103
|
await dockerService.startContainers(directusDir);
|
|
73
|
-
const healthCheckUrl = `${
|
|
104
|
+
const healthCheckUrl = `${directusInfo.url}${DOCKER_CONFIG.healthCheckEndpoint}`;
|
|
74
105
|
await dockerService.waitForHealthy(healthCheckUrl);
|
|
75
106
|
const templatePath = path.join(directusDir, 'template');
|
|
76
|
-
|
|
77
|
-
// s.start(`Attempting to apply template from: ${templatePath}`)
|
|
78
|
-
// ux.stdout(`Attempting to apply template from: ${templatePath}`)
|
|
107
|
+
ux.stdout(`Attempting to apply template from: ${templatePath}`);
|
|
79
108
|
await ApplyCommand.run([
|
|
80
109
|
'--directusUrl=http://localhost:8055',
|
|
81
110
|
'-p',
|
|
@@ -83,50 +112,48 @@ export async function init(dir, flags) {
|
|
|
83
112
|
'--userPassword=d1r3ctu5',
|
|
84
113
|
`--templateLocation=${templatePath}`,
|
|
85
114
|
]);
|
|
86
|
-
// s.stop('Template applied!')
|
|
87
115
|
}
|
|
88
116
|
catch (error) {
|
|
89
117
|
ux.error('Failed to start Directus containers or apply template');
|
|
90
118
|
throw error;
|
|
91
119
|
}
|
|
92
120
|
}
|
|
93
|
-
// Install dependencies
|
|
94
|
-
if (flags.installDeps
|
|
121
|
+
// Install dependencies if requested
|
|
122
|
+
if (flags.installDeps) {
|
|
95
123
|
const s = spinner();
|
|
96
124
|
s.start('Installing dependencies');
|
|
97
|
-
// ux.action.start('Installing dependencies')
|
|
98
125
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
126
|
+
if (fs.existsSync(frontendDir)) {
|
|
127
|
+
packageManager = await detectPackageManager(frontendDir);
|
|
128
|
+
await installDependencies({
|
|
129
|
+
cwd: frontendDir,
|
|
130
|
+
packageManager,
|
|
131
|
+
silent: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
105
134
|
}
|
|
106
135
|
catch (error) {
|
|
107
136
|
ux.warn('Failed to install dependencies');
|
|
108
137
|
throw error;
|
|
109
138
|
}
|
|
110
|
-
// ux.action.stop()
|
|
111
139
|
s.stop('Dependencies installed!');
|
|
112
140
|
}
|
|
113
141
|
// Initialize Git repo
|
|
114
142
|
if (flags.gitInit) {
|
|
115
143
|
const s = spinner();
|
|
116
144
|
s.start('Initializing git repository');
|
|
117
|
-
// ux.action.start('Initializing git repository')
|
|
118
145
|
await initGit(dir);
|
|
119
|
-
// ux.action.stop()
|
|
120
146
|
s.stop('Git repository initialized!');
|
|
121
147
|
}
|
|
122
148
|
// Finishing up
|
|
123
149
|
const relativeDir = path.relative(process.cwd(), dir);
|
|
124
|
-
const
|
|
150
|
+
const directusText = `- Directus is running on ${directusInfo.url ?? 'http://localhost:8055'}. You can login with the email: ${chalk.cyan(directusInfo.email)} and password: ${chalk.cyan(directusInfo.password)}. \n`;
|
|
151
|
+
const frontendText = flags.frontend ? `- To start the frontend, run ${chalk.cyan(`cd ${flags.frontend}`)} and then ${chalk.cyan(`${packageManager?.name} run dev`)}. \n` : '';
|
|
152
|
+
const projectText = `- Navigate to your project directory using ${chalk.cyan(`cd ${relativeDir}`)}. \n`;
|
|
153
|
+
const readmeText = '- Review the \`./README.md\` file for more information and next steps.';
|
|
154
|
+
const nextSteps = chalk.white(`${directusText}${projectText}${frontendText}${readmeText}`);
|
|
125
155
|
note(nextSteps, 'Next Steps');
|
|
126
|
-
|
|
127
|
-
// ux.stdout('• directus')
|
|
128
|
-
// ux.stdout(`• ${flags.frontend}`)
|
|
129
|
-
outro(`Problems? Join the community on Discord at ${chalk.underline(chalk.cyan('https://directus.chat'))}`);
|
|
156
|
+
outro(`Problems or questions? Hop into the community on Discord at ${chalk.underline(chalk.cyan('https://directus.chat'))}`);
|
|
130
157
|
}
|
|
131
158
|
catch (error) {
|
|
132
159
|
catchError(error, {
|
|
@@ -137,7 +164,7 @@ export async function init(dir, flags) {
|
|
|
137
164
|
}
|
|
138
165
|
return {
|
|
139
166
|
directusDir,
|
|
140
|
-
frontendDir,
|
|
167
|
+
frontendDir: flags.frontend ? path.join(dir, flags.frontend) : undefined,
|
|
141
168
|
template,
|
|
142
169
|
};
|
|
143
170
|
}
|
|
@@ -148,9 +175,7 @@ export async function init(dir, flags) {
|
|
|
148
175
|
*/
|
|
149
176
|
async function initGit(targetDir) {
|
|
150
177
|
try {
|
|
151
|
-
// ux.action.start('Initializing git repository')
|
|
152
178
|
await execa('git', ['init'], { cwd: targetDir });
|
|
153
|
-
// ux.action.stop()
|
|
154
179
|
}
|
|
155
180
|
catch (error) {
|
|
156
181
|
catchError(error, {
|
package/dist/lib/load/index.d.ts
CHANGED
|
@@ -1,13 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
content: boolean;
|
|
3
|
-
dashboards: boolean;
|
|
4
|
-
extensions: boolean;
|
|
5
|
-
files: boolean;
|
|
6
|
-
flows: boolean;
|
|
7
|
-
permissions: boolean;
|
|
8
|
-
schema: boolean;
|
|
9
|
-
settings: boolean;
|
|
10
|
-
users: boolean;
|
|
11
|
-
}
|
|
1
|
+
import type { ApplyFlags } from './apply-flags.js';
|
|
12
2
|
export default function apply(dir: string, flags: ApplyFlags): Promise<{}>;
|
|
13
|
-
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface DirectusTemplateFrontend {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
}
|
|
5
|
+
export interface DirectusTemplateConfig {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
template: string;
|
|
9
|
+
frontends: {
|
|
10
|
+
[key: string]: DirectusTemplateFrontend;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface TemplatePackageJson {
|
|
14
|
+
name: string;
|
|
15
|
+
version: string;
|
|
16
|
+
description: string;
|
|
17
|
+
'directus:template'?: DirectusTemplateConfig;
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/lib/utils/auth.d.ts
CHANGED
|
@@ -8,21 +8,23 @@ interface AuthFlags {
|
|
|
8
8
|
* Get the Directus URL from the user
|
|
9
9
|
* @returns The Directus URL
|
|
10
10
|
*/
|
|
11
|
-
export declare function getDirectusUrl(): Promise<string
|
|
11
|
+
export declare function getDirectusUrl(): Promise<string>;
|
|
12
12
|
/**
|
|
13
13
|
* Get the Directus token from the user
|
|
14
14
|
* @param directusUrl - The Directus URL
|
|
15
15
|
* @returns The Directus token
|
|
16
16
|
*/
|
|
17
|
-
export declare function getDirectusToken(directusUrl: string): Promise<string
|
|
17
|
+
export declare function getDirectusToken(directusUrl: string): Promise<string>;
|
|
18
18
|
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
* Initialize the Directus API with the provided flags and log in the user
|
|
20
|
+
* @param flags - The validated ApplyFlags
|
|
21
|
+
* @returns {Promise<void>} - Returns nothing
|
|
22
|
+
*/
|
|
22
23
|
export declare function initializeDirectusApi(flags: AuthFlags): Promise<void>;
|
|
23
24
|
/**
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
* Validate the authentication flags
|
|
26
|
+
* @param flags - The AuthFlags
|
|
27
|
+
* @returns {void} - Errors if the flags are invalid
|
|
28
|
+
*/
|
|
27
29
|
export declare function validateAuthFlags(flags: AuthFlags): void;
|
|
28
30
|
export {};
|
package/dist/lib/utils/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readMe } from '@directus/sdk';
|
|
2
|
-
import { text } from '@clack/prompts';
|
|
2
|
+
import { text, log, isCancel } from '@clack/prompts';
|
|
3
3
|
import { ux } from '@oclif/core';
|
|
4
4
|
import { api } from '../sdk.js';
|
|
5
5
|
import catchError from './catch-error.js';
|
|
@@ -13,6 +13,10 @@ export async function getDirectusUrl() {
|
|
|
13
13
|
placeholder: 'http://localhost:8055',
|
|
14
14
|
message: 'What is your Directus URL?',
|
|
15
15
|
});
|
|
16
|
+
if (isCancel(directusUrl)) {
|
|
17
|
+
log.info('Exiting...');
|
|
18
|
+
ux.exit(0);
|
|
19
|
+
}
|
|
16
20
|
// Validate URL
|
|
17
21
|
if (!validateUrl(directusUrl)) {
|
|
18
22
|
ux.warn('Invalid URL');
|
|
@@ -31,6 +35,10 @@ export async function getDirectusToken(directusUrl) {
|
|
|
31
35
|
placeholder: 'admin-token-here',
|
|
32
36
|
message: 'What is your Directus Admin Token?',
|
|
33
37
|
});
|
|
38
|
+
if (isCancel(directusToken)) {
|
|
39
|
+
log.info('Exiting...');
|
|
40
|
+
ux.exit(0);
|
|
41
|
+
}
|
|
34
42
|
// Validate token by fetching the user
|
|
35
43
|
try {
|
|
36
44
|
await api.loginWithToken(directusToken);
|
|
@@ -49,9 +57,10 @@ export async function getDirectusToken(directusUrl) {
|
|
|
49
57
|
}
|
|
50
58
|
}
|
|
51
59
|
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
* Initialize the Directus API with the provided flags and log in the user
|
|
61
|
+
* @param flags - The validated ApplyFlags
|
|
62
|
+
* @returns {Promise<void>} - Returns nothing
|
|
63
|
+
*/
|
|
55
64
|
export async function initializeDirectusApi(flags) {
|
|
56
65
|
api.initialize(flags.directusUrl);
|
|
57
66
|
try {
|
|
@@ -71,9 +80,10 @@ export async function initializeDirectusApi(flags) {
|
|
|
71
80
|
}
|
|
72
81
|
}
|
|
73
82
|
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
* Validate the authentication flags
|
|
84
|
+
* @param flags - The AuthFlags
|
|
85
|
+
* @returns {void} - Errors if the flags are invalid
|
|
86
|
+
*/
|
|
77
87
|
export function validateAuthFlags(flags) {
|
|
78
88
|
if (!flags.directusUrl) {
|
|
79
89
|
ux.error('Directus URL is required.');
|
|
@@ -5,10 +5,15 @@ interface GitHubUrlParts {
|
|
|
5
5
|
repo: string;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
* Parse a GitHub URL or path into its components
|
|
9
|
+
* @param url The URL or path to parse
|
|
10
|
+
* @returns The parsed components
|
|
11
|
+
*/
|
|
12
12
|
export declare function parseGitHubUrl(url: string): GitHubUrlParts;
|
|
13
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Creates a giget-compatible string from GitHub URL parts
|
|
15
|
+
* @param parts The parsed GitHub URL parts
|
|
16
|
+
* @returns A string in the format 'gh:owner/repo#ref[/path]'
|
|
17
|
+
*/
|
|
18
|
+
export declare function createGigetString(parts: GitHubUrlParts): string;
|
|
14
19
|
export {};
|
|
@@ -1,49 +1,89 @@
|
|
|
1
|
-
import { DEFAULT_REPO } from '../constants.js';
|
|
1
|
+
import { DEFAULT_BRANCH, DEFAULT_REPO } from '../constants.js';
|
|
2
2
|
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// - https://github.com/owner/repo/tree/branch
|
|
15
|
-
// - https://github.com/owner/repo/tree/branch/path
|
|
16
|
-
// - owner/repo
|
|
17
|
-
// - owner/repo/path
|
|
3
|
+
* Clean and normalize a GitHub URL
|
|
4
|
+
* Handles various formats:
|
|
5
|
+
* - Full URLs with .git
|
|
6
|
+
* - URLs with query parameters
|
|
7
|
+
* - URLs with hash fragments
|
|
8
|
+
* - URLs with branches/refs
|
|
9
|
+
* - Repository paths
|
|
10
|
+
* @param url The URL or path to clean
|
|
11
|
+
* @returns Cleaned URL without .git, queries, or hashes
|
|
12
|
+
*/
|
|
13
|
+
function cleanGitHubUrl(url) {
|
|
18
14
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
// If it's not a URL, return as is (might be a path)
|
|
16
|
+
if (!url.includes('://')) {
|
|
17
|
+
return url.replace(/\.git$/, '');
|
|
21
18
|
}
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
// Parse the URL
|
|
20
|
+
const parsed = new URL(url);
|
|
21
|
+
// Remove .git suffix from pathname
|
|
22
|
+
parsed.pathname = parsed.pathname.replace(/\.git$/, '');
|
|
23
|
+
// Remove search params and hash
|
|
24
|
+
parsed.search = '';
|
|
25
|
+
parsed.hash = '';
|
|
26
|
+
return parsed.toString();
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// If URL parsing fails, just remove .git suffix
|
|
30
|
+
return url.replace(/\.git$/, '');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse a GitHub URL or path into its components
|
|
35
|
+
* @param url The URL or path to parse
|
|
36
|
+
* @returns The parsed components
|
|
37
|
+
*/
|
|
38
|
+
export function parseGitHubUrl(url) {
|
|
39
|
+
if (!url) {
|
|
40
|
+
throw new Error('URL is required');
|
|
41
|
+
}
|
|
42
|
+
// Clean the URL first
|
|
43
|
+
const cleanedUrl = cleanGitHubUrl(url);
|
|
44
|
+
// Handle full GitHub URLs
|
|
45
|
+
if (cleanedUrl.includes('github.com')) {
|
|
46
|
+
try {
|
|
47
|
+
const parsed = new URL(cleanedUrl);
|
|
48
|
+
const parts = parsed.pathname.split('/').filter(Boolean);
|
|
49
|
+
if (parts.length < 2) {
|
|
50
|
+
throw new Error('Invalid GitHub URL format');
|
|
34
51
|
}
|
|
52
|
+
const [owner, repo, ...rest] = parts;
|
|
53
|
+
const path = rest.length > 0 ? rest.join('/') : undefined;
|
|
54
|
+
const ref = parsed.searchParams.get('ref') || DEFAULT_BRANCH;
|
|
55
|
+
return { owner, repo, path, ref };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new Error(`Invalid GitHub URL: ${url}`);
|
|
35
59
|
}
|
|
36
|
-
return { owner, path, ref, repo };
|
|
37
60
|
}
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
// Handle repository paths (owner/repo format)
|
|
62
|
+
const parts = cleanedUrl.split('/').filter(Boolean);
|
|
63
|
+
if (parts.length >= 2) {
|
|
64
|
+
const [owner, repo, ...rest] = parts;
|
|
65
|
+
const path = rest.length > 0 ? rest.join('/') : undefined;
|
|
66
|
+
return { owner, repo, path, ref: DEFAULT_BRANCH };
|
|
40
67
|
}
|
|
68
|
+
// Handle simple template names using DEFAULT_REPO
|
|
69
|
+
return {
|
|
70
|
+
...DEFAULT_REPO,
|
|
71
|
+
path: cleanedUrl // The template name becomes the subpath
|
|
72
|
+
};
|
|
41
73
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Creates a giget-compatible string from GitHub URL parts
|
|
76
|
+
* @param parts The parsed GitHub URL parts
|
|
77
|
+
* @returns A string in the format 'gh:owner/repo#ref[/path]'
|
|
78
|
+
*/
|
|
79
|
+
export function createGigetString(parts) {
|
|
80
|
+
// For the default repo case with a template name
|
|
81
|
+
if (parts.owner === DEFAULT_REPO.owner && parts.repo === DEFAULT_REPO.repo) {
|
|
82
|
+
return `gh:${parts.owner}/${parts.repo}/${parts.path}#${DEFAULT_REPO.ref}`;
|
|
83
|
+
}
|
|
84
|
+
// For other GitHub URLs
|
|
85
|
+
const base = `gh:${parts.owner}/${parts.repo}`;
|
|
86
|
+
const path = parts.path ? `/${parts.path}` : '';
|
|
87
|
+
const ref = parts.ref ? `#${parts.ref}` : '';
|
|
88
|
+
return `${base}${path}${ref}`;
|
|
49
89
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DirectusTemplateConfig } from '../types.js';
|
|
2
|
+
export interface TemplateInfo {
|
|
3
|
+
config: DirectusTemplateConfig;
|
|
4
|
+
frontendOptions: Array<{
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
}>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read and validate the template configuration from a directory
|
|
12
|
+
* @param dir Directory containing the template
|
|
13
|
+
* @returns Template configuration and frontend options
|
|
14
|
+
* @throws Error if package.json is missing or invalid
|
|
15
|
+
*/
|
|
16
|
+
export declare function readTemplateConfig(dir: string): TemplateInfo | null;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'pathe';
|
|
3
|
+
/**
|
|
4
|
+
* Read and validate the template configuration from a directory
|
|
5
|
+
* @param dir Directory containing the template
|
|
6
|
+
* @returns Template configuration and frontend options
|
|
7
|
+
* @throws Error if package.json is missing or invalid
|
|
8
|
+
*/
|
|
9
|
+
export function readTemplateConfig(dir) {
|
|
10
|
+
try {
|
|
11
|
+
const packageJsonPath = path.join(dir, 'package.json');
|
|
12
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
16
|
+
const templateConfig = packageJson['directus:template'];
|
|
17
|
+
if (!templateConfig) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Convert frontends object to array of options
|
|
21
|
+
const frontendOptions = Object.entries(templateConfig.frontends || {}).map(([id, frontend]) => ({
|
|
22
|
+
id,
|
|
23
|
+
name: frontend.name,
|
|
24
|
+
path: frontend.path.replace(/^\.\//, ''), // Remove leading ./
|
|
25
|
+
}));
|
|
26
|
+
return {
|
|
27
|
+
config: templateConfig,
|
|
28
|
+
frontendOptions,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/services/docker.js
CHANGED
|
@@ -1,7 +1,62 @@
|
|
|
1
1
|
import { spinner } from '@clack/prompts';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
|
+
import net from 'node:net';
|
|
4
|
+
import { ux } from '@oclif/core';
|
|
3
5
|
import catchError from '../lib/utils/catch-error.js';
|
|
4
6
|
import { waitFor } from '../lib/utils/wait.js';
|
|
7
|
+
/**
|
|
8
|
+
* Check if a port is in use and what's using it
|
|
9
|
+
* @param port The port to check
|
|
10
|
+
* @returns Object indicating if port is in use and what's using it
|
|
11
|
+
*/
|
|
12
|
+
async function checkPort(port) {
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
const server = net.createServer();
|
|
15
|
+
server.once('error', async (err) => {
|
|
16
|
+
if (err.code === 'EADDRINUSE') {
|
|
17
|
+
// Try to get information about what's using the port
|
|
18
|
+
try {
|
|
19
|
+
const { stdout } = await execa('lsof', ['-i', `:${port}`]);
|
|
20
|
+
const process = stdout.split('\n')[1]?.split(/\s+/)[0]; // Get process name
|
|
21
|
+
resolve({ inUse: true, process });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
resolve({ inUse: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
resolve({ inUse: false });
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
server.once('listening', () => {
|
|
32
|
+
server.close();
|
|
33
|
+
resolve({ inUse: false });
|
|
34
|
+
});
|
|
35
|
+
server.listen(port);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if required ports are available and warn if they're in use
|
|
40
|
+
* @returns Promise<void>
|
|
41
|
+
*/
|
|
42
|
+
async function checkRequiredPorts() {
|
|
43
|
+
const portsToCheck = [
|
|
44
|
+
{ port: 8055, name: 'Directus API' },
|
|
45
|
+
{ port: 5432, name: 'PostgreSQL' },
|
|
46
|
+
];
|
|
47
|
+
let hasConflicts = false;
|
|
48
|
+
for (const { port, name } of portsToCheck) {
|
|
49
|
+
const status = await checkPort(port);
|
|
50
|
+
if (status.inUse) {
|
|
51
|
+
hasConflicts = true;
|
|
52
|
+
const process = status.process ? ` by ${status.process}` : '';
|
|
53
|
+
ux.warn(`Port ${port} (${name}) is already in use${process}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (hasConflicts) {
|
|
57
|
+
ux.warn('Please stop any conflicting services before continuing.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
5
60
|
/**
|
|
6
61
|
* Check if Docker is installed and running
|
|
7
62
|
* @returns {Promise<DockerCheckResult>} Docker installation and running status
|
|
@@ -39,12 +94,12 @@ async function checkDocker() {
|
|
|
39
94
|
*/
|
|
40
95
|
async function startContainers(cwd) {
|
|
41
96
|
try {
|
|
42
|
-
//
|
|
97
|
+
// Check if required ports are available
|
|
98
|
+
await checkRequiredPorts();
|
|
43
99
|
const s = spinner();
|
|
44
100
|
s.start('Starting Docker containers');
|
|
45
101
|
return execa('docker-compose', ['up', '-d'], {
|
|
46
102
|
cwd,
|
|
47
|
-
// stdio: 'inherit',
|
|
48
103
|
}).then(() => {
|
|
49
104
|
s.stop('Docker containers running!');
|
|
50
105
|
});
|
|
@@ -67,7 +122,6 @@ async function stopContainers(cwd) {
|
|
|
67
122
|
try {
|
|
68
123
|
return execa('docker-compose', ['down'], {
|
|
69
124
|
cwd,
|
|
70
|
-
// stdio: 'inherit',
|
|
71
125
|
}).then(() => { });
|
|
72
126
|
}
|
|
73
127
|
catch (error) {
|