66 readdirSync ,
77 statSync ,
88} from "node:fs" ;
9- import { join , basename , dirname , resolve } from "node:path" ;
9+ import { join , basename , dirname , resolve , sep } from "node:path" ;
1010import chalk from "chalk" ;
1111import { Command , Option } from "clipanion" ;
1212import {
@@ -41,6 +41,59 @@ interface SkillFrontmatter {
4141 version ?: string ;
4242}
4343
44+ function parseSkillFrontmatter ( content : string ) : SkillFrontmatter {
45+ const match = content . match ( / ^ - - - \r ? \n ( [ \s \S ] * ?) \r ? \n - - - / ) ;
46+ if ( ! match ) return { } ;
47+
48+ const frontmatter : SkillFrontmatter = { } ;
49+ const lines = match [ 1 ] . split ( / \r ? \n / ) ;
50+ let inTagsList = false ;
51+
52+ for ( const line of lines ) {
53+ if ( inTagsList ) {
54+ const tagMatch = line . match ( / ^ \s * - \s * ( .+ ) $ / ) ;
55+ if ( tagMatch ) {
56+ frontmatter . tags ??= [ ] ;
57+ frontmatter . tags . push ( tagMatch [ 1 ] . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ) ;
58+ continue ;
59+ }
60+ if ( line . trim ( ) === "" ) continue ;
61+ inTagsList = false ;
62+ }
63+
64+ const colonIdx = line . indexOf ( ":" ) ;
65+ if ( colonIdx === - 1 ) continue ;
66+
67+ const key = line . slice ( 0 , colonIdx ) . trim ( ) ;
68+ const value = line . slice ( colonIdx + 1 ) . trim ( ) ;
69+
70+ switch ( key ) {
71+ case "name" :
72+ frontmatter . name = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
73+ break ;
74+ case "description" :
75+ frontmatter . description = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
76+ break ;
77+ case "version" :
78+ frontmatter . version = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
79+ break ;
80+ case "tags" :
81+ if ( value . startsWith ( "[" ) ) {
82+ frontmatter . tags = value
83+ . slice ( 1 , - 1 )
84+ . split ( "," )
85+ . map ( ( t ) => t . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) )
86+ . filter ( ( t ) => t . length > 0 ) ;
87+ } else if ( value === "" ) {
88+ inTagsList = true ;
89+ frontmatter . tags = [ ] ;
90+ }
91+ break ;
92+ }
93+ }
94+ return frontmatter ;
95+ }
96+
4497export class PublishCommand extends Command {
4598 static override paths = [ [ "publish" ] ] ;
4699
@@ -186,7 +239,7 @@ export class PublishCommand extends Command {
186239 skill . safeName ,
187240 ) ;
188241 const resolvedDir = resolve ( mintlifyDir ) ;
189- if ( ! resolvedDir . startsWith ( resolvedOutput ) ) {
242+ if ( ! resolvedDir . startsWith ( resolvedOutput + sep ) ) {
190243 console . log (
191244 chalk . red ( `Skipping ${ skill . safeName } (path traversal detected)` ) ,
192245 ) ;
@@ -255,7 +308,7 @@ export class PublishCommand extends Command {
255308 const resolvedSkillDir = resolve ( skillDir ) ;
256309 const resolvedWellKnownDir = resolve ( wellKnownDir ) ;
257310
258- if ( ! resolvedSkillDir . startsWith ( resolvedWellKnownDir ) ) {
311+ if ( ! resolvedSkillDir . startsWith ( resolvedWellKnownDir + sep ) ) {
259312 console . log (
260313 chalk . yellow ( ` Skipping "${ skill . name } " (path traversal detected)` ) ,
261314 ) ;
@@ -360,8 +413,7 @@ export class PublishCommand extends Command {
360413 for ( const entry of entries ) {
361414 const entryPath = join ( skillPath , entry ) ;
362415 if ( statSync ( entryPath ) . isFile ( ) ) {
363- if ( entry . startsWith ( "." ) || entry === ".skillkit-metadata.json" )
364- continue ;
416+ if ( entry . startsWith ( "." ) ) continue ;
365417 files . push ( entry ) ;
366418 }
367419 }
@@ -374,57 +426,7 @@ export class PublishCommand extends Command {
374426 }
375427
376428 private parseFrontmatter ( content : string ) : SkillFrontmatter {
377- const match = content . match ( / ^ - - - \r ? \n ( [ \s \S ] * ?) \r ? \n - - - / ) ;
378- if ( ! match ) return { } ;
379-
380- const frontmatter : SkillFrontmatter = { } ;
381- const lines = match [ 1 ] . split ( / \r ? \n / ) ;
382- let inTagsList = false ;
383-
384- for ( const line of lines ) {
385- if ( inTagsList ) {
386- const tagMatch = line . match ( / ^ \s * - \s * ( .+ ) $ / ) ;
387- if ( tagMatch ) {
388- frontmatter . tags ??= [ ] ;
389- frontmatter . tags . push ( tagMatch [ 1 ] . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ) ;
390- continue ;
391- }
392- if ( line . trim ( ) === "" ) continue ;
393- inTagsList = false ;
394- }
395-
396- const colonIdx = line . indexOf ( ":" ) ;
397- if ( colonIdx === - 1 ) continue ;
398-
399- const key = line . slice ( 0 , colonIdx ) . trim ( ) ;
400- const value = line . slice ( colonIdx + 1 ) . trim ( ) ;
401-
402- switch ( key ) {
403- case "name" :
404- frontmatter . name = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
405- break ;
406- case "description" :
407- frontmatter . description = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
408- break ;
409- case "version" :
410- frontmatter . version = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
411- break ;
412- case "tags" :
413- if ( value . startsWith ( "[" ) ) {
414- frontmatter . tags = value
415- . slice ( 1 , - 1 )
416- . split ( "," )
417- . map ( ( t ) => t . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) )
418- . filter ( ( t ) => t . length > 0 ) ;
419- } else if ( value === "" ) {
420- inTagsList = true ;
421- frontmatter . tags = [ ] ;
422- }
423- break ;
424- }
425- }
426-
427- return frontmatter ;
429+ return parseSkillFrontmatter ( content ) ;
428430 }
429431}
430432
@@ -468,7 +470,7 @@ export class PublishSubmitCommand extends Command {
468470 const skillName =
469471 this . name || frontmatter . name || basename ( dirname ( skillMdPath ) ) ;
470472
471- const repoInfo = this . getRepoInfo ( dirname ( skillMdPath ) ) ;
473+ const repoInfo = await this . getRepoInfo ( dirname ( skillMdPath ) ) ;
472474 if ( ! repoInfo ) {
473475 console . error ( chalk . red ( "Not a git repository or no remote configured" ) ) ;
474476 console . error (
@@ -567,42 +569,7 @@ export class PublishSubmitCommand extends Command {
567569 }
568570
569571 private parseFrontmatter ( content : string ) : SkillFrontmatter {
570- const match = content . match ( / ^ - - - \r ? \n ( [ \s \S ] * ?) \r ? \n - - - / ) ;
571- if ( ! match ) return { } ;
572-
573- const frontmatter : SkillFrontmatter = { } ;
574- const lines = match [ 1 ] . split ( / \r ? \n / ) ;
575-
576- for ( const line of lines ) {
577- const colonIdx = line . indexOf ( ":" ) ;
578- if ( colonIdx === - 1 ) continue ;
579-
580- const key = line . slice ( 0 , colonIdx ) . trim ( ) ;
581- const value = line . slice ( colonIdx + 1 ) . trim ( ) ;
582-
583- switch ( key ) {
584- case "name" :
585- frontmatter . name = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
586- break ;
587- case "description" :
588- frontmatter . description = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
589- break ;
590- case "version" :
591- frontmatter . version = value . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) ;
592- break ;
593- case "tags" :
594- if ( value . startsWith ( "[" ) ) {
595- frontmatter . tags = value
596- . slice ( 1 , - 1 )
597- . split ( "," )
598- . map ( ( t ) => t . trim ( ) . replace ( / ^ [ " ' ] | [ " ' ] $ / g, "" ) )
599- . filter ( ( t ) => t . length > 0 ) ;
600- }
601- break ;
602- }
603- }
604-
605- return frontmatter ;
572+ return parseSkillFrontmatter ( content ) ;
606573 }
607574
608575 private slugify ( name : string ) : string {
@@ -613,10 +580,12 @@ export class PublishSubmitCommand extends Command {
613580 . replace ( / ^ - + | - + $ / g, "" ) ;
614581 }
615582
616- private getRepoInfo ( dir : string ) : { owner : string ; repo : string } | null {
583+ private async getRepoInfo (
584+ dir : string ,
585+ ) : Promise < { owner : string ; repo : string } | null > {
617586 try {
618- const { execSync } = require ( "node:child_process" ) ;
619- const remote = execSync ( "git remote get-url origin" , {
587+ const { execFileSync } = await import ( "node:child_process" ) ;
588+ const remote = execFileSync ( "git" , [ " remote" , " get-url" , " origin"] , {
620589 cwd : dir ,
621590 encoding : "utf-8" ,
622591 stdio : [ "pipe" , "pipe" , "ignore" ] ,
0 commit comments