@loopress/cli 0.7.0 → 0.8.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 +45 -45
- package/dist/commands/composer/pull.js +4 -14
- package/dist/commands/composer/push.js +7 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/plugin/add.js +9 -9
- package/dist/commands/plugin/pull.js +7 -18
- package/dist/commands/plugin/push.d.ts +1 -0
- package/dist/commands/plugin/push.js +20 -45
- package/dist/commands/snippet/list.d.ts +1 -1
- package/dist/commands/snippet/list.js +23 -38
- package/dist/commands/snippet/pull.d.ts +1 -1
- package/dist/commands/snippet/pull.js +41 -50
- package/dist/commands/snippet/push.d.ts +1 -1
- package/dist/commands/snippet/push.js +82 -70
- package/dist/config/auth.manager.d.ts +0 -2
- package/dist/config/auth.manager.js +5 -25
- package/dist/config/json-file.d.ts +2 -0
- package/dist/config/json-file.js +21 -0
- package/dist/config/project-config.manager.d.ts +1 -3
- package/dist/config/project-config.manager.js +7 -23
- package/dist/lib/base.d.ts +14 -4
- package/dist/lib/base.js +63 -33
- package/dist/lib/push-command.d.ts +0 -1
- package/dist/lib/push-command.js +0 -1
- package/dist/lib/wp-client.d.ts +15 -0
- package/dist/lib/wp-client.js +53 -0
- package/dist/types/snippet.d.ts +7 -1
- package/dist/utils/loopress-config.js +5 -2
- package/dist/utils/snippet-plugin-flag.d.ts +3 -0
- package/dist/utils/snippet-plugin-flag.js +8 -0
- package/dist/utils/snippet-plugin.d.ts +23 -2
- package/dist/utils/snippet-plugin.js +168 -13
- package/oclif.manifest.json +165 -125
- package/package.json +5 -3
- package/dist/types/menu.d.ts +0 -7
- package/dist/types/menu.js +0 -1
- /package/dist/{config/types.d.ts → types/config.d.ts} +0 -0
- /package/dist/{config/types.js → types/config.js} +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
-
import got from 'got';
|
|
3
2
|
import { LoopressCommand } from '../../lib/base.js';
|
|
3
|
+
import { snippetPluginFlag } from '../../utils/snippet-plugin-flag.js';
|
|
4
4
|
import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
|
|
5
5
|
export default class List extends LoopressCommand {
|
|
6
6
|
static description = 'List snippets from WordPress';
|
|
@@ -12,48 +12,33 @@ export default class List extends LoopressCommand {
|
|
|
12
12
|
static flags = {
|
|
13
13
|
...LoopressCommand.baseFlags,
|
|
14
14
|
json: Flags.boolean({ char: 'j', description: 'Output in JSON format' }),
|
|
15
|
-
|
|
16
|
-
char: 'p',
|
|
17
|
-
description: 'WordPress snippet plugin to target (overrides loopress.json)',
|
|
18
|
-
options: ['code-snippets', 'wpcode'],
|
|
19
|
-
}),
|
|
15
|
+
...snippetPluginFlag,
|
|
20
16
|
};
|
|
21
17
|
async run() {
|
|
22
18
|
const { flags } = await this.parse(List);
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
const adapter = getSnippetPlugin(this.resolveSnippetPlugin(flags.plugin));
|
|
20
|
+
const remoteList = await this.wp.get(adapter.endpointPath());
|
|
21
|
+
const snippets = remoteList.map((r) => adapter.fromRemote(r));
|
|
22
|
+
if (flags.json) {
|
|
23
|
+
this.log(JSON.stringify(snippets, null, 2));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (snippets.length === 0) {
|
|
27
|
+
this.log('No snippets found');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
|
|
31
|
+
this.log('');
|
|
32
|
+
for (const snippet of snippets) {
|
|
33
|
+
this.log(` ${snippet.id}. ${snippet.name}`);
|
|
34
|
+
this.log(` Active: ${snippet.active ? 'yes' : 'no'}`);
|
|
35
|
+
if (snippet.tags.length > 0) {
|
|
36
|
+
this.log(` Tags: ${snippet.tags.join(', ')}`);
|
|
34
37
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
this.log('No snippets found');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'}:`);
|
|
41
|
-
console.log('');
|
|
42
|
-
for (const snippet of snippets) {
|
|
43
|
-
this.log(` ${snippet.id}. ${snippet.name}`);
|
|
44
|
-
this.log(` Active: ${snippet.active ? '✓' : '✗'}`);
|
|
45
|
-
if (snippet.tags && snippet.tags.length > 0) {
|
|
46
|
-
this.log(` Tags: ${snippet.tags.join(', ')}`);
|
|
47
|
-
}
|
|
48
|
-
if (snippet.description) {
|
|
49
|
-
this.log(` Description: ${snippet.description}`);
|
|
50
|
-
}
|
|
51
|
-
console.log('');
|
|
52
|
-
}
|
|
38
|
+
if (snippet.description) {
|
|
39
|
+
this.log(` Description: ${snippet.description}`);
|
|
53
40
|
}
|
|
54
|
-
|
|
55
|
-
catch (error) {
|
|
56
|
-
this.error(`❌ Error listing snippets: ${error.message}`);
|
|
41
|
+
this.log('');
|
|
57
42
|
}
|
|
58
43
|
}
|
|
59
44
|
}
|
|
@@ -9,8 +9,8 @@ export default class Pull extends LoopressCommand {
|
|
|
9
9
|
static description: string;
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
|
-
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
12
|
plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
14
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
16
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { Args
|
|
2
|
-
import
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
3
4
|
import slugify from 'slugify';
|
|
4
5
|
import { LoopressCommand } from '../../lib/base.js';
|
|
6
|
+
import { snippetPluginFlag } from '../../utils/snippet-plugin-flag.js';
|
|
5
7
|
import { getSnippetPlugin } from '../../utils/snippet-plugin.js';
|
|
6
8
|
const EXTENSIONS = {
|
|
7
9
|
css: 'css',
|
|
@@ -22,11 +24,18 @@ export function buildMetaFile(snippet) {
|
|
|
22
24
|
name: snippet.name,
|
|
23
25
|
type: snippet.type,
|
|
24
26
|
active: snippet.active,
|
|
27
|
+
location: snippet.location,
|
|
25
28
|
};
|
|
26
29
|
if (snippet.description)
|
|
27
30
|
meta.description = snippet.description;
|
|
28
31
|
if (snippet.tags.length > 0)
|
|
29
32
|
meta.tags = snippet.tags;
|
|
33
|
+
if (snippet.insertMethod === 'shortcode')
|
|
34
|
+
meta.insertMethod = snippet.insertMethod;
|
|
35
|
+
if (snippet.priority !== 10)
|
|
36
|
+
meta.priority = snippet.priority;
|
|
37
|
+
if (snippet.shortcodeAttributes.length > 0)
|
|
38
|
+
meta.shortcodeAttributes = snippet.shortcodeAttributes;
|
|
30
39
|
return JSON.stringify(meta, null, 2) + '\n';
|
|
31
40
|
}
|
|
32
41
|
export default class Pull extends LoopressCommand {
|
|
@@ -42,60 +51,42 @@ export default class Pull extends LoopressCommand {
|
|
|
42
51
|
];
|
|
43
52
|
static flags = {
|
|
44
53
|
...LoopressCommand.baseFlags,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
char: 'p',
|
|
48
|
-
description: 'WordPress snippet plugin to target (overrides loopress.json)',
|
|
49
|
-
options: ['code-snippets', 'wpcode'],
|
|
50
|
-
}),
|
|
54
|
+
...LoopressCommand.dryRunFlag,
|
|
55
|
+
...snippetPluginFlag,
|
|
51
56
|
};
|
|
52
57
|
async run() {
|
|
53
58
|
const { args, flags } = await this.parse(Pull);
|
|
54
|
-
const dryRun = flags['dry-run'];
|
|
55
|
-
const { plugin } = flags;
|
|
56
59
|
const { url } = this.siteConfig;
|
|
57
|
-
const path =
|
|
58
|
-
const resolvedPlugin =
|
|
59
|
-
this.log(
|
|
60
|
-
this.log(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
let count = 0;
|
|
76
|
-
let skipped = 0;
|
|
77
|
-
for (const snippet of snippets) {
|
|
78
|
-
if (!snippet.name.trim()) {
|
|
79
|
-
skipped++;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
const ext = EXTENSIONS[snippet.type];
|
|
83
|
-
const slug = slugify(snippet.name, { lower: true, strict: true });
|
|
84
|
-
const base = `${snippet.id}-${slug}`;
|
|
85
|
-
const filePath = `${path}/${base}.${ext}`;
|
|
86
|
-
const metaPath = `${path}/${base}.json`;
|
|
87
|
-
await fs.writeFile(filePath, buildSnippetFile(snippet));
|
|
88
|
-
await fs.writeFile(metaPath, buildMetaFile(snippet));
|
|
89
|
-
count++;
|
|
90
|
-
this.log(`✅ Pulled: ${snippet.name}`);
|
|
91
|
-
}
|
|
92
|
-
this.log(`🎉 Successfully pulled ${count} snippet${count === 1 ? '' : 's'} to ${path}`);
|
|
93
|
-
if (skipped > 0) {
|
|
94
|
-
this.warn(`${skipped} snippet${skipped === 1 ? '' : 's'} skipped because they have no name`);
|
|
60
|
+
const path = this.resolveSnippetsPath(args.path);
|
|
61
|
+
const resolvedPlugin = this.resolveSnippetPlugin(flags.plugin);
|
|
62
|
+
this.log(`Pulling snippets from ${url} via ${resolvedPlugin}`);
|
|
63
|
+
this.log(`Snippets path: ${path}`);
|
|
64
|
+
const adapter = getSnippetPlugin(resolvedPlugin);
|
|
65
|
+
const remoteList = await this.wp.get(adapter.endpointPath());
|
|
66
|
+
const snippets = remoteList.map((r) => adapter.fromRemote(r));
|
|
67
|
+
if (this.dryRun) {
|
|
68
|
+
this.log(`[dry-run] Would pull ${snippets.length} snippet${snippets.length === 1 ? '' : 's'} to ${path}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
await mkdir(path, { recursive: true });
|
|
72
|
+
let count = 0;
|
|
73
|
+
let skipped = 0;
|
|
74
|
+
for (const snippet of snippets) {
|
|
75
|
+
if (!snippet.name.trim()) {
|
|
76
|
+
skipped++;
|
|
77
|
+
continue;
|
|
95
78
|
}
|
|
79
|
+
const ext = EXTENSIONS[snippet.type];
|
|
80
|
+
const slug = slugify(snippet.name, { lower: true, strict: true });
|
|
81
|
+
const base = `${snippet.id}-${slug}`;
|
|
82
|
+
await writeFile(join(path, `${base}.${ext}`), buildSnippetFile(snippet));
|
|
83
|
+
await writeFile(join(path, `${base}.json`), buildMetaFile(snippet));
|
|
84
|
+
count++;
|
|
85
|
+
this.log(` Pulled: ${snippet.name}`);
|
|
96
86
|
}
|
|
97
|
-
|
|
98
|
-
|
|
87
|
+
this.log(`Pulled ${count} snippet${count === 1 ? '' : 's'} to ${path}`);
|
|
88
|
+
if (skipped > 0) {
|
|
89
|
+
this.warn(`${skipped} snippet${skipped === 1 ? '' : 's'} skipped because they have no name`);
|
|
99
90
|
}
|
|
100
91
|
}
|
|
101
92
|
}
|
|
@@ -6,8 +6,8 @@ export default class Push extends PushCommand {
|
|
|
6
6
|
static description: string;
|
|
7
7
|
static examples: string[];
|
|
8
8
|
static flags: {
|
|
9
|
-
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
9
|
plugin: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Args
|
|
2
|
-
import
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { readdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { basename, dirname, extname, join } from 'node:path';
|
|
4
4
|
import slugify from 'slugify';
|
|
5
5
|
import { PushCommand } from '../../lib/push-command.js';
|
|
6
|
-
import {
|
|
6
|
+
import { snippetPluginFlag } from '../../utils/snippet-plugin-flag.js';
|
|
7
|
+
import { defaultLocationForType, getSnippetPlugin, parseInsertMethod, parseLocation, parseType, } from '../../utils/snippet-plugin.js';
|
|
7
8
|
const TYPE_BY_EXTENSION = {
|
|
8
9
|
'.css': 'css',
|
|
9
10
|
'.html': 'html',
|
|
@@ -24,45 +25,37 @@ export default class Push extends PushCommand {
|
|
|
24
25
|
];
|
|
25
26
|
static flags = {
|
|
26
27
|
...PushCommand.baseFlags,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
char: 'p',
|
|
30
|
-
description: 'WordPress snippet plugin to target (overrides loopress.json)',
|
|
31
|
-
options: ['code-snippets', 'wpcode'],
|
|
32
|
-
}),
|
|
28
|
+
...PushCommand.dryRunFlag,
|
|
29
|
+
...snippetPluginFlag,
|
|
33
30
|
};
|
|
34
31
|
async run() {
|
|
35
32
|
const { args, flags } = await this.parse(Push);
|
|
36
|
-
const dryRun = flags['dry-run'];
|
|
37
|
-
const { plugin } = flags;
|
|
38
|
-
this.dryRun = dryRun;
|
|
39
33
|
const { url } = this.siteConfig;
|
|
40
|
-
const path =
|
|
41
|
-
const resolvedPlugin =
|
|
42
|
-
this.log(
|
|
43
|
-
this.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await this.pushSnippet(snippet, { adapter, dryRun, headers, url });
|
|
53
|
-
}
|
|
54
|
-
await this.recordSuccess();
|
|
55
|
-
this.log('🎉 All snippets pushed successfully!');
|
|
34
|
+
const path = this.resolveSnippetsPath(args.path);
|
|
35
|
+
const resolvedPlugin = this.resolveSnippetPlugin(flags.plugin);
|
|
36
|
+
this.log(`Pushing snippets to ${url} via ${resolvedPlugin}`);
|
|
37
|
+
this.log(`Snippets path: ${path}`);
|
|
38
|
+
const snippets = await this.loadSnippets(path);
|
|
39
|
+
this.log(`Found ${snippets.length} snippet${snippets.length === 1 ? '' : 's'} to push`);
|
|
40
|
+
const adapter = getSnippetPlugin(resolvedPlugin);
|
|
41
|
+
let failed = 0;
|
|
42
|
+
for (const snippet of snippets) {
|
|
43
|
+
const pushed = await this.pushSnippet(snippet, adapter);
|
|
44
|
+
if (!pushed)
|
|
45
|
+
failed++;
|
|
56
46
|
}
|
|
57
|
-
|
|
58
|
-
this.error(
|
|
47
|
+
if (failed > 0) {
|
|
48
|
+
this.error(`${failed} snippet${failed === 1 ? '' : 's'} failed to push.`);
|
|
59
49
|
}
|
|
50
|
+
if (this.dryRun)
|
|
51
|
+
return;
|
|
52
|
+
await this.recordSuccess();
|
|
53
|
+
this.log('All snippets pushed.');
|
|
60
54
|
}
|
|
61
55
|
// Renames the local file pair to the `<id>-<slug>` convention used by `snippet pull` whenever
|
|
62
56
|
// it doesn't already match (e.g. a hand-created `demo.php` with no id, or a stale slug after a rename).
|
|
63
57
|
// This is a side effect of `push`: local files on disk are renamed, not just the remote snippet.
|
|
64
58
|
async ensureCanonicalFilename(snippet, id, name) {
|
|
65
|
-
const fs = await import('node:fs/promises');
|
|
66
59
|
const dir = dirname(snippet.path);
|
|
67
60
|
const ext = extname(snippet.path);
|
|
68
61
|
const currentBase = basename(snippet.path, ext);
|
|
@@ -70,7 +63,7 @@ export default class Push extends PushCommand {
|
|
|
70
63
|
const oldMetaPath = join(dir, `${currentBase}.json`);
|
|
71
64
|
let meta = {};
|
|
72
65
|
try {
|
|
73
|
-
const existing = await
|
|
66
|
+
const existing = await readFile(oldMetaPath, 'utf8');
|
|
74
67
|
meta = JSON.parse(existing);
|
|
75
68
|
}
|
|
76
69
|
catch (error) {
|
|
@@ -79,84 +72,103 @@ export default class Push extends PushCommand {
|
|
|
79
72
|
}
|
|
80
73
|
meta.id = id;
|
|
81
74
|
meta.name = name;
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
meta.type = snippet.type;
|
|
76
|
+
// Persist the id against the *current* file pairing before renaming anything, so a
|
|
77
|
+
// crash between the rename and the sidecar write still leaves a valid `<name>.<ext>` /
|
|
78
|
+
// `<name>.json` pair with the id on disk, and a retried `snippet push` won't re-create
|
|
79
|
+
// this snippet as a duplicate because it looks unlinked.
|
|
80
|
+
await writeFile(oldMetaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
81
|
+
if (currentBase === canonicalBase)
|
|
84
82
|
return;
|
|
85
|
-
}
|
|
86
83
|
const newPath = join(dir, `${canonicalBase}${ext}`);
|
|
87
84
|
const newMetaPath = join(dir, `${canonicalBase}.json`);
|
|
88
|
-
await
|
|
89
|
-
await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.log(`📁 Renamed: ${snippet.path} → ${newPath}`);
|
|
85
|
+
await rename(snippet.path, newPath);
|
|
86
|
+
await writeFile(newMetaPath, JSON.stringify(meta, null, 2) + '\n');
|
|
87
|
+
await rm(oldMetaPath, { force: true });
|
|
88
|
+
this.log(` Renamed: ${snippet.path} → ${newPath}`);
|
|
93
89
|
}
|
|
94
90
|
async loadSnippets(path) {
|
|
95
|
-
const fs = await import('node:fs/promises');
|
|
96
91
|
const snippets = [];
|
|
97
|
-
const SNIPPET_EXTENSIONS = new Set(Object.keys(TYPE_BY_EXTENSION));
|
|
98
92
|
try {
|
|
99
|
-
const files = await
|
|
93
|
+
const files = await readdir(path);
|
|
100
94
|
for (const file of files) {
|
|
101
|
-
const ext =
|
|
102
|
-
if (!
|
|
95
|
+
const ext = extname(file);
|
|
96
|
+
if (!(ext in TYPE_BY_EXTENSION))
|
|
103
97
|
continue;
|
|
104
|
-
const filePath =
|
|
105
|
-
const metaPath =
|
|
106
|
-
const content = await
|
|
98
|
+
const filePath = join(path, file);
|
|
99
|
+
const metaPath = join(path, `${basename(file, ext)}.json`);
|
|
100
|
+
const content = await readFile(filePath, 'utf8');
|
|
107
101
|
let id;
|
|
108
102
|
let name;
|
|
109
103
|
let type;
|
|
104
|
+
let active = false;
|
|
105
|
+
let tags = [];
|
|
106
|
+
let location = null;
|
|
107
|
+
let insertMethod = null;
|
|
108
|
+
let priority = 10;
|
|
109
|
+
let shortcodeAttributes = [];
|
|
110
110
|
try {
|
|
111
|
-
const metaContent = await
|
|
111
|
+
const metaContent = await readFile(metaPath, 'utf8');
|
|
112
112
|
const meta = JSON.parse(metaContent);
|
|
113
113
|
id = meta.id ? Number(meta.id) : undefined;
|
|
114
114
|
name = meta.name ? String(meta.name) : undefined;
|
|
115
115
|
type = parseType(meta.type) ?? undefined;
|
|
116
|
+
active = Boolean(meta.active);
|
|
117
|
+
tags = Array.isArray(meta.tags) ? meta.tags.map(String) : [];
|
|
118
|
+
location = parseLocation(meta.location);
|
|
119
|
+
insertMethod = parseInsertMethod(meta.insertMethod);
|
|
120
|
+
priority = meta.priority === undefined ? 10 : Number(meta.priority);
|
|
121
|
+
shortcodeAttributes = Array.isArray(meta.shortcodeAttributes) ? meta.shortcodeAttributes.map(String) : [];
|
|
116
122
|
}
|
|
117
123
|
catch (error) {
|
|
118
124
|
if (error.code !== 'ENOENT')
|
|
119
125
|
throw error;
|
|
120
126
|
}
|
|
127
|
+
const resolvedType = type ?? (ext in TYPE_BY_EXTENSION ? TYPE_BY_EXTENSION[ext] : 'php');
|
|
121
128
|
snippets.push({
|
|
129
|
+
active,
|
|
122
130
|
code: content,
|
|
123
131
|
id,
|
|
124
|
-
|
|
132
|
+
insertMethod: insertMethod ?? 'auto',
|
|
133
|
+
location: location ?? defaultLocationForType(resolvedType),
|
|
134
|
+
name: name ?? basename(file, ext),
|
|
125
135
|
path: filePath,
|
|
126
|
-
|
|
136
|
+
priority,
|
|
137
|
+
shortcodeAttributes,
|
|
138
|
+
tags,
|
|
139
|
+
type: resolvedType,
|
|
127
140
|
});
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
catch (error) {
|
|
131
|
-
this.error(
|
|
144
|
+
this.error(`Error loading snippets: ${error.message}`);
|
|
132
145
|
}
|
|
133
146
|
return snippets;
|
|
134
147
|
}
|
|
135
|
-
async pushSnippet(snippet,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.log(`📄 Code preview: ${snippet.code.slice(0, 100)}...`);
|
|
140
|
-
return;
|
|
148
|
+
async pushSnippet(snippet, adapter) {
|
|
149
|
+
if (this.dryRun) {
|
|
150
|
+
this.log(`[dry-run] Would push: ${snippet.name}`);
|
|
151
|
+
return true;
|
|
141
152
|
}
|
|
153
|
+
const endpointPath = adapter.endpointPath();
|
|
142
154
|
try {
|
|
143
|
-
const
|
|
144
|
-
const payload = adapter.toPayload(snippet.name, snippet.code, snippet.path, snippet.type);
|
|
155
|
+
const payload = adapter.toPayload(snippet);
|
|
145
156
|
if (snippet.id) {
|
|
146
|
-
this.
|
|
147
|
-
|
|
148
|
-
this.log(`✅ Updated: ${snippet.name}`);
|
|
157
|
+
await this.wp.put(`${endpointPath}/${snippet.id}`, payload);
|
|
158
|
+
this.log(` Updated: ${snippet.name} (id: ${snippet.id})`);
|
|
149
159
|
await this.ensureCanonicalFilename(snippet, snippet.id, snippet.name);
|
|
150
|
-
return;
|
|
151
160
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
161
|
+
else {
|
|
162
|
+
const response = await this.wp.post(endpointPath, payload);
|
|
163
|
+
const created = adapter.fromRemote(response);
|
|
164
|
+
this.log(` Created: ${snippet.name} (id: ${created.id})`);
|
|
165
|
+
await this.ensureCanonicalFilename(snippet, created.id, created.name);
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
157
168
|
}
|
|
158
169
|
catch (error) {
|
|
159
|
-
this.
|
|
170
|
+
this.warn(` Failed to push ${snippet.name}: ${error.message}`);
|
|
171
|
+
return false;
|
|
160
172
|
}
|
|
161
173
|
}
|
|
162
174
|
}
|
|
@@ -5,9 +5,7 @@ export interface ConsoleAuth {
|
|
|
5
5
|
}
|
|
6
6
|
export declare class AuthManager {
|
|
7
7
|
private readonly homeDir;
|
|
8
|
-
private static instance;
|
|
9
8
|
constructor(homeDir?: string);
|
|
10
|
-
static getInstance(): AuthManager;
|
|
11
9
|
clearAuth(): void;
|
|
12
10
|
getAuth(): ConsoleAuth | null;
|
|
13
11
|
getAuthFilePath(): string;
|
|
@@ -1,45 +1,25 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, unlinkSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { readJsonFile, writeJsonFileAtomic } from './json-file.js';
|
|
4
5
|
export class AuthManager {
|
|
5
6
|
homeDir;
|
|
6
|
-
static instance;
|
|
7
7
|
constructor(homeDir = homedir()) {
|
|
8
8
|
this.homeDir = homeDir;
|
|
9
9
|
}
|
|
10
|
-
static getInstance() {
|
|
11
|
-
if (!AuthManager.instance) {
|
|
12
|
-
AuthManager.instance = new AuthManager();
|
|
13
|
-
}
|
|
14
|
-
return AuthManager.instance;
|
|
15
|
-
}
|
|
16
10
|
clearAuth() {
|
|
17
11
|
const filePath = this.getAuthFilePath();
|
|
18
12
|
if (existsSync(filePath))
|
|
19
13
|
unlinkSync(filePath);
|
|
20
14
|
}
|
|
21
15
|
getAuth() {
|
|
22
|
-
|
|
23
|
-
if (!existsSync(filePath))
|
|
24
|
-
return null;
|
|
25
|
-
try {
|
|
26
|
-
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
16
|
+
return readJsonFile(this.getAuthFilePath());
|
|
31
17
|
}
|
|
32
18
|
getAuthFilePath() {
|
|
33
19
|
return join(this.homeDir, '.loopress', 'auth.json');
|
|
34
20
|
}
|
|
35
21
|
setAuth(auth) {
|
|
36
|
-
|
|
37
|
-
if (!existsSync(dir))
|
|
38
|
-
mkdirSync(dir, { recursive: true });
|
|
39
|
-
const filePath = this.getAuthFilePath();
|
|
40
|
-
const tmpPath = `${filePath}.tmp`;
|
|
41
|
-
writeFileSync(tmpPath, JSON.stringify(auth, null, 2));
|
|
42
|
-
renameSync(tmpPath, filePath);
|
|
22
|
+
writeJsonFileAtomic(this.getAuthFilePath(), auth);
|
|
43
23
|
}
|
|
44
24
|
}
|
|
45
|
-
export const authManager = AuthManager
|
|
25
|
+
export const authManager = new AuthManager();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
4
|
+
// Missing file or invalid JSON are treated as "no data" (returns null). Any other
|
|
5
|
+
// read failure (permissions, EISDIR, ...) propagates instead of being swallowed.
|
|
6
|
+
export function readJsonFile(filePath) {
|
|
7
|
+
if (!existsSync(filePath))
|
|
8
|
+
return null;
|
|
9
|
+
const content = readFileSync(filePath, 'utf8');
|
|
10
|
+
try {
|
|
11
|
+
return JSON.parse(content);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// Mode 0o600 (owner read/write only) since these files hold auth tokens.
|
|
18
|
+
export function writeJsonFileAtomic(filePath, data) {
|
|
19
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
20
|
+
writeFileAtomic.sync(filePath, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
21
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { EnvironmentConfig, LoopressConfig, ProjectConfig } from '
|
|
1
|
+
import { EnvironmentConfig, LoopressConfig, ProjectConfig } from '../types/config.js';
|
|
2
2
|
export declare class ProjectConfigManager {
|
|
3
3
|
private readonly homeDir;
|
|
4
|
-
private static instance;
|
|
5
4
|
constructor(homeDir?: string);
|
|
6
|
-
static getInstance(): ProjectConfigManager;
|
|
7
5
|
createProjectId(): string;
|
|
8
6
|
ensureConfigDir(): void;
|
|
9
7
|
getConfigFilePath(): string;
|
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { existsSync, mkdirSync
|
|
2
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { readJsonFile, writeJsonFileAtomic } from './json-file.js';
|
|
5
6
|
export class ProjectConfigManager {
|
|
6
7
|
homeDir;
|
|
7
|
-
static instance;
|
|
8
8
|
constructor(homeDir = homedir()) {
|
|
9
9
|
this.homeDir = homeDir;
|
|
10
10
|
}
|
|
11
|
-
static getInstance() {
|
|
12
|
-
if (!ProjectConfigManager.instance) {
|
|
13
|
-
ProjectConfigManager.instance = new ProjectConfigManager();
|
|
14
|
-
}
|
|
15
|
-
return ProjectConfigManager.instance;
|
|
16
|
-
}
|
|
17
11
|
createProjectId() {
|
|
18
12
|
return randomUUID();
|
|
19
13
|
}
|
|
@@ -73,17 +67,11 @@ export class ProjectConfigManager {
|
|
|
73
67
|
}));
|
|
74
68
|
}
|
|
75
69
|
readConfig() {
|
|
76
|
-
const
|
|
77
|
-
if (
|
|
78
|
-
return { currentProject: null, projects: {} };
|
|
79
|
-
}
|
|
80
|
-
try {
|
|
81
|
-
const parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
82
|
-
return this.sanitizeConfig(parsed);
|
|
83
|
-
}
|
|
84
|
-
catch {
|
|
70
|
+
const parsed = readJsonFile(this.getConfigFilePath());
|
|
71
|
+
if (parsed === null) {
|
|
85
72
|
return { currentProject: null, projects: {} };
|
|
86
73
|
}
|
|
74
|
+
return this.sanitizeConfig(parsed);
|
|
87
75
|
}
|
|
88
76
|
removeEnvironment(projectId, envName) {
|
|
89
77
|
const config = this.readConfig();
|
|
@@ -136,11 +124,7 @@ export class ProjectConfigManager {
|
|
|
136
124
|
this.writeConfig(config);
|
|
137
125
|
}
|
|
138
126
|
writeConfig(config) {
|
|
139
|
-
this.
|
|
140
|
-
const filePath = this.getConfigFilePath();
|
|
141
|
-
const tmpPath = `${filePath}.tmp`;
|
|
142
|
-
writeFileSync(tmpPath, JSON.stringify(config, null, 2));
|
|
143
|
-
renameSync(tmpPath, filePath);
|
|
127
|
+
writeJsonFileAtomic(this.getConfigFilePath(), config);
|
|
144
128
|
}
|
|
145
129
|
isProjectConfig(value) {
|
|
146
130
|
if (typeof value !== 'object' || value === null)
|
|
@@ -174,4 +158,4 @@ export class ProjectConfigManager {
|
|
|
174
158
|
return projects;
|
|
175
159
|
}
|
|
176
160
|
}
|
|
177
|
-
export const configManager = ProjectConfigManager
|
|
161
|
+
export const configManager = new ProjectConfigManager();
|
package/dist/lib/base.d.ts
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import { EnvironmentConfig } from '../config
|
|
2
|
+
import { EnvironmentConfig } from '../types/config.js';
|
|
3
|
+
import { LoopressLocalConfig } from '../utils/loopress-config.js';
|
|
4
|
+
import { WpClient } from './wp-client.js';
|
|
3
5
|
export declare abstract class LoopressCommand extends Command {
|
|
4
6
|
static baseFlags: {
|
|
5
7
|
password: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
8
|
url: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
9
|
user: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
10
|
};
|
|
11
|
+
static dryRunFlag: {
|
|
12
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
protected dryRun: boolean;
|
|
15
|
+
protected localConfig: LoopressLocalConfig;
|
|
9
16
|
protected siteConfig: EnvironmentConfig;
|
|
10
|
-
|
|
17
|
+
private wpClient?;
|
|
18
|
+
protected get rootDir(): string;
|
|
19
|
+
protected get wp(): WpClient;
|
|
11
20
|
init(): Promise<void>;
|
|
12
|
-
protected resolveSnippetPlugin(flag?: string):
|
|
13
|
-
protected resolveSnippetsPath(override?: string):
|
|
21
|
+
protected resolveSnippetPlugin(flag?: string): 'code-snippets' | 'wpcode';
|
|
22
|
+
protected resolveSnippetsPath(override?: string): string;
|
|
23
|
+
private resolveEnvironment;
|
|
14
24
|
}
|