directus-template-cli 0.7.0-beta.8 → 0.7.0
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 +109 -8
- package/dist/commands/apply.d.ts +3 -2
- package/dist/commands/apply.js +80 -10
- 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 +46 -12
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +35 -22
- package/dist/lib/constants.d.ts +8 -0
- package/dist/lib/constants.js +10 -1
- package/dist/lib/init/config.js +1 -1
- package/dist/lib/init/index.js +41 -24
- package/dist/lib/load/apply-flags.d.ts +2 -1
- package/dist/lib/sdk.js +19 -10
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/utils/auth.d.ts +4 -0
- package/dist/lib/utils/auth.js +34 -2
- 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 +66 -9
- 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 +1 -1
package/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
# Directus Template CLI
|
|
2
2
|
|
|
3
|
-
A streamlined CLI tool for managing Directus templates - making it easy to apply and extract template configurations across instances.
|
|
3
|
+
A streamlined CLI tool for creating new Directus projects and managing Directus templates - making it easy to apply and extract template configurations across instances.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
This tool is best suited for:
|
|
6
6
|
- Proof of Concept (POC) projects
|
|
7
7
|
- Demo environments
|
|
8
8
|
- New project setups
|
|
9
9
|
|
|
10
|
-
We strongly recommend against using this tool in existing production environments or as a critical part of your CI/CD pipeline without thorough testing. Always create backups before applying templates.
|
|
10
|
+
⚠️ We strongly recommend against using this tool in existing production environments or as a critical part of your CI/CD pipeline without thorough testing. Always create backups before applying templates.
|
|
11
11
|
|
|
12
12
|
**Important Notes:**
|
|
13
13
|
- **Primary Purpose**: Built to deploy templates created by the Directus Core Team. While community templates are supported, the unlimited possible configurations make comprehensive support challenging.
|
|
14
|
-
- **Database Compatibility**: PostgreSQL and SQLite
|
|
14
|
+
- **Database Compatibility**: PostgreSQL is recommended. Applying templates that are extracted and applied between different databases (Extract from SQLite -> Apply to Postgres) can caused issues and is not recommended. MySQL users may encounter known issues.
|
|
15
15
|
- **Performance**: Remote operations (extract/apply) are rate-limited to 10 requests/second using bottleneck. Processing time varies based on your instance size (collections, items, assets).
|
|
16
16
|
- **Version Compatibility**:
|
|
17
17
|
- v0.5.0+: Compatible with Directus 11 and up
|
|
@@ -19,6 +19,105 @@ We strongly recommend against using this tool in existing production environment
|
|
|
19
19
|
|
|
20
20
|
Using the @latest tag ensures you're receiving the latest version of the packaged templates with the CLI. You can review [the specific versions on NPM](https://www.npmjs.com/package/directus-template-cli) and use @{version} syntax to apply the templates included with that version.
|
|
21
21
|
|
|
22
|
+
## Initializing a New Project
|
|
23
|
+
|
|
24
|
+
The CLI can initialize a new Directus project with an optional frontend framework using official or community templates.
|
|
25
|
+
|
|
26
|
+
1. Run the following command and follow the interactive prompts:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
npx directus-template-cli@latest init
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You'll be guided through:
|
|
33
|
+
- Selecting a directory for your new project
|
|
34
|
+
- Choosing a Directus backend template
|
|
35
|
+
- Selecting a frontend framework (if available for the template)
|
|
36
|
+
- Setting up Git and installing dependencies
|
|
37
|
+
|
|
38
|
+
### Command Options
|
|
39
|
+
|
|
40
|
+
You can also provide arguments and flags:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
npx directus-template-cli@latest init my-project
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The first argument (`my-project` in the example above) specifies the directory where the project will be created. If not provided, you'll be prompted to enter a directory during the interactive process.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
npx directus-template-cli@latest init --frontend=nextjs --template=cms
|
|
50
|
+
npx directus-template-cli@latest init my-project --frontend=nextjs --template=cms
|
|
51
|
+
npx directus-template-cli@latest init --template=https://github.com/directus-labs/starters/tree/main/cms
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Available flags:
|
|
56
|
+
|
|
57
|
+
- `--frontend`: Frontend framework to use (e.g., nextjs, nuxt, astro)
|
|
58
|
+
- `--gitInit`: Initialize a new Git repository (defaults to true, use --no-gitInit to disable)
|
|
59
|
+
- `--installDeps`: Install dependencies automatically (defaults to true, use --no-installDeps to disable)
|
|
60
|
+
- `--overwriteDir`: Override the default directory if it already exists (defaults to false)
|
|
61
|
+
- `--template`: Template name (e.g., simple-cms) or GitHub URL (e.g., https://github.com/directus-labs/starters/tree/main/cms)
|
|
62
|
+
- `--disableTelemetry`: Disable telemetry collection
|
|
63
|
+
|
|
64
|
+
You can use any public GitHub repository URL for the `--template` parameter, pointing to the specific directory containing the template. This is especially useful for using community-maintained templates or your own custom templates hosted on GitHub.
|
|
65
|
+
|
|
66
|
+
### Creating Custom Templates
|
|
67
|
+
|
|
68
|
+
You can create your own custom templates for use with the `init` command. A template is defined by a `package.json` file with a `directus:template` property that specifies the template configuration.
|
|
69
|
+
|
|
70
|
+
NOTE: the `init` command will NOT work without this step of the process.
|
|
71
|
+
|
|
72
|
+
Here's an example of a template configuration:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"name": "directus-cms-starter",
|
|
77
|
+
"version": "1.0.0",
|
|
78
|
+
"description": "A starter template for Directus CMS projects",
|
|
79
|
+
"directus:template": {
|
|
80
|
+
"name": "CMS",
|
|
81
|
+
"description": "A ready-to-use CMS with block builder, visual editing, and integration with your favorite framework.",
|
|
82
|
+
"template": "./directus/template",
|
|
83
|
+
"frontends": {
|
|
84
|
+
"nextjs": {
|
|
85
|
+
"name": "Next.js",
|
|
86
|
+
"path": "./nextjs"
|
|
87
|
+
},
|
|
88
|
+
"nuxt": {
|
|
89
|
+
"name": "Nuxt",
|
|
90
|
+
"path": "./nuxt"
|
|
91
|
+
},
|
|
92
|
+
"astro": {
|
|
93
|
+
"name": "Astro",
|
|
94
|
+
"path": "./astro"
|
|
95
|
+
},
|
|
96
|
+
"svelte": {
|
|
97
|
+
"name": "Svelte",
|
|
98
|
+
"path": "./sveltekit"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
The `directus:template` property contains:
|
|
106
|
+
|
|
107
|
+
- `name`: Display name for the template in the CLI
|
|
108
|
+
- `description`: A brief description of the template's purpose and features
|
|
109
|
+
- `template`: Path to the Directus template directory (containing schema, permissions, etc.) - this should point to a template extracted using the `extract` command
|
|
110
|
+
- `frontends`: Object defining available frontend frameworks for this template
|
|
111
|
+
- Each key is a frontend identifier used with the `--frontend` flag
|
|
112
|
+
- Each frontend has a `name` (display name) and `path` (directory containing the frontend code)
|
|
113
|
+
|
|
114
|
+
When you use this template with the `init` command, it will:
|
|
115
|
+
1. Copy the Directus template files from the specified template directory
|
|
116
|
+
2. Copy the selected frontend code based on your choice or the `--frontend` flag
|
|
117
|
+
3. Set up the project structure with both backend and frontend integrated
|
|
118
|
+
|
|
119
|
+
> **Note**: The template directory (`./directus/template` in the example above) should contain a valid Directus template created using the `extract` command. The directory structure should match what is created by the CLI when extracting a template, with subdirectories for schema, permissions, content, etc.
|
|
120
|
+
|
|
22
121
|
## Applying a Template
|
|
23
122
|
|
|
24
123
|
🚧 Make backups of your project/database before applying templates.
|
|
@@ -59,7 +158,7 @@ npx directus-template-cli@latest apply -p --directusUrl="http://localhost:8055"
|
|
|
59
158
|
|
|
60
159
|
```
|
|
61
160
|
|
|
62
|
-
Available flags
|
|
161
|
+
Available flags:
|
|
63
162
|
|
|
64
163
|
- `--directusUrl`: URL of the Directus instance to apply the template to (required)
|
|
65
164
|
- `--directusToken`: Token to use for the Directus instance (required if not using email/password)
|
|
@@ -77,6 +176,7 @@ Available flags for programmatic mode:
|
|
|
77
176
|
- `--schema`: Load Schema
|
|
78
177
|
- `--settings`: Load Settings
|
|
79
178
|
- `--users`: Load Users
|
|
179
|
+
- `--disableTelemetry`: Disable telemetry collection
|
|
80
180
|
|
|
81
181
|
When using `--partial`, you can also use `--no` flags to exclude specific components from being applied. For example:
|
|
82
182
|
|
|
@@ -130,7 +230,7 @@ You can also pass flags as environment variables. This can be useful for CI/CD p
|
|
|
130
230
|
- `TARGET_DIRECTUS_PASSWORD`: Equivalent to `--userPassword`
|
|
131
231
|
- `TEMPLATE_LOCATION`: Equivalent to `--templateLocation`
|
|
132
232
|
- `TEMPLATE_TYPE`: Equivalent to `--templateType`
|
|
133
|
-
|
|
233
|
+
|
|
134
234
|
|
|
135
235
|
### Existing Data
|
|
136
236
|
|
|
@@ -142,7 +242,7 @@ In most of the system collections (collections,roles, permissions, etc.), if an
|
|
|
142
242
|
|
|
143
243
|
Exceptions:
|
|
144
244
|
|
|
145
|
-
- directus_settings
|
|
245
|
+
- `directus_settings`: The CLI attempts to merge the template's project settings with the existing settings in the target instance. Using the existing settings as a base and updating them with the values from the template. This should prevent overwriting branding, themes, and other customizations.
|
|
146
246
|
|
|
147
247
|
**Your Collections:**
|
|
148
248
|
|
|
@@ -181,7 +281,7 @@ Using email/password:
|
|
|
181
281
|
npx directus-template-cli@latest extract -p --templateName="My Template" --templateLocation="./my-template" --userEmail="admin@example.com" --userPassword="admin" --directusUrl="http://localhost:8055"
|
|
182
282
|
```
|
|
183
283
|
|
|
184
|
-
Available flags
|
|
284
|
+
Available flags:
|
|
185
285
|
|
|
186
286
|
- `--directusUrl`: URL of the Directus instance to extract the template from (required)
|
|
187
287
|
- `--directusToken`: Token to use for the Directus instance (required if not using email/password)
|
|
@@ -189,6 +289,7 @@ Available flags for programmatic mode:
|
|
|
189
289
|
- `--userPassword`: Password for Directus authentication (required if not using token)
|
|
190
290
|
- `--templateLocation`: Directory to extract the template to (required)
|
|
191
291
|
- `--templateName`: Name of the template (required)
|
|
292
|
+
- `--disableTelemetry`: Disable telemetry collection
|
|
192
293
|
|
|
193
294
|
#### Using Environment Variables
|
|
194
295
|
|
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,18 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { text, select,
|
|
1
|
+
import { Flags, ux } from '@oclif/core';
|
|
2
|
+
import { text, select, log, intro } from '@clack/prompts';
|
|
3
3
|
import * as path from 'pathe';
|
|
4
4
|
import { animatedBunny } from '../lib/utils/animated-bunny.js';
|
|
5
5
|
import * as customFlags from '../flags/common.js';
|
|
6
6
|
import { DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR } from '../lib/constants.js';
|
|
7
7
|
import { validateInteractiveFlags, validateProgrammaticFlags } from '../lib/load/apply-flags.js';
|
|
8
8
|
import apply from '../lib/load/index.js';
|
|
9
|
-
import { getDirectusToken, getDirectusUrl, initializeDirectusApi } from '../lib/utils/auth.js';
|
|
9
|
+
import { getDirectusToken, getDirectusUrl, initializeDirectusApi, getDirectusEmailAndPassword } from '../lib/utils/auth.js';
|
|
10
10
|
import catchError from '../lib/utils/catch-error.js';
|
|
11
11
|
import { getCommunityTemplates, getGithubTemplate, getInteractiveLocalTemplate, getLocalTemplate } from '../lib/utils/get-template.js';
|
|
12
12
|
import { logger } from '../lib/utils/logger.js';
|
|
13
13
|
import openUrl from '../lib/utils/open-url.js';
|
|
14
14
|
import chalk from 'chalk';
|
|
15
|
-
|
|
15
|
+
import { BaseCommand } from './base.js';
|
|
16
|
+
import { track, shutdown } from '../services/posthog.js';
|
|
17
|
+
import { BSL_LICENSE_HEADLINE, BSL_LICENSE_TEXT, BSL_LICENSE_CTA } from '../lib/constants.js';
|
|
18
|
+
export default class ApplyCommand extends BaseCommand {
|
|
16
19
|
static description = 'Apply a template to a blank Directus instance.';
|
|
17
20
|
static examples = [
|
|
18
21
|
'$ directus-template-cli apply',
|
|
@@ -85,6 +88,7 @@ export default class ApplyCommand extends Command {
|
|
|
85
88
|
default: undefined,
|
|
86
89
|
description: 'Load users',
|
|
87
90
|
}),
|
|
91
|
+
disableTelemetry: customFlags.disableTelemetry,
|
|
88
92
|
};
|
|
89
93
|
/**
|
|
90
94
|
* MAIN
|
|
@@ -164,21 +168,52 @@ export default class ApplyCommand extends Command {
|
|
|
164
168
|
validatedFlags.directusToken = directusToken;
|
|
165
169
|
}
|
|
166
170
|
else {
|
|
167
|
-
const userEmail = await
|
|
168
|
-
message: 'What is your email?',
|
|
169
|
-
});
|
|
171
|
+
const { userEmail, userPassword } = await getDirectusEmailAndPassword();
|
|
170
172
|
validatedFlags.userEmail = userEmail;
|
|
171
|
-
const userPassword = await password({
|
|
172
|
-
message: 'What is your password?',
|
|
173
|
-
});
|
|
174
173
|
validatedFlags.userPassword = userPassword;
|
|
175
174
|
}
|
|
176
175
|
await initializeDirectusApi(validatedFlags);
|
|
177
176
|
if (template) {
|
|
178
177
|
// /* TODO: Replace with custom styledHeader function */ ux.styledHeader(ux.colorize(DIRECTUS_PURPLE, `Applying template - ${template.templateName} to ${directusUrl}`))
|
|
178
|
+
// Track start just before applying
|
|
179
|
+
if (!validatedFlags.disableTelemetry) {
|
|
180
|
+
await track({
|
|
181
|
+
command: 'apply',
|
|
182
|
+
lifecycle: 'start',
|
|
183
|
+
distinctId: this.userConfig.distinctId,
|
|
184
|
+
flags: {
|
|
185
|
+
programmatic: false,
|
|
186
|
+
templateName: template.templateName,
|
|
187
|
+
templateType,
|
|
188
|
+
// Include other relevant flags from validatedFlags if needed
|
|
189
|
+
...validatedFlags,
|
|
190
|
+
},
|
|
191
|
+
runId: this.runId,
|
|
192
|
+
config: this.config,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
179
195
|
await apply(template.directoryPath, validatedFlags);
|
|
180
196
|
ux.action.stop();
|
|
197
|
+
// Track completion before final messages/exit
|
|
198
|
+
if (!validatedFlags.disableTelemetry) {
|
|
199
|
+
await track({
|
|
200
|
+
command: 'apply',
|
|
201
|
+
lifecycle: 'complete',
|
|
202
|
+
distinctId: this.userConfig.distinctId,
|
|
203
|
+
flags: {
|
|
204
|
+
templateName: template.templateName,
|
|
205
|
+
templateType,
|
|
206
|
+
...validatedFlags,
|
|
207
|
+
},
|
|
208
|
+
runId: this.runId,
|
|
209
|
+
config: this.config,
|
|
210
|
+
});
|
|
211
|
+
await shutdown();
|
|
212
|
+
}
|
|
181
213
|
ux.stdout(SEPARATOR);
|
|
214
|
+
log.warn(BSL_LICENSE_HEADLINE);
|
|
215
|
+
log.info(BSL_LICENSE_TEXT);
|
|
216
|
+
log.info(BSL_LICENSE_CTA);
|
|
182
217
|
ux.stdout('Template applied successfully.');
|
|
183
218
|
ux.exit(0);
|
|
184
219
|
}
|
|
@@ -216,10 +251,45 @@ export default class ApplyCommand extends Command {
|
|
|
216
251
|
const logMessage = `Applying template - ${template.templateName} to ${validatedFlags.directusUrl}`;
|
|
217
252
|
// /* TODO: Replace with custom styledHeader function */ ux.styledHeader(logMessage)
|
|
218
253
|
logger.log('info', logMessage);
|
|
254
|
+
// Track start just before applying
|
|
255
|
+
if (!validatedFlags.disableTelemetry) {
|
|
256
|
+
await track({
|
|
257
|
+
command: 'apply',
|
|
258
|
+
lifecycle: 'start',
|
|
259
|
+
distinctId: this.userConfig.distinctId,
|
|
260
|
+
flags: {
|
|
261
|
+
programmatic: true,
|
|
262
|
+
templateName: template.templateName,
|
|
263
|
+
// Include other relevant flags from validatedFlags
|
|
264
|
+
...validatedFlags,
|
|
265
|
+
},
|
|
266
|
+
runId: this.runId,
|
|
267
|
+
config: this.config,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
219
270
|
await apply(template.directoryPath, validatedFlags);
|
|
220
271
|
ux.action.stop();
|
|
272
|
+
// Track completion before final messages/exit
|
|
273
|
+
if (!validatedFlags.disableTelemetry) {
|
|
274
|
+
await track({
|
|
275
|
+
command: 'apply',
|
|
276
|
+
lifecycle: 'complete',
|
|
277
|
+
distinctId: this.userConfig.distinctId,
|
|
278
|
+
flags: {
|
|
279
|
+
templateName: template.templateName,
|
|
280
|
+
// Include other relevant flags from validatedFlags
|
|
281
|
+
...validatedFlags,
|
|
282
|
+
},
|
|
283
|
+
runId: this.runId,
|
|
284
|
+
config: this.config,
|
|
285
|
+
});
|
|
286
|
+
await shutdown();
|
|
287
|
+
}
|
|
221
288
|
ux.stdout(SEPARATOR);
|
|
222
289
|
ux.stdout('Template applied successfully.');
|
|
290
|
+
log.warn(BSL_LICENSE_HEADLINE);
|
|
291
|
+
log.info(BSL_LICENSE_TEXT);
|
|
292
|
+
log.info(BSL_LICENSE_CTA);
|
|
223
293
|
// ux.exit(0)
|
|
224
294
|
}
|
|
225
295
|
/**
|
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,17 +1,19 @@
|
|
|
1
|
-
import { text,
|
|
2
|
-
import {
|
|
1
|
+
import { text, select, intro, log } from '@clack/prompts';
|
|
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';
|
|
6
6
|
import path from 'pathe';
|
|
7
7
|
import * as customFlags from '../flags/common.js';
|
|
8
|
-
import { DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR } from '../lib/constants.js';
|
|
8
|
+
import { DIRECTUS_PINK, DIRECTUS_PURPLE, SEPARATOR, BSL_LICENSE_TEXT, BSL_LICENSE_CTA, BSL_LICENSE_HEADLINE } 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
|
-
import { getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags } from '../lib/utils/auth.js';
|
|
13
|
+
import { getDirectusToken, getDirectusUrl, initializeDirectusApi, validateAuthFlags, getDirectusEmailAndPassword } 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,26 @@ 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
|
+
}
|
|
104
|
+
log.warn(BSL_LICENSE_HEADLINE);
|
|
105
|
+
log.info(BSL_LICENSE_TEXT);
|
|
106
|
+
log.info(BSL_LICENSE_CTA);
|
|
68
107
|
ux.stdout(SEPARATOR);
|
|
69
108
|
ux.stdout('Template extracted successfully.');
|
|
70
109
|
this.exit(0);
|
|
@@ -104,13 +143,8 @@ export default class ExtractCommand extends Command {
|
|
|
104
143
|
flags.directusToken = directusToken;
|
|
105
144
|
}
|
|
106
145
|
else {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
flags.userEmail = email;
|
|
111
|
-
const userPassword = await password({
|
|
112
|
-
message: 'What is our password?',
|
|
113
|
-
});
|
|
146
|
+
const { userEmail, userPassword } = await getDirectusEmailAndPassword();
|
|
147
|
+
flags.userEmail = userEmail;
|
|
114
148
|
flags.userPassword = userPassword;
|
|
115
149
|
}
|
|
116
150
|
ux.stdout(SEPARATOR);
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { confirm, intro, select, text, isCancel, cancel } from '@clack/prompts';
|
|
1
|
+
import { confirm, intro, select, text, isCancel, cancel, log as clackLog } from '@clack/prompts';
|
|
2
2
|
import { Args, Flags, ux } from '@oclif/core';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import fs from 'node:fs';
|
|
@@ -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
|
}),
|
|
@@ -81,7 +83,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
81
83
|
const github = createGitHub();
|
|
82
84
|
// If no dir is provided, ask for it
|
|
83
85
|
if (!args.directory || args.directory === '.') {
|
|
84
|
-
|
|
86
|
+
let dirResponse = await text({
|
|
85
87
|
message: 'Enter the directory to create the project in:',
|
|
86
88
|
placeholder: './my-directus-project',
|
|
87
89
|
});
|
|
@@ -89,30 +91,38 @@ export default class InitCommand extends BaseCommand {
|
|
|
89
91
|
cancel('Project creation cancelled.');
|
|
90
92
|
process.exit(0);
|
|
91
93
|
}
|
|
94
|
+
// If there's no response, set a default
|
|
95
|
+
if (!dirResponse) {
|
|
96
|
+
clackLog.warn('No directory provided, using default: ./my-directus-project');
|
|
97
|
+
dirResponse = './my-directus-project';
|
|
98
|
+
}
|
|
92
99
|
this.targetDir = dirResponse;
|
|
93
100
|
}
|
|
94
|
-
if (fs.existsSync(this.targetDir) && !flags.
|
|
95
|
-
const
|
|
101
|
+
if (fs.existsSync(this.targetDir) && !flags.overwriteDir) {
|
|
102
|
+
const overwriteDirResponse = await confirm({
|
|
96
103
|
message: 'Directory already exists. Would you like to overwrite it?',
|
|
104
|
+
initialValue: false,
|
|
97
105
|
});
|
|
98
|
-
if (isCancel(
|
|
106
|
+
if (isCancel(overwriteDirResponse) || overwriteDirResponse === false) {
|
|
99
107
|
cancel('Project creation cancelled.');
|
|
100
108
|
process.exit(0);
|
|
101
109
|
}
|
|
102
|
-
if (
|
|
103
|
-
flags.
|
|
110
|
+
if (overwriteDirResponse) {
|
|
111
|
+
flags.overwriteDir = true;
|
|
104
112
|
}
|
|
105
113
|
}
|
|
106
|
-
// 1. Fetch available templates
|
|
114
|
+
// 1. Fetch available templates (now returns Array<{id: string, name: string, description?: string}>)
|
|
107
115
|
const availableTemplates = await github.getTemplates();
|
|
108
116
|
// 2. Prompt for template if not provided
|
|
109
|
-
let { template } = flags;
|
|
117
|
+
let { template } = flags; // This will store the chosen template ID
|
|
118
|
+
let chosenTemplateObject;
|
|
110
119
|
if (!template) {
|
|
111
120
|
const templateResponse = await select({
|
|
112
121
|
message: 'Which Directus backend template would you like to use?',
|
|
113
|
-
options: availableTemplates.map(
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
116
126
|
})),
|
|
117
127
|
});
|
|
118
128
|
if (isCancel(templateResponse)) {
|
|
@@ -121,22 +131,25 @@ export default class InitCommand extends BaseCommand {
|
|
|
121
131
|
}
|
|
122
132
|
template = templateResponse;
|
|
123
133
|
}
|
|
124
|
-
//
|
|
125
|
-
|
|
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
|
|
126
137
|
const isDirectUrl = template?.startsWith('http');
|
|
127
|
-
|
|
128
|
-
|
|
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.`);
|
|
129
142
|
const templateNameResponse = await text({
|
|
130
|
-
message: 'Please enter a valid template
|
|
143
|
+
message: 'Please enter a valid template ID, a direct GitHub URL, or Ctrl+C to cancel:',
|
|
131
144
|
});
|
|
132
145
|
if (isCancel(templateNameResponse)) {
|
|
133
146
|
cancel('Project creation cancelled.');
|
|
134
147
|
process.exit(0);
|
|
135
148
|
}
|
|
136
149
|
template = templateNameResponse;
|
|
137
|
-
|
|
150
|
+
chosenTemplateObject = availableTemplates.find(t => t.id === template); // Update chosen object after re-entry
|
|
138
151
|
}
|
|
139
|
-
flags.template = template;
|
|
152
|
+
flags.template = template; // Ensure the flag stores the ID
|
|
140
153
|
// Download the template to a temporary directory to read its configuration
|
|
141
154
|
const tempDir = path.join(os.tmpdir(), `directus-template-${Date.now()}`);
|
|
142
155
|
let chosenFrontend = flags.frontend;
|
|
@@ -215,7 +228,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
215
228
|
gitInit: initGit,
|
|
216
229
|
installDeps,
|
|
217
230
|
template,
|
|
218
|
-
|
|
231
|
+
overwriteDir: flags.overwriteDir,
|
|
219
232
|
},
|
|
220
233
|
});
|
|
221
234
|
// Track the command completion unless telemetry is disabled
|
|
@@ -229,7 +242,7 @@ export default class InitCommand extends BaseCommand {
|
|
|
229
242
|
gitInit: initGit,
|
|
230
243
|
installDeps,
|
|
231
244
|
template,
|
|
232
|
-
|
|
245
|
+
overwriteDir: flags.overwriteDir,
|
|
233
246
|
},
|
|
234
247
|
runId: this.runId,
|
|
235
248
|
config: this.config,
|
package/dist/lib/constants.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export declare const DIRECTUS_PURPLE = "#6644ff";
|
|
2
2
|
export declare const DIRECTUS_PINK = "#FF99DD";
|
|
3
3
|
export declare const SEPARATOR = "------------------";
|
|
4
|
+
export declare const pinkText: import("chalk").ChalkInstance;
|
|
5
|
+
export declare const purpleText: import("chalk").ChalkInstance;
|
|
4
6
|
export declare const COMMUNITY_TEMPLATE_REPO: {
|
|
5
7
|
string: string;
|
|
6
8
|
url: string;
|
|
@@ -15,3 +17,9 @@ export declare const DEFAULT_REPO: {
|
|
|
15
17
|
export declare const POSTHOG_PUBLIC_KEY = "phc_STopE6gj6LDIjYonVF7493kQJK8S4v0Xrl6YPr2z9br";
|
|
16
18
|
export declare const POSTHOG_HOST = "https://us.i.posthog.com";
|
|
17
19
|
export declare const DEFAULT_BRANCH = "main";
|
|
20
|
+
export declare const BSL_LICENSE_URL = "https://directus.io/bsl";
|
|
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;
|
|
25
|
+
export declare const DEFAULT_DIRECTUS_URL = "http://localhost:8055";
|