plugship 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plugship",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Deploy local WordPress plugins to remote sites from the command line",
5
5
  "type": "module",
6
6
  "bin": {
@@ -34,10 +34,10 @@
34
34
  ],
35
35
  "license": "MIT",
36
36
  "dependencies": {
37
+ "@inquirer/prompts": "^7.2.1",
37
38
  "archiver": "^7.0.1",
38
39
  "chalk": "^5.3.0",
39
40
  "commander": "^12.1.0",
40
- "@inquirer/prompts": "^7.2.1",
41
41
  "form-data": "^4.0.1",
42
42
  "ora": "^8.1.1"
43
43
  }
@@ -4,7 +4,7 @@ import { select, confirm } from '@inquirer/prompts';
4
4
  import chalk from 'chalk';
5
5
  import { getSite, listSites } from './config.js';
6
6
  import { detectPlugin } from './plugin-detector.js';
7
- import { createPluginZip } from './zipper.js';
7
+ import { createPluginZip, getIgnoreSource } from './zipper.js';
8
8
  import { WordPressApi } from './wordpress-api.js';
9
9
  import { DeployError, ConfigError } from './errors.js';
10
10
  import { RECEIVER_DOWNLOAD_URL } from './constants.js';
@@ -48,18 +48,19 @@ async function getAllSites() {
48
48
  }
49
49
 
50
50
  async function checkIgnoreFile(cwd) {
51
- try {
52
- await access(join(cwd, '.plugshipignore'));
53
- } catch {
54
- logger.warn('No .plugshipignore file found.');
55
- const create = await confirm({
56
- message: 'Create one with default template?',
57
- default: true,
58
- });
59
- if (create) {
60
- const { ignoreCommand } = await import('../commands/ignore.js');
61
- await ignoreCommand([]);
62
- }
51
+ const source = await getIgnoreSource(cwd);
52
+ if (source) {
53
+ logger.info(`Using ${source} for file exclusions`);
54
+ return;
55
+ }
56
+ logger.warn('No .plugshipignore or .distignore file found.');
57
+ const create = await confirm({
58
+ message: 'Create .plugshipignore with default template?',
59
+ default: true,
60
+ });
61
+ if (create) {
62
+ const { ignoreCommand } = await import('../commands/ignore.js');
63
+ await ignoreCommand([]);
63
64
  }
64
65
  }
65
66
 
@@ -90,23 +91,46 @@ export async function deploy({ siteName, activate = true, dryRun = false, all =
90
91
  try {
91
92
  const zipStat = await stat(existingZipPath);
92
93
  const sizeMB = (zipStat.size / 1024 / 1024).toFixed(2);
93
- logger.info(`Existing ZIP found: builds/${plugin.slug}.zip (${sizeMB} MB)`);
94
- const action = await select({
95
- message: 'What do you want to do?',
96
- choices: [
97
- { name: 'Use existing ZIP', value: 'existing' },
98
- { name: 'Build a new ZIP', value: 'rebuild' },
99
- ],
100
- });
101
- if (action === 'rebuild') {
94
+
95
+ // Check if ignore file was modified after the ZIP was built
96
+ let ignoreStale = false;
97
+ for (const ignoreFile of ['.plugshipignore', '.distignore']) {
98
+ try {
99
+ const ignoreStat = await stat(join(cwd, ignoreFile));
100
+ if (ignoreStat.mtimeMs > zipStat.mtimeMs) {
101
+ ignoreStale = true;
102
+ }
103
+ break;
104
+ } catch {
105
+ // file not found, try next
106
+ }
107
+ }
108
+
109
+ if (ignoreStale) {
110
+ logger.warn('Ignore file changed since last build — rebuilding ZIP...');
102
111
  const spin = logger.spinner('Creating ZIP archive...');
103
112
  spin.start();
104
113
  ({ zipPath, size } = await createPluginZip(cwd, plugin.slug));
105
114
  spin.succeed(`ZIP created (${(size / 1024 / 1024).toFixed(2)} MB)`);
106
115
  } else {
107
- zipPath = existingZipPath;
108
- size = zipStat.size;
109
- logger.success(`Using existing ZIP (${sizeMB} MB)`);
116
+ logger.info(`Existing ZIP found: builds/${plugin.slug}.zip (${sizeMB} MB)`);
117
+ const action = await select({
118
+ message: 'What do you want to do?',
119
+ choices: [
120
+ { name: 'Use existing ZIP', value: 'existing' },
121
+ { name: 'Build a new ZIP', value: 'rebuild' },
122
+ ],
123
+ });
124
+ if (action === 'rebuild') {
125
+ const spin = logger.spinner('Creating ZIP archive...');
126
+ spin.start();
127
+ ({ zipPath, size } = await createPluginZip(cwd, plugin.slug));
128
+ spin.succeed(`ZIP created (${(size / 1024 / 1024).toFixed(2)} MB)`);
129
+ } else {
130
+ zipPath = existingZipPath;
131
+ size = zipStat.size;
132
+ logger.success(`Using existing ZIP (${sizeMB} MB)`);
133
+ }
110
134
  }
111
135
  } catch {
112
136
  const spin = logger.spinner('Creating ZIP archive...');
package/src/lib/zipper.js CHANGED
@@ -4,19 +4,46 @@ import { stat, mkdir, readFile } from 'node:fs/promises';
4
4
  import archiver from 'archiver';
5
5
  import { DEFAULT_EXCLUDES } from './constants.js';
6
6
 
7
+ async function loadIgnoreFile(filePath) {
8
+ const content = await readFile(filePath, 'utf-8');
9
+ const patterns = [];
10
+ for (const line of content.split('\n')) {
11
+ const trimmed = line.trim();
12
+ if (trimmed && !trimmed.startsWith('#')) {
13
+ patterns.push(trimmed);
14
+ }
15
+ }
16
+ return patterns;
17
+ }
18
+
19
+ export async function getIgnoreSource(sourceDir) {
20
+ try {
21
+ await readFile(join(sourceDir, '.plugshipignore'), 'utf-8');
22
+ return '.plugshipignore';
23
+ } catch {
24
+ try {
25
+ await readFile(join(sourceDir, '.distignore'), 'utf-8');
26
+ return '.distignore';
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ }
32
+
7
33
  async function loadIgnorePatterns(sourceDir) {
8
34
  const patterns = [...DEFAULT_EXCLUDES];
9
- try {
10
- const content = await readFile(join(sourceDir, '.plugshipignore'), 'utf-8');
11
- for (const line of content.split('\n')) {
12
- const trimmed = line.trim();
13
- if (trimmed && !trimmed.startsWith('#')) {
14
- patterns.push(trimmed);
15
- }
35
+
36
+ // .plugshipignore takes priority, then .distignore
37
+ for (const file of ['.plugshipignore', '.distignore']) {
38
+ try {
39
+ const extra = await loadIgnoreFile(join(sourceDir, file));
40
+ patterns.push(...extra);
41
+ return patterns;
42
+ } catch {
43
+ // file not found, try next
16
44
  }
17
- } catch {
18
- // No .plugshipignore file — use defaults only
19
45
  }
46
+
20
47
  return patterns;
21
48
  }
22
49
 
@@ -52,11 +79,16 @@ export async function createPluginZip(sourceDir, slug) {
52
79
  }
53
80
 
54
81
  function matchGlob(filePath, pattern) {
55
- // Strip trailing /** for directory matching
56
- if (pattern.endsWith('/**')) {
57
- const dir = pattern.slice(0, -3);
82
+ // Strip trailing /**, /* or / for directory matching
83
+ if (pattern.endsWith('/**') || pattern.endsWith('/*') || pattern.endsWith('/')) {
84
+ const dir = pattern.replace(/\/\*{0,2}$/, '');
58
85
  return filePath === dir || filePath.startsWith(dir + '/');
59
86
  }
87
+ // **/*.ext — match extension at any depth
88
+ if (pattern.startsWith('**/')) {
89
+ const sub = pattern.slice(3);
90
+ return matchGlob(filePath, sub) || filePath.split('/').some((seg) => matchGlob(seg, sub));
91
+ }
60
92
  // Exact match or wildcard prefix
61
93
  if (pattern.startsWith('*.')) {
62
94
  return filePath.endsWith(pattern.slice(1));
@@ -66,5 +98,10 @@ function matchGlob(filePath, pattern) {
66
98
  const firstSegment = filePath.split('/')[0];
67
99
  return firstSegment.startsWith('.');
68
100
  }
69
- return filePath === pattern;
101
+ // Exact match (file or directory name)
102
+ // "resources" matches "resources", "resources/file.js", "resources/sub/file.js"
103
+ if (filePath === pattern || filePath.startsWith(pattern + '/')) {
104
+ return true;
105
+ }
106
+ return false;
70
107
  }