com.elestrago.unity.package-tools 2.0.11 → 2.1.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.
Files changed (27) hide show
  1. package/CAHNGELOG.md +8 -0
  2. package/Documentation~/api.md +502 -0
  3. package/Documentation~/manual.md +140 -0
  4. package/Documentation~/samples.md +73 -0
  5. package/Editor/Drawers/CopyEntryPropertyDrawer.cs +95 -0
  6. package/Editor/Drawers/CopyEntryPropertyDrawer.cs.meta +2 -0
  7. package/Editor/EditorConstants.cs +6 -0
  8. package/Editor/Inspectors/PackageManifestConfigInspector.cs +31 -0
  9. package/Editor/PackageManifestConfig.cs +20 -0
  10. package/Editor/Tools/FileTools.cs +73 -0
  11. package/Samples~/ClaudeSkills/unity-package-docs/SKILL.md +309 -0
  12. package/Samples~/ClaudeSkills/unity-package-docs/assets/README.md.template +42 -0
  13. package/Samples~/ClaudeSkills/unity-package-docs/assets/api-chunk.md.template +41 -0
  14. package/Samples~/ClaudeSkills/unity-package-docs/assets/api-index.md.template +26 -0
  15. package/Samples~/ClaudeSkills/unity-package-docs/assets/api.md.template +43 -0
  16. package/Samples~/ClaudeSkills/unity-package-docs/assets/manual.md.template +57 -0
  17. package/Samples~/ClaudeSkills/unity-package-docs/assets/samples.md.template +56 -0
  18. package/Samples~/ClaudeSkills/unity-package-docs/scripts/scan_package.py +504 -0
  19. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/SKILL.md +309 -0
  20. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/README.md.template +42 -0
  21. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/api-chunk.md.template +41 -0
  22. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/api-index.md.template +26 -0
  23. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/api.md.template +43 -0
  24. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/manual.md.template +57 -0
  25. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/assets/samples.md.template +56 -0
  26. package/Samples~/ClaudeSkills/unity-package-docs/unity-package-docs/scripts/scan_package.py +504 -0
  27. package/package.json +9 -4
@@ -0,0 +1,73 @@
1
+ <!-- generated by unity-package-docs; safe to regenerate -->
2
+
3
+ # Package Tool — Samples
4
+
5
+ How the sample content in this repository uses the package. Each entry maps a sample file to the package types it exercises, so a reader can recreate the same setup against the public API.
6
+
7
+ ## Sample Layout
8
+
9
+ ```
10
+ Assets/Example/Sample
11
+ ├── Prefabs
12
+ │ └── ExamplePrefab.prefab
13
+ └── Scene
14
+ └── SampleScene.unity
15
+ ```
16
+
17
+ On export, `CopySamplesToDirectory` copies this tree to `Release/Samples~/ExampleSample/`, skipping `.meta` files. The shipped sample is consumable from the Package Manager **Samples** tab on the published package.
18
+
19
+ ## Sample → Package Type Mapping
20
+
21
+ ### `Assets/Example/Sample/Prefabs/ExamplePrefab.prefab`
22
+
23
+ Kind: prefab
24
+
25
+ This sample asset references no scripts from the package. It exists as a structural demo (the importing user sees a working Scene/Prefab in the package's Samples tab) rather than as an API exercise.
26
+
27
+ ### `Assets/Example/Sample/Scene/SampleScene.unity`
28
+
29
+ Kind: scene
30
+
31
+ This sample asset references no scripts from the package. It exists as a structural demo (the importing user sees a working Scene/Prefab in the package's Samples tab) rather than as an API exercise.
32
+
33
+ ## Reproducing the Sample
34
+
35
+ The example sample is structural only — there are no MonoBehaviour scripts to instantiate. To reproduce from scratch:
36
+
37
+ 1. Create an empty Unity scene and a primitive prefab; both can live anywhere in `Assets/`.
38
+ 2. Create a `PackageManifestConfig` asset via **Assets > Create > JCMG/PackageTools/PackageManifestConfig** and configure `sourcePath`, `packageDestinationPath`, and a [`Sample`](api.md#packagemanifestconfigsample) entry whose `sourcePath` points at the folder holding the scene and prefab.
39
+ 3. Click **Export Package Source** on the inspector — `FileTools.CreateOrUpdatePackageSource` runs the export pipeline (see `manual.md` → **Export pipeline**) and the sample folder lands under `packageDestinationPath/Samples~/{folderName}`.
40
+
41
+ Minimal equivalent in C#:
42
+
43
+ ```csharp
44
+ using PackageTool;
45
+ using PackageTool.Tools;
46
+ using UnityEditor;
47
+ using UnityEngine;
48
+
49
+ var config = ScriptableObject.CreateInstance<PackageManifestConfig>();
50
+ config.packageName = "com.example.sample";
51
+ config.displayName = "Example Sample Package";
52
+ config.packageVersion = "1.0.0";
53
+ config.unityVersion = "2021.3";
54
+ config.sourcePath = "Assets/Example/Source";
55
+ config.packageDestinationPath = "Release";
56
+ config.samples = new[]
57
+ {
58
+ new PackageManifestConfig.Sample
59
+ {
60
+ sourcePath = "Assets/Example/Sample",
61
+ displayName = "Example Sample",
62
+ folderName = "ExampleSample",
63
+ description = "This is example for check test samples",
64
+ },
65
+ };
66
+ AssetDatabase.CreateAsset(config, "Assets/Example/PackageManifestConfig.asset");
67
+ FileTools.CreateOrUpdatePackageSource(config);
68
+ ```
69
+
70
+ ## Notes
71
+
72
+ - The `Samples~/` folder name (trailing tilde) is a Unity convention: tilde-suffixed folders are excluded from the AssetDatabase import, so packaged samples don't pollute the consuming project until the user clicks **Import** in the Package Manager.
73
+ - For samples that stage content from outside `Assets/` — e.g. a Claude skill folder, vendored data, or generated output — use a [`CopyEntry`](api.md#packagemanifestconfigcopyentry) with `destinationPath` pointing at the same `sourcePath` your `Sample` declares. The `CopyEntry` step runs first; `CopySamplesToDirectory` then ships the staged content into the package.
@@ -0,0 +1,95 @@
1
+ using PackageTool.Tools;
2
+ using UnityEditor;
3
+ using UnityEngine;
4
+
5
+ namespace PackageTool.Drawers
6
+ {
7
+ [CustomPropertyDrawer(typeof(PackageManifestConfig.CopyEntry))]
8
+ internal sealed class CopyEntryPropertyDrawer : PropertyDrawer
9
+ {
10
+ private const string SOURCE_PATH_PROPERTY_NAME = "sourcePath";
11
+ private const string DESTINATION_PATH_PROPERTY_NAME = "destinationPath";
12
+
13
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
14
+ {
15
+ var sourceRect = new Rect(position)
16
+ {
17
+ height = EditorConstants.FOLDER_PATH_PICKER_HEIGHT
18
+ };
19
+
20
+ var destinationRect = new Rect(sourceRect)
21
+ {
22
+ position = new Vector2(position.x, sourceRect.y + sourceRect.height + 2f)
23
+ };
24
+
25
+ DrawSourceRow(sourceRect, property.FindPropertyRelative(SOURCE_PATH_PROPERTY_NAME));
26
+ DrawDestinationRow(destinationRect, property.FindPropertyRelative(DESTINATION_PATH_PROPERTY_NAME));
27
+ }
28
+
29
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
30
+ => EditorConstants.FOLDER_PATH_PICKER_HEIGHT * 2f + 2f;
31
+
32
+ private static void DrawSourceRow(Rect rowRect, SerializedProperty pathProperty)
33
+ {
34
+ var fieldRect = new Rect(rowRect)
35
+ {
36
+ width = rowRect.width - EditorConstants.FOLDER_PATH_PICKER_HEIGHT * 2f
37
+ - EditorConstants.FOLDER_PATH_PICKER_BUFFER
38
+ };
39
+
40
+ EditorGUI.PropertyField(fieldRect, pathProperty, new GUIContent(EditorConstants.COPY_ENTRY_SOURCE_LABEL));
41
+
42
+ var filePickerRect = new Rect
43
+ {
44
+ position = new Vector2(
45
+ fieldRect.x + fieldRect.width + EditorConstants.FOLDER_PATH_PICKER_BUFFER,
46
+ rowRect.y),
47
+ width = EditorConstants.FOLDER_PATH_PICKER_HEIGHT,
48
+ height = EditorConstants.FOLDER_PATH_PICKER_HEIGHT
49
+ };
50
+
51
+ var folderPickerRect = new Rect
52
+ {
53
+ position = new Vector2(
54
+ filePickerRect.x + filePickerRect.width,
55
+ rowRect.y),
56
+ width = EditorConstants.FOLDER_PATH_PICKER_HEIGHT,
57
+ height = EditorConstants.FOLDER_PATH_PICKER_HEIGHT
58
+ };
59
+
60
+ GUILayoutTools.DrawFilePicker(
61
+ filePickerRect,
62
+ pathProperty,
63
+ EditorConstants.SELECT_SOURCE_PATH_FILE_PICKER_TITLE);
64
+ GUILayoutTools.DrawFolderPicker(
65
+ folderPickerRect,
66
+ pathProperty,
67
+ EditorConstants.SELECT_SOURCE_PATH_PICKER_FOLDER_TITLE);
68
+ }
69
+
70
+ private static void DrawDestinationRow(Rect rowRect, SerializedProperty pathProperty)
71
+ {
72
+ var fieldRect = new Rect(rowRect)
73
+ {
74
+ width = rowRect.width - EditorConstants.FOLDER_PATH_PICKER_HEIGHT
75
+ - EditorConstants.FOLDER_PATH_PICKER_BUFFER
76
+ };
77
+
78
+ EditorGUI.PropertyField(fieldRect, pathProperty, new GUIContent(EditorConstants.COPY_ENTRY_DESTINATION_LABEL));
79
+
80
+ var folderPickerRect = new Rect
81
+ {
82
+ position = new Vector2(
83
+ fieldRect.x + fieldRect.width + EditorConstants.FOLDER_PATH_PICKER_BUFFER,
84
+ rowRect.y),
85
+ width = EditorConstants.FOLDER_PATH_PICKER_HEIGHT,
86
+ height = EditorConstants.FOLDER_PATH_PICKER_HEIGHT
87
+ };
88
+
89
+ GUILayoutTools.DrawFolderPicker(
90
+ folderPickerRect,
91
+ pathProperty,
92
+ EditorConstants.SELECT_COPY_DESTINATION_FOLDER_PICKER_TITLE);
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 88c4785ea4243706f9dbebc1dd7a5ae4
@@ -108,5 +108,11 @@ namespace PackageTool
108
108
  public const string SAMPLES_FOLDER_NAME = "Samples~";
109
109
  public const string SAMPLES_HEADER_LABEL = "Samples";
110
110
  public const string SAMPLES_ELEMENT_LABEL_FORMAT = "Sample {0}:";
111
+
112
+ public const string COPY_ENTRIES_HEADER_LABEL = "Copy Entries";
113
+ public const string COPY_ENTRY_ELEMENT_LABEL_FORMAT = "Copy {0}:";
114
+ public const string COPY_ENTRY_SOURCE_LABEL = "Source";
115
+ public const string COPY_ENTRY_DESTINATION_LABEL = "Destination";
116
+ public const string SELECT_COPY_DESTINATION_FOLDER_PICKER_TITLE = "Select Copy Destination Folder";
111
117
  }
112
118
  }
@@ -37,6 +37,7 @@ namespace PackageTool.Inspectors
37
37
  private ReorderableList _keywordReorderableList;
38
38
  private ReorderableList _dependenciesReorderableList;
39
39
  private ReorderableList _samplesReorderableList;
40
+ private ReorderableList _copyEntriesReorderableList;
40
41
 
41
42
  private const string SOURCE_PATH_PROPERTY_NAME = "sourcePath";
42
43
  private const string DOCUMENTATION_PATH_PROPERTY_NAME = "documentationPath";
@@ -61,6 +62,7 @@ namespace PackageTool.Inspectors
61
62
  private const string VERSION_CONSTANTS_NAMESPACE_PROPERTY_NAME = "versionConstantsNamespace";
62
63
  private const string ID_PROPERTY_NAME = "_id";
63
64
  private const string SAMPLES_PROPERTY_NAME = "samples";
65
+ private const string COPY_ENTRIES_PROPERTY_NAME = "copyEntries";
64
66
 
65
67
  private void OnEnable()
66
68
  {
@@ -109,6 +111,16 @@ namespace PackageTool.Inspectors
109
111
  drawElementCallback = DrawSampleElement,
110
112
  elementHeight = EditorGUIUtility.singleLineHeight * 5f
111
113
  };
114
+
115
+ var copyEntriesProp = serializedObject.FindProperty(COPY_ENTRIES_PROPERTY_NAME);
116
+ _copyEntriesReorderableList = new ReorderableList(
117
+ serializedObject,
118
+ copyEntriesProp)
119
+ {
120
+ drawHeaderCallback = DrawCopyEntriesHeader,
121
+ drawElementCallback = DrawCopyEntryElement,
122
+ elementHeight = EditorConstants.FOLDER_PATH_PICKER_HEIGHT * 2f + 2f
123
+ };
112
124
  }
113
125
 
114
126
  public override void OnInspectorGUI()
@@ -203,6 +215,7 @@ namespace PackageTool.Inspectors
203
215
  EditorGUILayout.Space();
204
216
  // _sourcePathsReorderableList.DoLayoutList();
205
217
  _excludePathsReorderableList.DoLayoutList();
218
+ _copyEntriesReorderableList.DoLayoutList();
206
219
 
207
220
  // Package Source Export
208
221
  using (new EditorGUILayout.HorizontalScope())
@@ -400,5 +413,23 @@ namespace PackageTool.Inspectors
400
413
  }
401
414
 
402
415
  #endregion
416
+
417
+ #region Copy Entries ReorderableList
418
+
419
+ private void DrawCopyEntriesHeader(Rect rect)
420
+ {
421
+ EditorGUI.LabelField(rect, EditorConstants.COPY_ENTRIES_HEADER_LABEL, EditorStyles.boldLabel);
422
+ }
423
+
424
+ private void DrawCopyEntryElement(Rect rect, int index, bool isActive, bool isFocused)
425
+ {
426
+ EditorGUI.PropertyField(
427
+ rect,
428
+ serializedObject.FindProperty(COPY_ENTRIES_PROPERTY_NAME).GetArrayElementAtIndex(index),
429
+ new GUIContent(string.Format(EditorConstants.COPY_ENTRY_ELEMENT_LABEL_FORMAT, index))
430
+ );
431
+ }
432
+
433
+ #endregion
403
434
  }
404
435
  }
@@ -82,6 +82,21 @@ namespace PackageTool
82
82
  || string.IsNullOrEmpty(folderName);
83
83
  }
84
84
 
85
+ /// <summary>
86
+ /// Describes a copy operation that mirrors a source file or folder into a destination folder inside the
87
+ /// project. When the source is a folder, the same-named subfolder of the destination is replaced with the
88
+ /// source folder's content. When the source is a file, the same-named file inside the destination folder
89
+ /// is replaced. An empty or non-existent destination path falls back to the project root.
90
+ /// </summary>
91
+ [Serializable]
92
+ public sealed class CopyEntry
93
+ {
94
+ public string sourcePath;
95
+ public string destinationPath;
96
+
97
+ public bool IsEmpty() => string.IsNullOrEmpty(sourcePath);
98
+ }
99
+
85
100
  /// <summary>
86
101
  /// A unique id for this <see cref="PackageManifestConfig"/> instance.
87
102
  /// </summary>
@@ -162,6 +177,11 @@ namespace PackageTool
162
177
 
163
178
  public Sample[] samples;
164
179
 
180
+ /// <summary>
181
+ /// A collection of additional source-to-destination copy operations performed during export.
182
+ /// </summary>
183
+ public CopyEntry[] copyEntries;
184
+
165
185
  /// <summary>
166
186
  /// A path to the where the VersionConstants.cs file should be created/updated
167
187
  /// </summary>
@@ -122,6 +122,7 @@ namespace PackageTool.Tools
122
122
  // If its a file, copy over it and its meta file if it exists.
123
123
  var normalizedSourcePath = Path.GetFullPath(packageManifest.sourcePath);
124
124
 
125
+ CopyEntriesToProject(packageManifest);
125
126
  CopyDocumentationToDirectory(packageManifest);
126
127
  RecursivelyCopyDirectoriesAndFiles(packageManifest, normalizedSourcePath, normalizedDestinationPath);
127
128
  CopySamplesToDirectory(packageManifest);
@@ -192,6 +193,78 @@ namespace PackageTool.Tools
192
193
  File.Copy(normalizedSourcePath, destinationPath);
193
194
  }
194
195
 
196
+ /// <summary>
197
+ /// Stages each configured <see cref="PackageManifestConfig.CopyEntry"/> into the project before the
198
+ /// rest of the export runs. A folder source's content is merged directly into
199
+ /// <see cref="PackageManifestConfig.CopyEntry.destinationPath"/> (same-named files are overwritten,
200
+ /// unrelated files in the destination are left alone). A file source overwrites
201
+ /// <c>destination/{filename}</c>. A blank destination resolves to the project root; a non-blank
202
+ /// destination that does not yet exist is created. Missing sources log a warning and are skipped.
203
+ /// </summary>
204
+ public static void CopyEntriesToProject(PackageManifestConfig packageManifest)
205
+ {
206
+ var entries = packageManifest.copyEntries;
207
+ if (entries == null || entries.Length == 0)
208
+ return;
209
+
210
+ foreach (var entry in entries)
211
+ {
212
+ if (entry.IsEmpty())
213
+ continue;
214
+
215
+ var normalizedSourcePath = Path.GetFullPath(entry.sourcePath);
216
+ var destinationDirectory = ResolveDestinationDirectory(entry.destinationPath);
217
+
218
+ if (!Directory.Exists(destinationDirectory))
219
+ Directory.CreateDirectory(destinationDirectory);
220
+
221
+ if (!Directory.Exists(normalizedSourcePath) && !File.Exists(normalizedSourcePath))
222
+ {
223
+ Debug.LogWarningFormat(
224
+ "[Package Tools] Copy entry source [{0}] does not exist, skipping.",
225
+ entry.sourcePath);
226
+ continue;
227
+ }
228
+
229
+ if (Directory.Exists(normalizedSourcePath))
230
+ {
231
+ CopyDirectoryRecursively(normalizedSourcePath, destinationDirectory);
232
+ }
233
+ else if (File.Exists(normalizedSourcePath))
234
+ {
235
+ var sourceName = Path.GetFileName(normalizedSourcePath);
236
+ var targetPath = Path.Combine(destinationDirectory, sourceName);
237
+
238
+ if (File.Exists(targetPath))
239
+ File.Delete(targetPath);
240
+
241
+ File.Copy(normalizedSourcePath, targetPath);
242
+ }
243
+ }
244
+ }
245
+
246
+ private static string ResolveDestinationDirectory(string rawDestinationPath)
247
+ {
248
+ if (string.IsNullOrEmpty(rawDestinationPath))
249
+ return EditorConstants.ProjectPath;
250
+
251
+ return Path.GetFullPath(rawDestinationPath);
252
+ }
253
+
254
+ private static void CopyDirectoryRecursively(string normalizedSourcePath, string normalizedDestinationPath)
255
+ {
256
+ var directoryInfo = new DirectoryInfo(normalizedSourcePath);
257
+
258
+ foreach (var sdi in directoryInfo.GetDirectories(EditorConstants.WILDCARD_FILTER, SearchOption.AllDirectories))
259
+ Directory.CreateDirectory(sdi.FullName.Replace(normalizedSourcePath, normalizedDestinationPath));
260
+
261
+ foreach (var fi in directoryInfo.GetFiles(EditorConstants.WILDCARD_FILTER, SearchOption.AllDirectories))
262
+ {
263
+ var newPath = fi.FullName.Replace(normalizedSourcePath, normalizedDestinationPath);
264
+ File.Copy(fi.FullName, newPath, overwrite: true);
265
+ }
266
+ }
267
+
195
268
  private static void CopySamplesToDirectory(PackageManifestConfig packageManifest)
196
269
  {
197
270
  var samples = packageManifest.samples;