directus-template-cli 0.7.0-beta.13 → 0.7.0-beta.15
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/dist/commands/apply.d.ts +3 -2
- package/dist/commands/apply.js +70 -2
- package/dist/commands/base.d.ts +1 -0
- package/dist/commands/base.js +13 -0
- package/dist/commands/extract.d.ts +4 -2
- package/dist/commands/extract.js +38 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +28 -20
- package/dist/lib/constants.d.ts +4 -1
- package/dist/lib/constants.js +4 -4
- package/dist/lib/init/index.js +30 -18
- package/dist/lib/load/apply-flags.d.ts +2 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/utils/catch-error.d.ts +1 -1
- package/dist/lib/utils/catch-error.js +7 -0
- package/dist/lib/utils/parse-github-url.js +39 -10
- package/dist/services/docker.js +63 -6
- package/dist/services/execution-context.d.ts +18 -0
- package/dist/services/execution-context.js +20 -0
- package/dist/services/github.d.ts +7 -2
- package/dist/services/github.js +100 -10
- package/dist/services/posthog.d.ts +13 -0
- package/dist/services/posthog.js +27 -1
- package/oclif.manifest.json +21 -4
- package/package.json +2 -3
package/dist/commands/apply.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class ApplyCommand extends
|
|
1
|
+
import { BaseCommand } from './base.js';
|
|
2
|
+
export default class ApplyCommand extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
@@ -20,6 +20,7 @@ export default class ApplyCommand extends Command {
|
|
|
20
20
|
userEmail: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
21
|
userPassword: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
22
|
users: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
|
+
disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
23
24
|
};
|
|
24
25
|
/**
|
|
25
26
|
* MAIN
|
package/dist/commands/apply.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Flags, ux } from '@oclif/core';
|
|
2
2
|
import { text, select, password, log, intro } from '@clack/prompts';
|
|
3
3
|
import * as path from 'pathe';
|
|
4
4
|
import { animatedBunny } from '../lib/utils/animated-bunny.js';
|
|
@@ -13,7 +13,9 @@ import { getCommunityTemplates, getGithubTemplate, getInteractiveLocalTemplate,
|
|
|
13
13
|
import { logger } from '../lib/utils/logger.js';
|
|
14
14
|
import openUrl from '../lib/utils/open-url.js';
|
|
15
15
|
import chalk from 'chalk';
|
|
16
|
-
|
|
16
|
+
import { BaseCommand } from './base.js';
|
|
17
|
+
import { track, shutdown } from '../services/posthog.js';
|
|
18
|
+
export default class ApplyCommand extends BaseCommand {
|
|
17
19
|
static description = 'Apply a template to a blank Directus instance.';
|
|
18
20
|
static examples = [
|
|
19
21
|
'$ directus-template-cli apply',
|
|
@@ -86,6 +88,7 @@ export default class ApplyCommand extends Command {
|
|
|
86
88
|
default: undefined,
|
|
87
89
|
description: 'Load users',
|
|
88
90
|
}),
|
|
91
|
+
disableTelemetry: customFlags.disableTelemetry,
|
|
89
92
|
};
|
|
90
93
|
/**
|
|
91
94
|
* MAIN
|
|
@@ -177,8 +180,41 @@ export default class ApplyCommand extends Command {
|
|
|
177
180
|
await initializeDirectusApi(validatedFlags);
|
|
178
181
|
if (template) {
|
|
179
182
|
// /* TODO: Replace with custom styledHeader function */ ux.styledHeader(ux.colorize(DIRECTUS_PURPLE, `Applying template - ${template.templateName} to ${directusUrl}`))
|
|
183
|
+
// Track start just before applying
|
|
184
|
+
if (!validatedFlags.disableTelemetry) {
|
|
185
|
+
await track({
|
|
186
|
+
command: 'apply',
|
|
187
|
+
lifecycle: 'start',
|
|
188
|
+
distinctId: this.userConfig.distinctId,
|
|
189
|
+
flags: {
|
|
190
|
+
programmatic: false,
|
|
191
|
+
templateName: template.templateName,
|
|
192
|
+
templateType,
|
|
193
|
+
// Include other relevant flags from validatedFlags if needed
|
|
194
|
+
...validatedFlags,
|
|
195
|
+
},
|
|
196
|
+
runId: this.runId,
|
|
197
|
+
config: this.config,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
180
200
|
await apply(template.directoryPath, validatedFlags);
|
|
181
201
|
ux.action.stop();
|
|
202
|
+
// Track completion before final messages/exit
|
|
203
|
+
if (!validatedFlags.disableTelemetry) {
|
|
204
|
+
await track({
|
|
205
|
+
command: 'apply',
|
|
206
|
+
lifecycle: 'complete',
|
|
207
|
+
distinctId: this.userConfig.distinctId,
|
|
208
|
+
flags: {
|
|
209
|
+
templateName: template.templateName,
|
|
210
|
+
templateType,
|
|
211
|
+
...validatedFlags,
|
|
212
|
+
},
|
|
213
|
+
runId: this.runId,
|
|
214
|
+
config: this.config,
|
|
215
|
+
});
|
|
216
|
+
await shutdown();
|
|
217
|
+
}
|
|
182
218
|
ux.stdout(SEPARATOR);
|
|
183
219
|
log.warn(BSL_LICENSE_TEXT);
|
|
184
220
|
ux.stdout('Template applied successfully.');
|
|
@@ -218,8 +254,40 @@ export default class ApplyCommand extends Command {
|
|
|
218
254
|
const logMessage = `Applying template - ${template.templateName} to ${validatedFlags.directusUrl}`;
|
|
219
255
|
// /* TODO: Replace with custom styledHeader function */ ux.styledHeader(logMessage)
|
|
220
256
|
logger.log('info', logMessage);
|
|
257
|
+
// Track start just before applying
|
|
258
|
+
if (!validatedFlags.disableTelemetry) {
|
|
259
|
+
await track({
|
|
260
|
+
command: 'apply',
|
|
261
|
+
lifecycle: 'start',
|
|
262
|
+
distinctId: this.userConfig.distinctId,
|
|
263
|
+
flags: {
|
|
264
|
+
programmatic: true,
|
|
265
|
+
templateName: template.templateName,
|
|
266
|
+
// Include other relevant flags from validatedFlags
|
|
267
|
+
...validatedFlags,
|
|
268
|
+
},
|
|
269
|
+
runId: this.runId,
|
|
270
|
+
config: this.config,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
221
273
|
await apply(template.directoryPath, validatedFlags);
|
|
222
274
|
ux.action.stop();
|
|
275
|
+
// Track completion before final messages/exit
|
|
276
|
+
if (!validatedFlags.disableTelemetry) {
|
|
277
|
+
await track({
|
|
278
|
+
command: 'apply',
|
|
279
|
+
lifecycle: 'complete',
|
|
280
|
+
distinctId: this.userConfig.distinctId,
|
|
281
|
+
flags: {
|
|
282
|
+
templateName: template.templateName,
|
|
283
|
+
// Include other relevant flags from validatedFlags
|
|
284
|
+
...validatedFlags,
|
|
285
|
+
},
|
|
286
|
+
runId: this.runId,
|
|
287
|
+
config: this.config,
|
|
288
|
+
});
|
|
289
|
+
await shutdown();
|
|
290
|
+
}
|
|
223
291
|
ux.stdout(SEPARATOR);
|
|
224
292
|
ux.stdout('Template applied successfully.');
|
|
225
293
|
// ux.exit(0)
|
package/dist/commands/base.d.ts
CHANGED
package/dist/commands/base.js
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'pathe';
|
|
5
|
+
import { setExecutionContext } from '../services/execution-context.js';
|
|
5
6
|
export class BaseCommand extends Command {
|
|
6
7
|
runId;
|
|
7
8
|
userConfig = {};
|
|
@@ -10,6 +11,18 @@ export class BaseCommand extends Command {
|
|
|
10
11
|
this.runId = randomUUID();
|
|
11
12
|
this.loadUserConfig();
|
|
12
13
|
}
|
|
14
|
+
async init() {
|
|
15
|
+
await super.init();
|
|
16
|
+
const { flags } = await this.parse({
|
|
17
|
+
flags: this.ctor.flags,
|
|
18
|
+
args: this.ctor.args,
|
|
19
|
+
strict: false,
|
|
20
|
+
});
|
|
21
|
+
setExecutionContext({
|
|
22
|
+
distinctId: this.userConfig.distinctId ?? undefined,
|
|
23
|
+
disableTelemetry: flags?.disableTelemetry ?? false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
13
26
|
loadUserConfig() {
|
|
14
27
|
try {
|
|
15
28
|
const configPath = path.join(this.config.configDir, 'config.json');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseCommand } from './base.js';
|
|
2
2
|
export interface ExtractFlags {
|
|
3
3
|
directusToken: string;
|
|
4
4
|
directusUrl: string;
|
|
@@ -7,8 +7,9 @@ export interface ExtractFlags {
|
|
|
7
7
|
templateName: string;
|
|
8
8
|
userEmail: string;
|
|
9
9
|
userPassword: string;
|
|
10
|
+
disableTelemetry?: boolean;
|
|
10
11
|
}
|
|
11
|
-
export default class ExtractCommand extends
|
|
12
|
+
export default class ExtractCommand extends BaseCommand {
|
|
12
13
|
static description: string;
|
|
13
14
|
static examples: string[];
|
|
14
15
|
static flags: {
|
|
@@ -19,6 +20,7 @@ export default class ExtractCommand extends Command {
|
|
|
19
20
|
templateName: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
21
|
userEmail: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
21
22
|
userPassword: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
|
+
disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* Main run method for the ExtractCommand
|
package/dist/commands/extract.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { text, password, select, intro, log } from '@clack/prompts';
|
|
2
|
-
import {
|
|
2
|
+
import { ux } from '@oclif/core';
|
|
3
3
|
import slugify from '@sindresorhus/slugify';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import fs from 'node:fs';
|
|
@@ -7,11 +7,13 @@ import path from 'pathe';
|
|
|
7
7
|
import * as customFlags from '../flags/common.js';
|
|
8
8
|
import { DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR, BSL_LICENSE_TEXT } from '../lib/constants.js';
|
|
9
9
|
import { animatedBunny } from '../lib/utils/animated-bunny.js';
|
|
10
|
+
import { BaseCommand } from './base.js';
|
|
11
|
+
import { track, shutdown } from '../services/posthog.js';
|
|
10
12
|
import extract from '../lib/extract/index.js';
|
|
11
13
|
import { getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags } from '../lib/utils/auth.js';
|
|
12
14
|
import catchError from '../lib/utils/catch-error.js';
|
|
13
15
|
import { generatePackageJsonContent, generateReadmeContent, } from '../lib/utils/template-defaults.js';
|
|
14
|
-
export default class ExtractCommand extends
|
|
16
|
+
export default class ExtractCommand extends BaseCommand {
|
|
15
17
|
static description = 'Extract a template from a Directus instance.';
|
|
16
18
|
static examples = [
|
|
17
19
|
'$ directus-template-cli extract',
|
|
@@ -25,6 +27,7 @@ export default class ExtractCommand extends Command {
|
|
|
25
27
|
templateName: customFlags.templateName,
|
|
26
28
|
userEmail: customFlags.userEmail,
|
|
27
29
|
userPassword: customFlags.userPassword,
|
|
30
|
+
disableTelemetry: customFlags.disableTelemetry,
|
|
28
31
|
};
|
|
29
32
|
/**
|
|
30
33
|
* Main run method for the ExtractCommand
|
|
@@ -43,6 +46,22 @@ export default class ExtractCommand extends Command {
|
|
|
43
46
|
* @returns {Promise<void>} - Returns nothing
|
|
44
47
|
*/
|
|
45
48
|
async extractTemplate(templateName, directory, flags) {
|
|
49
|
+
// Track start of extraction attempt
|
|
50
|
+
if (!flags.disableTelemetry) {
|
|
51
|
+
await track({
|
|
52
|
+
command: 'extract',
|
|
53
|
+
lifecycle: 'start',
|
|
54
|
+
distinctId: this.userConfig.distinctId,
|
|
55
|
+
flags: {
|
|
56
|
+
templateName,
|
|
57
|
+
templateLocation: directory,
|
|
58
|
+
directusUrl: flags.directusUrl,
|
|
59
|
+
programmatic: flags.programmatic,
|
|
60
|
+
},
|
|
61
|
+
runId: this.runId,
|
|
62
|
+
config: this.config,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
46
65
|
try {
|
|
47
66
|
if (!fs.existsSync(directory)) {
|
|
48
67
|
fs.mkdirSync(directory, { recursive: true });
|
|
@@ -65,6 +84,23 @@ export default class ExtractCommand extends Command {
|
|
|
65
84
|
ux.action.start(`Extracting template - ${ux.colorize(DIRECTUS_PINK, templateName)} from ${ux.colorize(DIRECTUS_PINK, flags.directusUrl)} to ${ux.colorize(DIRECTUS_PINK, directory)}`);
|
|
66
85
|
await extract(directory);
|
|
67
86
|
ux.action.stop();
|
|
87
|
+
// Track completion before final messages/exit
|
|
88
|
+
if (!flags.disableTelemetry) {
|
|
89
|
+
await track({
|
|
90
|
+
command: 'extract',
|
|
91
|
+
lifecycle: 'complete',
|
|
92
|
+
distinctId: this.userConfig.distinctId,
|
|
93
|
+
flags: {
|
|
94
|
+
templateName,
|
|
95
|
+
templateLocation: directory,
|
|
96
|
+
directusUrl: flags.directusUrl,
|
|
97
|
+
programmatic: flags.programmatic,
|
|
98
|
+
},
|
|
99
|
+
runId: this.runId,
|
|
100
|
+
config: this.config,
|
|
101
|
+
});
|
|
102
|
+
await shutdown();
|
|
103
|
+
}
|
|
68
104
|
log.warn(BSL_LICENSE_TEXT);
|
|
69
105
|
ux.stdout(SEPARATOR);
|
|
70
106
|
ux.stdout('Template extracted successfully.');
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export interface InitFlags {
|
|
|
3
3
|
frontend?: string;
|
|
4
4
|
gitInit?: boolean;
|
|
5
5
|
installDeps?: boolean;
|
|
6
|
-
|
|
6
|
+
overwriteDir?: boolean;
|
|
7
7
|
template?: string;
|
|
8
8
|
disableTelemetry?: boolean;
|
|
9
9
|
}
|
|
@@ -22,7 +22,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
22
22
|
frontend: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
23
|
gitInit: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
24
|
installDeps: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
25
|
-
|
|
25
|
+
overwriteDir: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
26
26
|
template: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
27
|
disableTelemetry: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
28
28
|
};
|
package/dist/commands/init.js
CHANGED
|
@@ -45,7 +45,9 @@ export default class InitCommand extends BaseCommand {
|
|
|
45
45
|
default: true,
|
|
46
46
|
description: 'Install dependencies automatically',
|
|
47
47
|
}),
|
|
48
|
-
|
|
48
|
+
overwriteDir: Flags.boolean({
|
|
49
|
+
aliases: ['overwrite-dir'],
|
|
50
|
+
allowNo: true,
|
|
49
51
|
default: false,
|
|
50
52
|
description: 'Override the default directory',
|
|
51
53
|
}),
|
|
@@ -96,28 +98,31 @@ export default class InitCommand extends BaseCommand {
|
|
|
96
98
|
}
|
|
97
99
|
this.targetDir = dirResponse;
|
|
98
100
|
}
|
|
99
|
-
if (fs.existsSync(this.targetDir) && !flags.
|
|
100
|
-
const
|
|
101
|
+
if (fs.existsSync(this.targetDir) && !flags.overwriteDir) {
|
|
102
|
+
const overwriteDirResponse = await confirm({
|
|
101
103
|
message: 'Directory already exists. Would you like to overwrite it?',
|
|
104
|
+
initialValue: false,
|
|
102
105
|
});
|
|
103
|
-
if (isCancel(
|
|
106
|
+
if (isCancel(overwriteDirResponse) || overwriteDirResponse === false) {
|
|
104
107
|
cancel('Project creation cancelled.');
|
|
105
108
|
process.exit(0);
|
|
106
109
|
}
|
|
107
|
-
if (
|
|
108
|
-
flags.
|
|
110
|
+
if (overwriteDirResponse) {
|
|
111
|
+
flags.overwriteDir = true;
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
|
-
// 1. Fetch available templates
|
|
114
|
+
// 1. Fetch available templates (now returns Array<{id: string, name: string, description?: string}>)
|
|
112
115
|
const availableTemplates = await github.getTemplates();
|
|
113
116
|
// 2. Prompt for template if not provided
|
|
114
|
-
let { template } = flags;
|
|
117
|
+
let { template } = flags; // This will store the chosen template ID
|
|
118
|
+
let chosenTemplateObject;
|
|
115
119
|
if (!template) {
|
|
116
120
|
const templateResponse = await select({
|
|
117
121
|
message: 'Which Directus backend template would you like to use?',
|
|
118
|
-
options: availableTemplates.map(
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
options: availableTemplates.map(tmpl => ({
|
|
123
|
+
value: tmpl.id, // The value submitted will be the ID (directory name)
|
|
124
|
+
label: tmpl.name, // Display the friendly name
|
|
125
|
+
hint: tmpl.description, // Show the description as a hint
|
|
121
126
|
})),
|
|
122
127
|
});
|
|
123
128
|
if (isCancel(templateResponse)) {
|
|
@@ -126,22 +131,25 @@ export default class InitCommand extends BaseCommand {
|
|
|
126
131
|
}
|
|
127
132
|
template = templateResponse;
|
|
128
133
|
}
|
|
129
|
-
//
|
|
130
|
-
|
|
134
|
+
// Find the chosen template object for potential future use (e.g., display name later)
|
|
135
|
+
chosenTemplateObject = availableTemplates.find(t => t.id === template);
|
|
136
|
+
// 3. Validate that the template exists in the available list
|
|
131
137
|
const isDirectUrl = template?.startsWith('http');
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
// Validate against the 'id' property of the template objects
|
|
139
|
+
while (!isDirectUrl && !availableTemplates.some(t => t.id === template)) {
|
|
140
|
+
// Keep the warning message simple or refer back to the list shown in the prompt
|
|
141
|
+
clackLog.warn(`Template ID "${template}" is not valid. Please choose from the list provided or enter a direct GitHub URL.`);
|
|
134
142
|
const templateNameResponse = await text({
|
|
135
|
-
message: 'Please enter a valid template
|
|
143
|
+
message: 'Please enter a valid template ID, a direct GitHub URL, or Ctrl+C to cancel:',
|
|
136
144
|
});
|
|
137
145
|
if (isCancel(templateNameResponse)) {
|
|
138
146
|
cancel('Project creation cancelled.');
|
|
139
147
|
process.exit(0);
|
|
140
148
|
}
|
|
141
149
|
template = templateNameResponse;
|
|
142
|
-
|
|
150
|
+
chosenTemplateObject = availableTemplates.find(t => t.id === template); // Update chosen object after re-entry
|
|
143
151
|
}
|
|
144
|
-
flags.template = template;
|
|
152
|
+
flags.template = template; // Ensure the flag stores the ID
|
|
145
153
|
// Download the template to a temporary directory to read its configuration
|
|
146
154
|
const tempDir = path.join(os.tmpdir(), `directus-template-${Date.now()}`);
|
|
147
155
|
let chosenFrontend = flags.frontend;
|
|
@@ -220,7 +228,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
220
228
|
gitInit: initGit,
|
|
221
229
|
installDeps,
|
|
222
230
|
template,
|
|
223
|
-
|
|
231
|
+
overwriteDir: flags.overwriteDir,
|
|
224
232
|
},
|
|
225
233
|
});
|
|
226
234
|
// Track the command completion unless telemetry is disabled
|
|
@@ -234,7 +242,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
234
242
|
gitInit: initGit,
|
|
235
243
|
installDeps,
|
|
236
244
|
template,
|
|
237
|
-
|
|
245
|
+
overwriteDir: flags.overwriteDir,
|
|
238
246
|
},
|
|
239
247
|
runId: this.runId,
|
|
240
248
|
config: this.config,
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -18,4 +18,7 @@ export declare const POSTHOG_PUBLIC_KEY = "phc_STopE6gj6LDIjYonVF7493kQJK8S4v0Xr
|
|
|
18
18
|
export declare const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
19
19
|
export declare const DEFAULT_BRANCH = "main";
|
|
20
20
|
export declare const BSL_LICENSE_URL = "https://directus.io/bsl";
|
|
21
|
-
export declare const
|
|
21
|
+
export declare const BSL_EMAIL = "licensing@directus.io";
|
|
22
|
+
export declare const BSL_LICENSE_HEADLINE = "You REQUIRE a license to use Directus if your organization has more than $5MM USD a year in revenue and/or funding.";
|
|
23
|
+
export declare const BSL_LICENSE_TEXT = "For all organizations with less than $5MM USD a year in revenue and funding, Directus is free for personal projects, hobby projects and in production. This second group does not require a license. Directus is licensed under BSL 1.1.";
|
|
24
|
+
export declare const BSL_LICENSE_CTA: string;
|
package/dist/lib/constants.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import terminalLink from 'terminal-link';
|
|
3
2
|
export const DIRECTUS_PURPLE = '#6644ff';
|
|
4
3
|
export const DIRECTUS_PINK = '#FF99DD';
|
|
5
4
|
export const SEPARATOR = '------------------';
|
|
@@ -20,6 +19,7 @@ export const POSTHOG_PUBLIC_KEY = 'phc_STopE6gj6LDIjYonVF7493kQJK8S4v0Xrl6YPr2z9
|
|
|
20
19
|
export const POSTHOG_HOST = 'https://us.i.posthog.com';
|
|
21
20
|
export const DEFAULT_BRANCH = 'main';
|
|
22
21
|
export const BSL_LICENSE_URL = 'https://directus.io/bsl';
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
export const BSL_LICENSE_TEXT =
|
|
22
|
+
export const BSL_EMAIL = 'licensing@directus.io';
|
|
23
|
+
export const BSL_LICENSE_HEADLINE = 'You REQUIRE a license to use Directus if your organization has more than $5MM USD a year in revenue and/or funding.';
|
|
24
|
+
export const BSL_LICENSE_TEXT = 'For all organizations with less than $5MM USD a year in revenue and funding, Directus is free for personal projects, hobby projects and in production. This second group does not require a license. Directus is licensed under BSL 1.1.';
|
|
25
|
+
export const BSL_LICENSE_CTA = `Visit ${pinkText(BSL_LICENSE_URL)} for more information or reach out to us at ${pinkText(BSL_EMAIL)}.`;
|
package/dist/lib/init/index.js
CHANGED
|
@@ -7,19 +7,18 @@ import fs from 'node:fs';
|
|
|
7
7
|
import { detectPackageManager, installDependencies } from 'nypm';
|
|
8
8
|
import path from 'pathe';
|
|
9
9
|
import dotenv from 'dotenv';
|
|
10
|
-
import terminalLink from 'terminal-link';
|
|
11
10
|
import ApplyCommand from '../../commands/apply.js';
|
|
12
11
|
import { createDocker } from '../../services/docker.js';
|
|
13
12
|
import catchError from '../utils/catch-error.js';
|
|
14
13
|
import { createGigetString, parseGitHubUrl } from '../utils/parse-github-url.js';
|
|
15
14
|
import { readTemplateConfig } from '../utils/template-config.js';
|
|
16
15
|
import { DOCKER_CONFIG } from './config.js';
|
|
17
|
-
import { BSL_LICENSE_TEXT, pinkText } from '../constants.js';
|
|
16
|
+
import { BSL_LICENSE_TEXT, BSL_LICENSE_HEADLINE, BSL_LICENSE_CTA, pinkText } from '../constants.js';
|
|
18
17
|
export async function init({ dir, flags }) {
|
|
19
18
|
// Check target directory
|
|
20
|
-
const shouldForce = flags.
|
|
19
|
+
const shouldForce = flags.overwriteDir;
|
|
21
20
|
if (fs.existsSync(dir) && !shouldForce) {
|
|
22
|
-
throw new Error('Directory already exists. Use --
|
|
21
|
+
throw new Error('Directory already exists. Use --overwrite-dir to override.');
|
|
23
22
|
}
|
|
24
23
|
// If template is a URL, we need to handle it differently
|
|
25
24
|
const isDirectUrl = flags.template?.startsWith('http');
|
|
@@ -111,23 +110,33 @@ export async function init({ dir, flags }) {
|
|
|
111
110
|
if (!isHealthy) {
|
|
112
111
|
throw new Error('Directus failed to become healthy');
|
|
113
112
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
// Check if a template path is specified in the config and exists
|
|
114
|
+
let templatePath;
|
|
115
|
+
if (templateInfo?.config?.template && typeof templateInfo.config.template === 'string') {
|
|
116
|
+
templatePath = path.join(dir, templateInfo.config.template); // Path relative to root dir
|
|
117
|
+
}
|
|
118
|
+
if (templatePath && fs.existsSync(templatePath)) {
|
|
119
|
+
ux.stdout(`Applying template from: ${templatePath}`);
|
|
120
|
+
await ApplyCommand.run([
|
|
121
|
+
`--directusUrl=${directusInfo.url || 'http://localhost:8055'}`,
|
|
122
|
+
'-p',
|
|
123
|
+
`--userEmail=${directusInfo.email}`,
|
|
124
|
+
`--userPassword=${directusInfo.password}`,
|
|
125
|
+
`--templateLocation=${templatePath}`,
|
|
126
|
+
]);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
ux.stdout('Skipping backend template application.');
|
|
130
|
+
}
|
|
123
131
|
}
|
|
132
|
+
// Detect package manager even if not installing dependencies
|
|
133
|
+
packageManager = await detectPackageManager(frontendDir);
|
|
124
134
|
// Install dependencies if requested
|
|
125
135
|
if (flags.installDeps) {
|
|
126
136
|
const s = spinner();
|
|
127
137
|
s.start('Installing dependencies');
|
|
128
138
|
try {
|
|
129
139
|
if (fs.existsSync(frontendDir)) {
|
|
130
|
-
packageManager = await detectPackageManager(frontendDir);
|
|
131
140
|
await installDependencies({
|
|
132
141
|
cwd: frontendDir,
|
|
133
142
|
packageManager,
|
|
@@ -151,14 +160,17 @@ export async function init({ dir, flags }) {
|
|
|
151
160
|
// Finishing up
|
|
152
161
|
const relativeDir = path.relative(process.cwd(), dir);
|
|
153
162
|
const directusUrl = directusInfo.url ?? 'http://localhost:8055';
|
|
154
|
-
const directusText = `- Directus is running on ${
|
|
163
|
+
const directusText = `- Directus is running on ${directusUrl}. \n`;
|
|
164
|
+
const directusLoginText = `- You can login with the email: ${pinkText(directusInfo.email)} and password: ${pinkText(directusInfo.password)}. \n`;
|
|
155
165
|
const frontendText = flags.frontend ? `- To start the frontend, run ${pinkText(`cd ${flags.frontend}`)} and then ${pinkText(`${packageManager?.name} run dev`)}. \n` : '';
|
|
156
166
|
const projectText = `- Navigate to your project directory using ${pinkText(`cd ${relativeDir}`)}. \n`;
|
|
157
167
|
const readmeText = '- Review the \`./README.md\` file for more information and next steps.';
|
|
158
|
-
const nextSteps = `${directusText}${projectText}${frontendText}${readmeText}`;
|
|
168
|
+
const nextSteps = `${directusText}${directusLoginText}${projectText}${frontendText}${readmeText}`;
|
|
159
169
|
note(nextSteps, 'Next Steps');
|
|
160
|
-
clackLog.warn(
|
|
161
|
-
|
|
170
|
+
clackLog.warn(BSL_LICENSE_HEADLINE);
|
|
171
|
+
clackLog.info(BSL_LICENSE_TEXT);
|
|
172
|
+
clackLog.info(BSL_LICENSE_CTA);
|
|
173
|
+
outro(`Problems or questions? Hop into the community at ${pinkText('https://directus.chat')}`);
|
|
162
174
|
}
|
|
163
175
|
catch (error) {
|
|
164
176
|
catchError(error, {
|
|
@@ -15,7 +15,8 @@ export interface ApplyFlags {
|
|
|
15
15
|
templateType: 'community' | 'github' | 'local';
|
|
16
16
|
userEmail: string;
|
|
17
17
|
userPassword: string;
|
|
18
|
-
users
|
|
18
|
+
users?: boolean;
|
|
19
|
+
disableTelemetry?: boolean;
|
|
19
20
|
}
|
|
20
21
|
export declare const loadFlags: readonly ["content", "dashboards", "extensions", "files", "flows", "permissions", "schema", "settings", "users"];
|
|
21
22
|
export declare function validateProgrammaticFlags(flags: ApplyFlags): ApplyFlags;
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
interface ErrorHandlerOptions {
|
|
5
5
|
/** Additional context to be included in the error log. */
|
|
6
|
-
context?: Record<string,
|
|
6
|
+
context?: Record<string, unknown>;
|
|
7
7
|
/** If true, the error will be treated as fatal and the process will exit. */
|
|
8
8
|
fatal?: boolean;
|
|
9
9
|
/** If true, the error will be logged to a file. */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { ux } from '@oclif/core';
|
|
2
2
|
import { DirectusError } from '../sdk.js';
|
|
3
3
|
import { logger } from '../utils/logger.js';
|
|
4
|
+
import { captureException } from '../../services/posthog.js';
|
|
5
|
+
import { getExecutionContext } from '../../services/execution-context.js';
|
|
4
6
|
/**
|
|
5
7
|
* Handles errors by formatting them and optionally logging to console and file.
|
|
6
8
|
* @param error - The error to be handled.
|
|
@@ -9,6 +11,7 @@ import { logger } from '../utils/logger.js';
|
|
|
9
11
|
*/
|
|
10
12
|
export default function catchError(error, options = {}) {
|
|
11
13
|
const { context = {}, fatal = false, logToFile = true } = options;
|
|
14
|
+
const { distinctId, disableTelemetry } = getExecutionContext();
|
|
12
15
|
let errorMessage;
|
|
13
16
|
if (error instanceof DirectusError) {
|
|
14
17
|
errorMessage = error.message;
|
|
@@ -19,6 +22,10 @@ export default function catchError(error, options = {}) {
|
|
|
19
22
|
else {
|
|
20
23
|
errorMessage = `Unknown error: ${JSON.stringify(error)}`;
|
|
21
24
|
}
|
|
25
|
+
// Capture exception before logging/exiting
|
|
26
|
+
if (!disableTelemetry && distinctId) {
|
|
27
|
+
captureException({ error, distinctId, properties: { context } });
|
|
28
|
+
}
|
|
22
29
|
// Format the error message with context if provided
|
|
23
30
|
const formattedMessage = [
|
|
24
31
|
errorMessage,
|
|
@@ -39,30 +39,59 @@ export function parseGitHubUrl(url) {
|
|
|
39
39
|
if (!url) {
|
|
40
40
|
throw new Error('URL is required');
|
|
41
41
|
}
|
|
42
|
-
// Clean the URL first
|
|
43
42
|
const cleanedUrl = cleanGitHubUrl(url);
|
|
44
|
-
// Handle full GitHub URLs
|
|
45
43
|
if (cleanedUrl.includes('github.com')) {
|
|
46
44
|
try {
|
|
47
45
|
const parsed = new URL(cleanedUrl);
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
throw new Error('Invalid GitHub URL format');
|
|
46
|
+
const pathParts = parsed.pathname.split('/').filter(Boolean);
|
|
47
|
+
if (pathParts.length < 2) {
|
|
48
|
+
throw new Error('Invalid GitHub URL format: Needs owner and repo.');
|
|
51
49
|
}
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
50
|
+
const owner = pathParts[0];
|
|
51
|
+
const repo = pathParts[1];
|
|
52
|
+
let ref = DEFAULT_BRANCH; // Default ref
|
|
53
|
+
let path;
|
|
54
|
+
// Check for /tree/ref/ or /blob/ref/ patterns
|
|
55
|
+
const treeIndex = pathParts.indexOf('tree');
|
|
56
|
+
const blobIndex = pathParts.indexOf('blob');
|
|
57
|
+
let refIndex = -1;
|
|
58
|
+
if (treeIndex > 1 && treeIndex + 1 < pathParts.length) {
|
|
59
|
+
refIndex = treeIndex + 1;
|
|
60
|
+
}
|
|
61
|
+
else if (blobIndex > 1 && blobIndex + 1 < pathParts.length) {
|
|
62
|
+
refIndex = blobIndex + 1;
|
|
63
|
+
}
|
|
64
|
+
if (refIndex !== -1) {
|
|
65
|
+
ref = pathParts[refIndex];
|
|
66
|
+
// Path is everything after the ref
|
|
67
|
+
path = pathParts.slice(refIndex + 1).join('/') || undefined;
|
|
68
|
+
}
|
|
69
|
+
else if (pathParts.length > 2) {
|
|
70
|
+
// If no tree/blob, but more parts exist, assume it's part of the path
|
|
71
|
+
// This handles cases like github.com/owner/repo/some/path without a specific ref marker
|
|
72
|
+
path = pathParts.slice(2).join('/') || undefined;
|
|
73
|
+
// If URL has an explicit ?ref= param, use that, otherwise keep default
|
|
74
|
+
ref = parsed.searchParams.get('ref') || ref;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
// No path, just owner/repo
|
|
78
|
+
ref = parsed.searchParams.get('ref') || ref;
|
|
79
|
+
}
|
|
80
|
+
// Ensure path is undefined if empty string
|
|
81
|
+
if (path === '')
|
|
82
|
+
path = undefined;
|
|
55
83
|
return { owner, repo, path, ref };
|
|
56
84
|
}
|
|
57
85
|
catch (error) {
|
|
58
|
-
throw new Error(`Invalid GitHub URL: ${url}`);
|
|
86
|
+
throw new Error(`Invalid GitHub URL: ${url}. Error: ${error.message}`);
|
|
59
87
|
}
|
|
60
88
|
}
|
|
61
|
-
// Handle repository paths (owner/repo format)
|
|
89
|
+
// Handle repository paths (owner/repo/path format) without github.com
|
|
62
90
|
const parts = cleanedUrl.split('/').filter(Boolean);
|
|
63
91
|
if (parts.length >= 2) {
|
|
64
92
|
const [owner, repo, ...rest] = parts;
|
|
65
93
|
const path = rest.length > 0 ? rest.join('/') : undefined;
|
|
94
|
+
// Assume default branch for simple paths unless we add ref detection here too
|
|
66
95
|
return { owner, repo, path, ref: DEFAULT_BRANCH };
|
|
67
96
|
}
|
|
68
97
|
// Handle simple template names using DEFAULT_REPO
|
package/dist/services/docker.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { spinner } from '@clack/prompts';
|
|
1
|
+
import { spinner, log } from '@clack/prompts';
|
|
2
2
|
import { execa } from 'execa';
|
|
3
3
|
import net from 'node:net';
|
|
4
4
|
import { ux } from '@oclif/core';
|
|
@@ -88,24 +88,81 @@ async function checkDocker() {
|
|
|
88
88
|
};
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Get the list of image names defined in the docker-compose file
|
|
93
|
+
* @param {string} cwd - The current working directory
|
|
94
|
+
* @returns {Promise<string[]>} - A list of image names
|
|
95
|
+
*/
|
|
96
|
+
async function getRequiredImagesFromCompose(cwd) {
|
|
97
|
+
try {
|
|
98
|
+
const { stdout } = await execa('docker', ['compose', 'config', '--images'], { cwd });
|
|
99
|
+
// stdout contains a list of image names, one per line
|
|
100
|
+
return stdout.split('\n').filter(img => img.trim() !== ''); // Filter out empty lines
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
// Handle potential errors, e.g., compose file not found or invalid
|
|
104
|
+
log.error('Failed to get images from docker-compose file.');
|
|
105
|
+
catchError(error, {
|
|
106
|
+
context: { cwd, function: 'getRequiredImagesFromCompose' },
|
|
107
|
+
fatal: false, // Don't necessarily exit, maybe let startContainers handle it
|
|
108
|
+
logToFile: true,
|
|
109
|
+
});
|
|
110
|
+
return []; // Return empty list on error
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if a list of Docker images exist locally
|
|
115
|
+
* @param {string[]} imageNames - An array of Docker image names (e.g., "postgres:16")
|
|
116
|
+
* @returns {Promise<boolean>} - True if all images exist locally, false otherwise
|
|
117
|
+
*/
|
|
118
|
+
async function checkImagesExist(imageNames) {
|
|
119
|
+
if (imageNames.length === 0) {
|
|
120
|
+
return true; // No images to check, technically they all "exist"
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
// Use Promise.allSettled to check all images even if some commands fail
|
|
124
|
+
const results = await Promise.allSettled(imageNames.map(imageName => execa('docker', ['inspect', '--type=image', imageName])));
|
|
125
|
+
// Check if all inspect commands succeeded (exit code 0)
|
|
126
|
+
return results.every(result => result.status === 'fulfilled' && result.value.exitCode === 0);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// This catch block might be redundant due to allSettled, but good for safety
|
|
130
|
+
log.error('Error checking for Docker images.');
|
|
131
|
+
catchError(error, {
|
|
132
|
+
context: { imageNames, function: 'checkImagesExist' },
|
|
133
|
+
fatal: false,
|
|
134
|
+
logToFile: true,
|
|
135
|
+
});
|
|
136
|
+
return false; // Assume images don't exist if there's an error checking
|
|
137
|
+
}
|
|
138
|
+
}
|
|
91
139
|
/**
|
|
92
140
|
* Start Docker containers using docker compose
|
|
93
141
|
* @param {string} cwd - The current working directory
|
|
94
142
|
* @returns {Promise<void>} - Returns nothing
|
|
95
143
|
*/
|
|
96
144
|
async function startContainers(cwd) {
|
|
145
|
+
const s = spinner();
|
|
97
146
|
try {
|
|
98
147
|
// Check if required ports are available
|
|
99
148
|
await checkRequiredPorts();
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
149
|
+
// Get required images from compose file
|
|
150
|
+
const requiredImages = await getRequiredImagesFromCompose(cwd);
|
|
151
|
+
const imagesExist = await checkImagesExist(requiredImages);
|
|
152
|
+
// Log a message if images need downloading
|
|
153
|
+
if (!imagesExist && requiredImages.length > 0) {
|
|
154
|
+
log.info('Required Docker image(s) are missing and will be downloaded.');
|
|
155
|
+
}
|
|
156
|
+
const startMessage = imagesExist || requiredImages.length === 0 ? 'Starting Docker containers...' : 'Downloading required Docker images...';
|
|
157
|
+
const endMessage = imagesExist || requiredImages.length === 0 ? 'Docker containers running!' : 'Docker images downloaded and containers started!';
|
|
158
|
+
s.start(startMessage); // Start spinner with the appropriate message
|
|
159
|
+
await execa('docker', ['compose', 'up', '-d'], {
|
|
103
160
|
cwd,
|
|
104
|
-
}).then(() => {
|
|
105
|
-
s.stop('Docker containers running!');
|
|
106
161
|
});
|
|
162
|
+
s.stop(endMessage); // Update spinner message on success
|
|
107
163
|
}
|
|
108
164
|
catch (error) {
|
|
165
|
+
s.stop('Error starting Docker containers.'); // Stop spinner on error
|
|
109
166
|
catchError(error, {
|
|
110
167
|
context: { cwd, function: 'startContainers' },
|
|
111
168
|
fatal: true,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the structure for the execution context, holding telemetry information.
|
|
3
|
+
*/
|
|
4
|
+
export interface ExecutionContext {
|
|
5
|
+
distinctId?: string;
|
|
6
|
+
disableTelemetry?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Sets the global execution context.
|
|
10
|
+
* This should be called early in the command lifecycle.
|
|
11
|
+
* @param context The context object containing distinctId and disableTelemetry status.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setExecutionContext(context: ExecutionContext): void;
|
|
14
|
+
/**
|
|
15
|
+
* Gets the currently set global execution context.
|
|
16
|
+
* @returns The current execution context.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getExecutionContext(): ExecutionContext;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Module-level variable to hold the current context.
|
|
2
|
+
// Initialize with default values (telemetry enabled, no distinctId yet).
|
|
3
|
+
let currentContext = {
|
|
4
|
+
disableTelemetry: false,
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Sets the global execution context.
|
|
8
|
+
* This should be called early in the command lifecycle.
|
|
9
|
+
* @param context The context object containing distinctId and disableTelemetry status.
|
|
10
|
+
*/
|
|
11
|
+
export function setExecutionContext(context) {
|
|
12
|
+
currentContext = context;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Gets the currently set global execution context.
|
|
16
|
+
* @returns The current execution context.
|
|
17
|
+
*/
|
|
18
|
+
export function getExecutionContext() {
|
|
19
|
+
return currentContext;
|
|
20
|
+
}
|
|
@@ -5,14 +5,19 @@ interface GitHubUrlParts {
|
|
|
5
5
|
ref?: string;
|
|
6
6
|
repo: string;
|
|
7
7
|
}
|
|
8
|
+
export interface TemplateInfo {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
}
|
|
8
13
|
export interface GitHubService {
|
|
9
14
|
getTemplateDirectories(template: string, customUrl?: string): Promise<string[]>;
|
|
10
|
-
getTemplates(customUrl?: string): Promise<
|
|
15
|
+
getTemplates(customUrl?: string): Promise<TemplateInfo[]>;
|
|
11
16
|
parseGitHubUrl(url: string): GitHubUrlParts;
|
|
12
17
|
}
|
|
13
18
|
export declare function createGitHub(token?: string): {
|
|
14
19
|
getTemplateDirectories: (template: string, customUrl?: string) => Promise<string[]>;
|
|
15
|
-
getTemplates: (customUrl?: string) => Promise<
|
|
20
|
+
getTemplates: (customUrl?: string) => Promise<TemplateInfo[]>;
|
|
16
21
|
parseGitHubUrl: typeof parseGitHubUrl;
|
|
17
22
|
};
|
|
18
23
|
export {};
|
package/dist/services/github.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Octokit } from '@octokit/rest';
|
|
2
2
|
import { DEFAULT_REPO } from '../lib/constants.js';
|
|
3
3
|
import { parseGitHubUrl } from '../lib/utils/parse-github-url.js';
|
|
4
|
+
import { ux } from '@oclif/core';
|
|
4
5
|
export function createGitHub(token) {
|
|
5
6
|
const octokit = new Octokit({
|
|
6
7
|
auth: token,
|
|
@@ -58,27 +59,112 @@ export function createGitHub(token) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
/**
|
|
61
|
-
* Get the templates for a repository.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* Get the templates for a repository, including name and description from package.json.
|
|
63
|
+
* Ensures 'blank' template appears last if found.
|
|
64
|
+
* If a direct URL to a template directory is provided, attempt to fetch its package.json.
|
|
65
|
+
* @param customUrl - The custom URL or base repository URL to get the templates for.
|
|
66
|
+
* @returns The templates for the repository with details, sorted.
|
|
64
67
|
*/
|
|
65
68
|
async function getTemplates(customUrl) {
|
|
66
|
-
//
|
|
69
|
+
// Handle direct URLs pointing to a specific template directory
|
|
67
70
|
if (customUrl?.startsWith('http')) {
|
|
68
|
-
|
|
71
|
+
const parsed = parseGitHubUrl(customUrl);
|
|
72
|
+
let name = parsed.path?.split('/').pop() || parsed.repo;
|
|
73
|
+
let description;
|
|
74
|
+
const packageJsonPath = joinPath(parsed.path || '', 'package.json');
|
|
75
|
+
try {
|
|
76
|
+
const { data: packageJsonContent } = await octokit.rest.repos.getContent({
|
|
77
|
+
owner: parsed.owner,
|
|
78
|
+
repo: parsed.repo,
|
|
79
|
+
path: packageJsonPath,
|
|
80
|
+
ref: parsed.ref,
|
|
81
|
+
mediaType: {
|
|
82
|
+
format: 'raw',
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
// getContent with mediaType: raw returns string directly
|
|
86
|
+
if (typeof packageJsonContent === 'string') {
|
|
87
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
88
|
+
const templateConfig = packageJson?.['directus:template'];
|
|
89
|
+
if (templateConfig?.name) {
|
|
90
|
+
name = templateConfig.name;
|
|
91
|
+
}
|
|
92
|
+
if (templateConfig?.description) {
|
|
93
|
+
description = templateConfig.description;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
// If package.json is missing or fails to parse, just use the default name derived from the URL.
|
|
99
|
+
// Don't warn here as it might be expected that a direct URL doesn't have this structure.
|
|
100
|
+
if (error.status !== 404) {
|
|
101
|
+
// Log other errors if needed for debugging, but don't show to user unless verbose?
|
|
102
|
+
console.error(`Error fetching package.json for direct URL ${customUrl}: ${error.message}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Return a single item array for the direct URL case
|
|
106
|
+
return [{ id: customUrl, name, description }];
|
|
69
107
|
}
|
|
70
108
|
const repo = customUrl ? parseGitHubUrl(customUrl) : DEFAULT_REPO;
|
|
71
|
-
const { data } = await octokit.rest.repos.getContent({
|
|
109
|
+
const { data: rootContent } = await octokit.rest.repos.getContent({
|
|
72
110
|
owner: repo.owner,
|
|
73
111
|
path: repo.path || '',
|
|
74
112
|
ref: repo.ref,
|
|
75
113
|
repo: repo.repo,
|
|
76
114
|
});
|
|
77
|
-
if (!Array.isArray(
|
|
115
|
+
if (!Array.isArray(rootContent))
|
|
78
116
|
return [];
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
117
|
+
const directories = rootContent.filter(item => item.type === 'dir');
|
|
118
|
+
// Fetch package.json for each directory concurrently
|
|
119
|
+
let templateInfos = await Promise.all(directories.map(async (dir) => {
|
|
120
|
+
const packageJsonPath = joinPath(repo.path || '', dir.path, 'package.json');
|
|
121
|
+
let name = dir.name;
|
|
122
|
+
let description;
|
|
123
|
+
try {
|
|
124
|
+
const { data: packageJsonContent } = await octokit.rest.repos.getContent({
|
|
125
|
+
owner: repo.owner,
|
|
126
|
+
repo: repo.repo,
|
|
127
|
+
path: packageJsonPath,
|
|
128
|
+
ref: repo.ref,
|
|
129
|
+
mediaType: {
|
|
130
|
+
format: 'raw',
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
// getContent with mediaType: raw returns string directly
|
|
134
|
+
if (typeof packageJsonContent === 'string') {
|
|
135
|
+
const packageJson = JSON.parse(packageJsonContent);
|
|
136
|
+
const templateConfig = packageJson?.['directus:template'];
|
|
137
|
+
if (templateConfig?.name) {
|
|
138
|
+
name = templateConfig.name;
|
|
139
|
+
}
|
|
140
|
+
if (templateConfig?.description) {
|
|
141
|
+
description = templateConfig.description;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
// Handle cases where package.json is missing or fails to parse
|
|
147
|
+
if (error.status !== 404) {
|
|
148
|
+
ux.warn(`Could not fetch or parse package.json for template "${dir.name}": ${error.message}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
id: dir.name,
|
|
153
|
+
name,
|
|
154
|
+
description,
|
|
155
|
+
};
|
|
156
|
+
}));
|
|
157
|
+
// Sort the templates to put "blank" last
|
|
158
|
+
templateInfos.sort((a, b) => {
|
|
159
|
+
const aIsBlank = a.id.toLowerCase() === 'blank' || a.name.toLowerCase() === 'blank';
|
|
160
|
+
const bIsBlank = b.id.toLowerCase() === 'blank' || b.name.toLowerCase() === 'blank';
|
|
161
|
+
if (aIsBlank && !bIsBlank)
|
|
162
|
+
return 1; // a comes AFTER b
|
|
163
|
+
if (!aIsBlank && bIsBlank)
|
|
164
|
+
return -1; // a comes BEFORE b
|
|
165
|
+
return a.name.localeCompare(b.name);
|
|
166
|
+
});
|
|
167
|
+
return templateInfos;
|
|
82
168
|
}
|
|
83
169
|
return {
|
|
84
170
|
getTemplateDirectories,
|
|
@@ -86,3 +172,7 @@ export function createGitHub(token) {
|
|
|
86
172
|
parseGitHubUrl,
|
|
87
173
|
};
|
|
88
174
|
}
|
|
175
|
+
// Helper functions
|
|
176
|
+
function joinPath(...segments) {
|
|
177
|
+
return segments.filter(Boolean).join('/');
|
|
178
|
+
}
|
|
@@ -35,3 +35,16 @@ export declare function track({ lifecycle, distinctId, command, flags, runId, me
|
|
|
35
35
|
properties?: Record<string, unknown>;
|
|
36
36
|
debug?: boolean;
|
|
37
37
|
}): void;
|
|
38
|
+
/**
|
|
39
|
+
* Manually capture an exception in PostHog
|
|
40
|
+
* @param error The error object to capture
|
|
41
|
+
* @param distinctId The distinct ID for the user
|
|
42
|
+
* @param properties Optional additional properties to track
|
|
43
|
+
* @param debug Whether to log debug information
|
|
44
|
+
*/
|
|
45
|
+
export declare function captureException({ error, distinctId, properties, debug }: {
|
|
46
|
+
error: unknown;
|
|
47
|
+
distinctId: string;
|
|
48
|
+
properties?: Record<string, unknown>;
|
|
49
|
+
debug?: boolean;
|
|
50
|
+
}): void;
|
package/dist/services/posthog.js
CHANGED
|
@@ -79,11 +79,37 @@ export function track({ lifecycle, distinctId, command, flags, runId, message, c
|
|
|
79
79
|
phClient.capture({
|
|
80
80
|
distinctId,
|
|
81
81
|
event: `directus_template_cli.${command}.${lifecycle}`,
|
|
82
|
-
properties: eventProperties
|
|
82
|
+
properties: { eventProperties }
|
|
83
83
|
});
|
|
84
84
|
if (debug)
|
|
85
85
|
ux.stdout('Event tracked successfully');
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* Manually capture an exception in PostHog
|
|
89
|
+
* @param error The error object to capture
|
|
90
|
+
* @param distinctId The distinct ID for the user
|
|
91
|
+
* @param properties Optional additional properties to track
|
|
92
|
+
* @param debug Whether to log debug information
|
|
93
|
+
*/
|
|
94
|
+
export function captureException({ error, distinctId, properties = {}, debug = false }) {
|
|
95
|
+
if (debug)
|
|
96
|
+
ux.stdout('Capturing exception...');
|
|
97
|
+
const phClient = getClient(debug);
|
|
98
|
+
const exceptionProperties = properties;
|
|
99
|
+
if (debug) {
|
|
100
|
+
ux.stdout('Sending exception data:');
|
|
101
|
+
ux.stdout(`Error: ${error}`);
|
|
102
|
+
ux.stdout(`Properties: ${JSON.stringify(exceptionProperties)}`);
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
phClient.captureException(error, distinctId, properties);
|
|
106
|
+
if (debug)
|
|
107
|
+
ux.stdout('Exception captured successfully');
|
|
108
|
+
}
|
|
109
|
+
catch (captureError) {
|
|
110
|
+
ux.warn(`Failed to capture PostHog exception: ${captureError}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
87
113
|
/**
|
|
88
114
|
* Get environment info
|
|
89
115
|
* @param config The config to get environment info from
|
package/oclif.manifest.json
CHANGED
|
@@ -159,6 +159,13 @@
|
|
|
159
159
|
"name": "users",
|
|
160
160
|
"allowNo": true,
|
|
161
161
|
"type": "boolean"
|
|
162
|
+
},
|
|
163
|
+
"disableTelemetry": {
|
|
164
|
+
"description": "Disable telemetry",
|
|
165
|
+
"env": "DISABLE_TELEMETRY",
|
|
166
|
+
"name": "disableTelemetry",
|
|
167
|
+
"allowNo": false,
|
|
168
|
+
"type": "boolean"
|
|
162
169
|
}
|
|
163
170
|
},
|
|
164
171
|
"hasDynamicHelp": false,
|
|
@@ -281,6 +288,13 @@
|
|
|
281
288
|
"hasDynamicHelp": false,
|
|
282
289
|
"multiple": false,
|
|
283
290
|
"type": "option"
|
|
291
|
+
},
|
|
292
|
+
"disableTelemetry": {
|
|
293
|
+
"description": "Disable telemetry",
|
|
294
|
+
"env": "DISABLE_TELEMETRY",
|
|
295
|
+
"name": "disableTelemetry",
|
|
296
|
+
"allowNo": false,
|
|
297
|
+
"type": "boolean"
|
|
284
298
|
}
|
|
285
299
|
},
|
|
286
300
|
"hasDynamicHelp": false,
|
|
@@ -341,10 +355,13 @@
|
|
|
341
355
|
"allowNo": true,
|
|
342
356
|
"type": "boolean"
|
|
343
357
|
},
|
|
344
|
-
"
|
|
358
|
+
"overwriteDir": {
|
|
359
|
+
"aliases": [
|
|
360
|
+
"overwrite-dir"
|
|
361
|
+
],
|
|
345
362
|
"description": "Override the default directory",
|
|
346
|
-
"name": "
|
|
347
|
-
"allowNo":
|
|
363
|
+
"name": "overwriteDir",
|
|
364
|
+
"allowNo": true,
|
|
348
365
|
"type": "boolean"
|
|
349
366
|
},
|
|
350
367
|
"template": {
|
|
@@ -378,5 +395,5 @@
|
|
|
378
395
|
]
|
|
379
396
|
}
|
|
380
397
|
},
|
|
381
|
-
"version": "0.7.0-beta.
|
|
398
|
+
"version": "0.7.0-beta.15"
|
|
382
399
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "directus-template-cli",
|
|
3
|
-
"version": "0.7.0-beta.
|
|
3
|
+
"version": "0.7.0-beta.15",
|
|
4
4
|
"description": "CLI Utility for applying templates to a Directus instance.",
|
|
5
5
|
"author": "bryantgillespie @bryantgillespie",
|
|
6
6
|
"type": "module",
|
|
@@ -38,8 +38,7 @@
|
|
|
38
38
|
"log-update": "^6.1.0",
|
|
39
39
|
"nypm": "^0.6.0",
|
|
40
40
|
"pathe": "^2.0.3",
|
|
41
|
-
"posthog-node": "^4.10.1"
|
|
42
|
-
"terminal-link": "^4.0.0"
|
|
41
|
+
"posthog-node": "^4.10.1"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
44
|
"@directus/types": "^13.0.0",
|