@orxataguy/tyr 1.0.41 → 1.0.43

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 CHANGED
@@ -184,7 +184,7 @@ Third parties can distribute commands as a `manifest.json` — a flat JSON map o
184
184
  }
185
185
  ```
186
186
 
187
- Register one with `tyr --add`, which validates the manifest, records it in `~/.tyr/imported_modules.yml`, and immediately downloads whatever commands are missing:
187
+ Register one with `tyr --add`, which validates the manifest, records it in `~/.tyr/imported_modules.yaml`, and immediately downloads whatever commands are missing:
188
188
 
189
189
  ```bash
190
190
  tyr --add https://raw.githubusercontent.com/someuser/tyr-modules/main/manifest.json my-module
@@ -200,7 +200,7 @@ Rules worth knowing:
200
200
 
201
201
  - A command name that already exists locally is never overwritten by a plain sync — only `tyr --update` (or `tyr --modules sync --force`) refreshes commands that were themselves installed by an import.
202
202
  - Hand-written commands are never touched, even if a manifest later defines a command with the same name.
203
- - If two modules define the same command name, the one processed last (last entry in `imported_modules.yml`) wins.
203
+ - If two modules define the same command name, the one processed last (last entry in `imported_modules.yaml`) wins.
204
204
  - Only `https://` URLs are accepted, both for manifests and for the command files they point to.
205
205
 
206
206
  Which commands came from which module is tracked in `~/.tyr/imported_modules.lock.yml`, generated automatically — you shouldn't need to edit it by hand.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orxataguy/tyr",
3
- "version": "1.0.41",
3
+ "version": "1.0.43",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tyr": "./bin/tyr.js"
@@ -110,7 +110,7 @@ export class Kernel {
110
110
  console.log('Run: tyr --config --repo <url> to link it.');
111
111
  }
112
112
 
113
- const modulesPath = path.join(this.userRoot, 'imported_modules.yml');
113
+ const modulesPath = path.join(this.userRoot, 'imported_modules.yaml');
114
114
  if (fs.existsSync(modulesPath)) {
115
115
  console.log('\nUpdating imported modules...');
116
116
  const updateContext = {
@@ -284,6 +284,10 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
284
284
  await tyrFs.write(mapPath, 'commands: {}\n');
285
285
  logger.success(`File created: ${mapPath}`);
286
286
 
287
+ const importedModulesPath = path.join(userRoot, 'imported_modules.yaml');
288
+ await tyrFs.write(importedModulesPath, 'modules: {}\n');
289
+ logger.success(`File created: ${importedModulesPath}`);
290
+
287
291
  const envPath = path.join(userRoot, '.env.example');
288
292
  if (!tyrFs.exists(envPath)) {
289
293
  await tyrFs.write(envPath, ENV_TEMPLATE);
@@ -331,6 +335,12 @@ export default function config({ logger, fs: tyrFs, frameworkRoot, shell }: TyrC
331
335
  }
332
336
  }
333
337
 
338
+ const importedModulesPath = path.join(userRoot, 'imported_modules.yaml');
339
+ if (!tyrFs.exists(importedModulesPath)) {
340
+ await tyrFs.write(importedModulesPath, 'modules: {}\n');
341
+ logger.success(`File created: ${importedModulesPath}`);
342
+ }
343
+
334
344
  const packageJsonPath = path.join(userRoot, 'package.json');
335
345
  const tsconfigPath = path.join(userRoot, 'tsconfig.json');
336
346
  let needsInstall = false;
@@ -29,7 +29,7 @@ export interface SyncSummary {
29
29
  failed: { command: string; reason: string }[];
30
30
  }
31
31
 
32
- const IMPORTED_MODULES_FILE = 'imported_modules.yml';
32
+ const IMPORTED_MODULES_FILE = 'imported_modules.yaml';
33
33
  const LOCK_FILE = 'imported_modules.lock.yml';
34
34
 
35
35
  function isValidHttpsUrl(value: string): boolean {
@@ -41,8 +41,19 @@ function isValidHttpsUrl(value: string): boolean {
41
41
  }
42
42
  }
43
43
 
44
+ // Allows namespaced command names like 'db:migrate', but not a leading/
45
+ // trailing/doubled ':' (e.g. ':foo', 'foo:', 'a::b'), since those would
46
+ // produce an empty segment.
44
47
  function isValidCommandName(name: string): boolean {
45
- return /^[a-zA-Z0-9_-]+$/.test(name);
48
+ return /^[a-zA-Z0-9_-]+(:[a-zA-Z0-9_-]+)*$/.test(name);
49
+ }
50
+
51
+ // ':' is a valid character in a command name (namespacing, e.g. 'db:migrate')
52
+ // but isn't a safe filename component on every platform (Windows reserves it).
53
+ // Only used when deriving the on-disk file name — the real name with its
54
+ // colons is still what's stored in map.yml, the lockfile, and the manifest.
55
+ function toSafeFilename(commandName: string): string {
56
+ return commandName.replace(/:/g, '-');
46
57
  }
47
58
 
48
59
  function slugFromUrl(url: string): string {
@@ -101,7 +112,7 @@ async function fetchManifest(web: any, manifestUrl: string): Promise<Record<stri
101
112
  }
102
113
 
103
114
  /**
104
- * Reconciles ~/.tyr/imported_modules.yml against ~/.tyr/map.yml.
115
+ * Reconciles ~/.tyr/imported_modules.yaml against ~/.tyr/map.yml.
105
116
  *
106
117
  * - A command that doesn't exist yet in map.yml is installed.
107
118
  * - A command that already exists and isn't tracked in the lockfile is left
@@ -111,7 +122,7 @@ async function fetchManifest(web: any, manifestUrl: string): Promise<Record<stri
111
122
  * silently.
112
123
  * - If two modules define the same command name, the last one processed
113
124
  * wins (modules are processed in the order they appear in
114
- * imported_modules.yml).
125
+ * imported_modules.yaml).
115
126
  */
116
127
  export async function syncModules(context: TyrContext, options: { force?: boolean } = {}): Promise<SyncSummary> {
117
128
  const { logger, fs, web, userRoot } = context as any;
@@ -155,6 +166,9 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
155
166
  } catch (e) {
156
167
  const reason = e instanceof Error ? e.message : String(e);
157
168
  logger.error(`Could not read manifest for module '${moduleName}': ${reason}`);
169
+ // logger.error() is silenced unless --debug is passed, so mirror it via
170
+ // logger.info() too — otherwise this failure would be entirely invisible.
171
+ logger.info(`Skipped module '${moduleName}': ${reason}`);
158
172
  summary.failed.push({ command: moduleName, reason });
159
173
  continue;
160
174
  }
@@ -180,14 +194,15 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
180
194
  const content = await web.get(fileUrl);
181
195
  const fileContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
182
196
 
183
- const destPath = path.join(userRoot, 'commands', `${commandName}.tyr.ts`);
197
+ const fileName = toSafeFilename(commandName);
198
+ const destPath = path.join(userRoot, 'commands', `${fileName}.tyr.ts`);
184
199
  await fs.write(destPath, fileContent);
185
200
 
186
201
  if (managed && managed.module !== moduleName) {
187
202
  logger.warn(`'${commandName}' was previously managed by module '${managed.module}'; now owned by '${moduleName}'.`);
188
203
  }
189
204
 
190
- mapConfig.commands[commandName] = `./commands/${commandName}.tyr.ts`;
205
+ mapConfig.commands[commandName] = `./commands/${fileName}.tyr.ts`;
191
206
  lock.commands[commandName] = { module: moduleName, manifest: manifestUrl, source: fileUrl };
192
207
  mapChanged = true;
193
208
  lockChanged = true;
@@ -202,6 +217,7 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
202
217
  } catch (e) {
203
218
  const reason = e instanceof Error ? e.message : String(e);
204
219
  logger.error(`Could not download '${commandName}' from '${fileUrl}': ${reason}`);
220
+ logger.info(`Skipped '${commandName}': ${reason}`);
205
221
  summary.failed.push({ command: commandName, reason });
206
222
  }
207
223
  }
@@ -215,11 +231,18 @@ export async function syncModules(context: TyrContext, options: { force?: boolea
215
231
  `skipped: ${summary.skipped.length}, failed: ${summary.failed.length}.`
216
232
  );
217
233
 
234
+ if (summary.failed.length > 0) {
235
+ logger.info('Failures:');
236
+ for (const { command, reason } of summary.failed) {
237
+ logger.info(` - ${command}: ${reason}`);
238
+ }
239
+ }
240
+
218
241
  return summary;
219
242
  }
220
243
 
221
244
  /**
222
- * Registers a manifest URL under a module name in imported_modules.yml and
245
+ * Registers a manifest URL under a module name in imported_modules.yaml and
223
246
  * immediately syncs it (downloads whatever commands are missing).
224
247
  */
225
248
  export async function addModule(context: TyrContext, manifestUrl: string, name?: string): Promise<void> {
@@ -233,6 +256,7 @@ export async function addModule(context: TyrContext, manifestUrl: string, name?:
233
256
 
234
257
  if (!isValidHttpsUrl(manifestUrl)) {
235
258
  logger.error('Manifest URL must be a valid https:// URL.');
259
+ logger.info(`Could not add module: '${manifestUrl}' is not a valid https:// URL.`);
236
260
  return;
237
261
  }
238
262
 
@@ -242,6 +266,7 @@ export async function addModule(context: TyrContext, manifestUrl: string, name?:
242
266
  } catch (e) {
243
267
  const reason = e instanceof Error ? e.message : String(e);
244
268
  logger.error(`Could not add module: ${reason}`);
269
+ logger.info(`Could not add module: ${reason}`);
245
270
  return;
246
271
  }
247
272
 
@@ -313,12 +338,14 @@ export async function generateManifest(context: TyrContext): Promise<void> {
313
338
  remoteUrl = await shell.exec('git remote get-url origin');
314
339
  } catch {
315
340
  logger.error("~/.tyr's repository has no 'origin' remote configured. Cannot generate manifest.json.");
341
+ logger.info("Cannot generate manifest.json: ~/.tyr's repository has no 'origin' remote configured.");
316
342
  return;
317
343
  }
318
344
 
319
345
  const repoInfo = parseGithubRemote(remoteUrl);
320
346
  if (!repoInfo) {
321
347
  logger.error(`Cannot generate manifest.json: only GitHub repositories are supported (raw.githubusercontent.com). Remote was: ${remoteUrl}`);
348
+ logger.info(`Cannot generate manifest.json: only GitHub repositories are supported. Remote was: ${remoteUrl}`);
322
349
  return;
323
350
  }
324
351
 
@@ -328,10 +355,10 @@ export async function generateManifest(context: TyrContext): Promise<void> {
328
355
  if (currentBranch && currentBranch !== 'HEAD') {
329
356
  branch = currentBranch;
330
357
  } else {
331
- logger.warn(`Could not determine the current branch (detached HEAD?). Falling back to '${branch}'.`);
358
+ logger.info(`Could not determine the current branch (detached HEAD?). Falling back to '${branch}'.`);
332
359
  }
333
360
  } catch {
334
- logger.warn(`Could not determine the current branch. Falling back to '${branch}'.`);
361
+ logger.info(`Could not determine the current branch. Falling back to '${branch}'.`);
335
362
  }
336
363
 
337
364
  const mapPath = path.join(userRoot, 'map.yml');
@@ -347,7 +374,7 @@ export async function generateManifest(context: TyrContext): Promise<void> {
347
374
  : scriptPath.replace(/^\.[/\\]/, '');
348
375
 
349
376
  if (repoRelativePath.startsWith('..')) {
350
- logger.warn(`Skipping '${name}': its file lives outside ~/.tyr and can't be published.`);
377
+ logger.info(`Skipping '${name}': its file lives outside ~/.tyr and can't be published.`);
351
378
  skipped.push(name);
352
379
  continue;
353
380
  }
@@ -357,7 +384,7 @@ export async function generateManifest(context: TyrContext): Promise<void> {
357
384
  }
358
385
 
359
386
  if (Object.keys(manifest).length === 0) {
360
- logger.warn('No commands to include in manifest.json.');
387
+ logger.info('No commands to include in manifest.json.');
361
388
  }
362
389
 
363
390
  const manifestPath = path.join(userRoot, 'manifest.json');